Coroutines

When working with the Virt-A-Mate API, you often need to perform several actions in sequence. For instance, you might want to spawn an atom, wait until it is registered in the scene, and then perform additional actions on it (such as parenting it to another object). However, some operations—like atom spawning—are not instantaneous. In these cases, attempting to access the new atom immediately can lead to issues because it hasn’t been fully initialized by the system.

What Is a Coroutine?

A coroutine is a special method in Unity that allows you to pause its execution (using the yield keyword) and then resume from where it left off in a later frame. This makes coroutines ideal for tasks that require waiting or for sequencing actions over multiple frames.

Key Points about Coroutines:

  • Non-blocking: Coroutines let you wait for conditions (like a time delay or an event) without freezing the rest of your application.
  • Sequential Execution: They allow you to write code that appears sequential (step-by-step) while handling asynchronous events.
  • Integration with Unity: In Unity (and therefore in Virt-A-Mate plugins, which are built on Unity), coroutines are implemented as methods returning an IEnumerator.

General Structure of a Coroutine

A typical coroutine in VAM looks like this:

using System.Collections;
using UnityEngine;
using SimpleJSON;

public class VAMExampleCoroutine : MVRScript
{
    public override void Init()
    {
        // Start the coroutine when the plugin is initialized.
        StartCoroutine(WaitAndPrint());
    }

    IEnumerator WaitAndPrint()
    {
        // Wait for 2 seconds.
        yield return new WaitForSeconds(2f);
        SuperController.LogMessage("2 seconds have passed");

        // Wait until a condition is met.
        while (!SomeCondition())
        {
            yield return null;  // Wait for the next frame.
        }
        SuperController.LogMessage("Condition met");
    }

    bool SomeCondition()
    {
        // Define your condition here.
        // For example, wait until 5 seconds have passed since the plugin started.
        return Time.time > 5f;
    }
}
  • IEnumerator WaitAndPrint()
    This is the coroutine method. It returns an IEnumerator, which is required for all coroutines.

  • yield return new WaitForSeconds(2f);
    This line pauses the coroutine for 2 seconds. Other code in your application continues to run during this time.

  • yield return null;
    This pauses execution until the next frame, useful for continuously checking a condition without blocking the main thread.

Use Cases for Coroutines in Virt-A-Mate Plugins

Waiting for an Atom or Plugin to Become Available

Sometimes you need to perform actions on an atom or plugin that isn’t immediately available when your code executes. For example, if you’re waiting for a specific plugin to load or an atom to spawn, a coroutine can periodically check for its availability.

Example Scenario: Imagine you have a plugin named “AdvancedLighting” that must be loaded before you can adjust some scene parameters. You can write a coroutine that checks every frame (or every few frames) until the plugin becomes available.

IEnumerator WaitForAtomAndDoSomething(string atomUID, float timeout = 5f)
{
    float elapsed = 0f;
    Atom targetAtom = null;
    
    while (elapsed < timeout)
    {
        targetAtom = GetAtomById(atomUID);
        if (targetAtom != null)
        {
            SuperController.LogMessage(atomUID + " is now available.");
            break;
        }
        elapsed += Time.deltaTime;
        yield return null; // Wait for the next frame.
    }
    
    if (targetAtom == null)
    {
        SuperController.LogError(atomUID + " did not become available within the timeout period.");
        yield break;
    }
    
    // Perform the desired action now that the atom is available.
    // Example: Parent or modify the atom.
}

Periodic Monitoring or Updating

Coroutines are ideal for tasks that require periodic updates—such as polling for changes, updating UI elements, or triggering regular events.

Example Scenario: You might want to periodically update a status message in your plugin UI to reflect changes in the scene or monitor performance metrics.

IEnumerator PeriodicStatusUpdate(float interval)
{
    while (true)
    {
        // Example: count scene atoms and log the count.
        int atomCount = SuperController.singleton.GetAtoms().Count;
        SuperController.LogMessage("Current atom count: " + atomCount);
        
        // Wait for the specified interval before the next update.
        yield return new WaitForSeconds(interval);
    }
}

Sequential Task Execution with Delays

Often, you need to execute multiple tasks in a specific order with delays between them. For instance, you might:

  1. Spawn an atom.
  2. Wait for it to register.
  3. Modify its properties.
  4. Then perform additional actions like linking or parenting.

Example Scenario: Consider a process where you want to spawn a Cube, wait for its registration, then change its color, and finally log a message.

IEnumerator SpawnAndModifyCube()
{
    // Spawn the Cube atom.
    SuperController.singleton.AddAtomByType("Cube", false, false, false);
    SuperController.LogMessage("Cube spawned. Waiting for registration...");

    // Wait until the Cube is registered (or timeout after 2 seconds).
    float timeout = 2f;
    float elapsed = 0f;
    Atom cubeAtom = null;
    while (elapsed < timeout)
    {
        cubeAtom = GetAtomById("Cube");
        if (cubeAtom != null)
            break;
        elapsed += Time.deltaTime;
        yield return null;
    }
    
    if (cubeAtom == null)
    {
        SuperController.LogError("Cube was not found after spawning.");
        yield break;
    }
    
    // Modify the Cube—for example, change its color.
    Renderer rend = cubeAtom.gameObject.GetComponent<Renderer>();
    if (rend != null)
    {
        rend.material.color = Color.red;
        SuperController.LogMessage("Cube color changed to red.");
    }
    
    // Optionally, wait a bit more before the next action.
    yield return new WaitForSeconds(1f);
    SuperController.LogMessage("Cube spawn and modification sequence complete.");
}

Delaying Action for UI Feedback

Sometimes you might want to delay certain actions to improve the user experience—for example, to give users time to read a message or to create smooth animations.

Example Scenario: You want to display a temporary notification in the UI, then automatically hide it after a few seconds.

IEnumerator ShowTemporaryNotification(string message, float displayTime)
{
    // Display the notification (assuming you have a VAM UI text element).
    // notificationText is a reference to a UI element in your plugin.
    notificationText.text = message;
    notificationText.gameObject.SetActive(true);
    
    // Wait for the specified display time.
    yield return new WaitForSeconds(displayTime);
    
    // Hide the notification.
    notificationText.gameObject.SetActive(false);
    SuperController.LogMessage("Notification hidden.");
}

By using coroutines, you can write cleaner, more maintainable code that gracefully handles operations that depend on time or asynchronous events. This technique is essential for creating reliable and responsive Virt-A-Mate plugins.