Kit Kernel Event System#

Applications built on Kit Kernel have a global name-based event distribution system based on the carb.eventdispatcher plugin. Events are a loose-coupling system that allows systems to be interconnected but depend on each other to the least practicable extent. This means that systems can communicate without needing to know anything about each other.

Example#

Imagine that we have an asset loading system, and we have a UI element that lists all resources currently being loaded. One approach to this might be to have the UI list query the asset system for a list of loading assets every UI update, but polling in this manner can be slow. Another option might be to have a means of registering callbacks for the asset system to notify when it starts and finishes loading an asset. This however introduces a dependency on the asset system. If we later have multiple asset providers, they may have a different method for communicating this information. The more dependent our UI system is on the asset system, the stronger the coupling and more work must go in to maintaining the coupling.

Enter our carb.eventdispatcher system for weak coupling. Now instead of the asset system having to maintain callbacks to call when it starts or finishes loading an asset, instead it simply dispatches an event through carb.eventdispatcher. No other systems may be listening to this event, or a hundred other systems may be listening. If no other system is listening, the dispatch is incredibly cheap.

Events are next to useless unless they can provide parameters, so naturally carb.eventdispatcher allows for parameters. In this case, the name of the asset being loaded would be an important parameter, but there could be others as well, such as who requested the asset, or which medium is providing the asset.

The other side of event dispatching is listening to an event or observing the event. When an event is observed, a callback is received whenever that event is dispatched. Our example UI system can observe the event and therefore receive a callback when an asset that we’re interested in is dispatched.

See the documentation for IEventDispatcher for observing and dispatching events.

Registration Example#

Registering for a global event is easily performed through IEventDispatcher since all events exist in the same global namespace.

        auto ed = carb::getCachedInterface<carb::eventdispatcher::IEventDispatcher>();

        // observeEvent() returns an ObserverGuard which we capture as m_updateSubscription. When the ObserverGuard is
        // reset or destroyed, the observer will no longer be notified of events.
        m_updateSubscription = ed->observeEvent(
            RStringKey("IApp InternalUpdate"),      // The observer name for debugging and profiling
            omni::kit::update::eKitInternalUpdate,  // The 'order' parameter, determining the global observer call order
            kGlobalEventPostUpdate,                 // The event to observe

            // The invocable to call when a dispatched event is observed
            [this](const carb::eventdispatcher::Event& event) {
                CARB_UNUSED(event);
                _internalUpdate(true);
            }
        );

Events 1.0 Backwards Compatibility#

Events 1.0 behavior is provided by maintaining adapters created through IEventsAdapter. This allows plugins and extensions that used IEventStream to continue functioning without explicit conversion to the Events 2.0 model.

The above example was expressed as Events 1.0 as follows:

m_updateSubscription = carb::events::createSubscriptionToPop(
    getRunLoop(nullptr)->postUpdate,        // The event stream to subscribe to

    // The invocable to call when an event is dispatched
    [this](carb::events::IEvent* event) {
        CARB_UNUSED(event);
        _internalUpdate(true);
    },
    omni::kit::update::eKitInternalUpdate,  // The 'order' parameter, determining the global subscriber call order
    "IApp InternalUpdate"                   // The name of the subscription for debugging and profiling
);

As you can see, the parameters are very similar. The most notable difference is that in Events 1.0 the IEventStream to subscribe to would need to be located and passed to the function that creates a subscription. With Events 2.0 since all events are referred to by name in a global namespace, this is no longer the case. Instead of having to chase pointers and call functions to find the proper event stream, use a RString wrapping the name of the event to observe.

Another difference is the name parameter. With Events 1.0 this was optional and often unspecified. With Events 2.0 it is required. It is also passed as a RStringKey to reduce memory and performance cost in copying the string.

Events 1.0 Message Bus#

Historically, omni::kit::RunLoop has a member messageBus that is used as a generic event stream so that individual systems do not need to create their own event streams. This Events 1.0 paradigm is no longer needed with Events 2.0 since the IEventDispatcher is global.

To maintain backwards compatibility, the messageBus remains per RunLoop, but as an adapter that redirects subscriptions to the global IEventDispatcher. Since the messageBus is a generic event stream and any event can be queued to it, this adapter requires that aliases be set to map the Events 1.0 EventType (typically a string hash) to Events 2.0 RString names.

The alias can be registered from Python by omni.kit.app.register_event_alias() or from C++ by carb::events::registerEventAlias(). The alias must be registered prior to using the events in a dispatch call, either through IEventDispatcher or the messageBus. An EventType will have two named events: an immediate event (that corresponds to the push-side subscription) and a deferred event (that corresponds to the typical pop-side subscription). Best practice is to have the deferred event be named the standard event name (e.g. omni.kit.app:script_command), and the immediate event append :immediate to the standard event name (e.g. omni.kit.app:script_command:immediate). This is automatically done by the Python function omni.kit.app.register_event_alias() so that typically only one event name needs to be given:

import carb.events
import omni.kit.app

MY_OLD_EVENT: int = carb.events.type_from_string("my.extension:Event") # string hash
MY_NEW_EVENT: str = "my.extension:Event"
omni.kit.app.register_event_alias(MY_OLD_EVENT, MY_NEW_EVENT)

The alias allows observers of MY_NEW_EVENT to also be called when MY_OLD_EVENT is pushed to messageBus, or for subscribers of MY_OLD_EVENT to be called when MY_NEW_EVENT is dispatched through IEventDispatcher.

messageBus functions as a message queue. That is, events would be pushed to the event stream and then the event stream would be pumped as the last task of a RunLoop update. Typically subscribers are pop-side and events would be delivered to these subscribers during the pump. The adapter that now impersonates messageBus also uses a queue concept, now through IMessageQueue. The IMessageQueue instance can be found via its name and carb::eventdispatcher::IMessageQueueFactory::getMessageQueue(). The name of the main (default) RunLoop’s message queue is messageBus (as constant omni::kit::kGlobalMessageBus), but other RunLoop objects use the name runloop:<runloop name>:messageBus.

While not typical, it was possible to push-side subscribe to a messageBus event, so that the subscription was called immediately when the event was queued, not when the queue was popped. This is still possible by observing the immediate event name (such as the omni.kit.app:script_command:immediate example given above).

To assist, there exists a function omni.kit.app.queue_event() to aid sending these generic events. The function will automatically append :immediate to the given event_name and dispatch immediate to any subscribers, and then queue the deferred event to the appropriate IMessageQueue given by the optional keyword argument runloop_name.

omni.kit.app.queue_event(MY_NEW_EVENT, payload={ "text": "hello world" })

Subscribing to Message Bus events is similar to other events.

from carb.eventdispatcher import get_eventdispatcher

# Subscribe to the deferred event (typical)
sub = get_eventdispatcher().observe_event(
    observer_name="My debug name",
    event_name=MY_NEW_EVENT,
    on_event=lambda e: print(f"deferred: {e['text']}")
)

# Subscribe to the immediate event (not typical)
sub2 = get_eventdispatcher().observe_event(
    observer_name="My debug name",
    event_name=MY_NEW_EVENT + ":immediate",
    on_event=lambda e: print(f"immediate: {e['text']}")
)

Event List#

The following list serves as documentation for Kit Kernel events. Note that event names and parameter names are case-sensitive.

Event names are given as interned strings using the RString system. Parameters are passed via the NamedVariant type; the key is given as an interned string using the RStringKey system.

omni.kit.app App Lifecycle Events#

omni.kit.app:started#

Dispatched at application startup time by the omni.kit.app plugin immediately before omni::kit::IApp::startup() returns.

No parameters.

See omni::kit::kGlobalEventAppStarted (C++) and omni.kit.app.GLOBAL_EVENT_APP_STARTED (Python).


omni.kit.app:ready#

Dispatched by omni.kit.app once the application enters a ready state. After application startup, every call to omni::kit::IApp::update() after the first call will check for readiness. Readiness can be delayed by calling omni::kit::IApp::delayAppReady() which must be called during each main loop iteration to prevent the omni.kit.app:ready event from being dispatched. Once a run loop completes without any calls to delay readiness, the application is considered “ready” and omni.kit.app:ready is dispatched.

No parameters.

See omni::kit::kGlobalEventAppReady and omni.kit.app.GLOBAL_EVENT_APP_READY (Python).


omni.kit.app:post_quit#

Dispatched during the next update after omni::kit::IApp::postQuit() is called. Once postQuit is called, the next omni::kit::IApp::update() will start the shutdown sequence. The first step of this sequence is to dispatch omni.kit.app:post_quit. Any observers of this event may - DURING EVENT DISPATCH - call omni::kit::IApp::tryCancelShutdown() to abort the shutdown process, unless the posted quit request was un-cancellable. If the request to quit was not cancelled, omni.kit.app:pre_shutdown is then dispatched.

Parameters:

  • uncancellable (bool) - A true value indicates that the shutdown cannot be cancelled.

See omni::kit::kGlobalEventPostQuit and omni.kit.app.GLOBAL_EVENT_POST_QUIT (Python).


omni.kit.app:pre_shutdown#

Dispatched to indicate the start of shutdown.

No parameters.

See omni::kit::kGlobalEventPreShutdown and omni.kit.app.GLOBAL_EVENT_PRE_SHUTDOWN (Python).

omni.kit.app Scripting Events#

omni.kit.app:script_command and omni.kit.app:script_command:immmediate#

These events are dispatched when a script command is issued via omni::kit::IAppScripting::executeString() or omni::kit::IAppScripting::executeString().

The event with the :immediate suffix is sent immediately upon when the function is called, but the primary event is queued and dispatched during the omni::kit::IApp::update() function.

Parameters:

  • text (string) - A human-readable (possibly multi-line) text block.

See omni::kit::kGlobalEventScriptingCommand and omni::kit::kGlobalEventScriptingCommandImmediate (C++) or omni.kit.app.GLOBAL_EVENT_SCRIPT_COMMAND and omni.kit.app.GLOBAL_EVENT_SCRIPT_COMMAND_IMMEDIATE (Python).


omni.kit.app:script_stdout and omni.kit.app:script_stdout:immediate#

Dispatched when Python produces stdout stream output.

The event with the :immediate suffix is sent immediately upon when the output is generated, but the primary event is queued and dispatched during the omni::kit::IApp::update() function.

Events are dispatched per-line, so multi-line output may cause several events to be sent in succession.

Parameters:

  • text (string) - A human-readable (typically single-line) text block.

See omni::kit::kGlobalEventScriptingStdOut and omni::kit::kGlobalEventScriptingStdOutImmediate (C++) or omni.kit.app.GLOBAL_EVENT_SCRIPT_STDOUT and omni.kit.app.GLOBAL_EVENT_SCRIPT_STDOUT_IMMEDIATE (Python).


omni.kit.app:script_stderr and omni.kit.app:script_stderr:immediate#

Dispatched when Python produces stderr stream output.

The event with the :immediate suffix is sent immediately upon when the output is generated, but the primary event is queued and dispatched during the omni::kit::IApp::update() function.

Events are dispatched per-line, so multi-line output may cause several events to be sent in succession.

Parameters:

  • text (string) - A human-readable (typically single-line) text block.

See omni::kit::kGlobalEventScriptingStdErr and omni::kit::kGlobalEventScriptingStdErrImmediate (C++) or omni.kit.app.GLOBAL_EVENT_SCRIPT_STDERR and omni.kit.app.GLOBAL_EVENT_SCRIPT_STDERR_IMMEDIATE (Python).

Other omni.kit.app Events#

omni.kit.app:error_log and omni.kit.app:error_log:immediate#

Dispatched when a warning or error log occurs.

The event with the :immediate suffix is sent immediately upon when the output is generated, but the primary event is queued and dispatched during the omni::kit::IApp::update() function.

Parameters:

  • level (integer) - The log level. Will be carb::logging::kLevelWarn or higher.

  • message (string) - The log message.

  • source (string) - The plugin or log channel that emitted the log message (error logs only).

  • filename (string) - The source filename that emitted the log message (error logs only).

  • functionName (string) - The function that emitted the log message (error logs only).

  • lineNumber (integer) - The line number in the source filename that emitted the log message (error logs only).

See omni::kit::kGlobalEventErrorLog and omni::kit::kGlobalEventErrorLogImmediate (C++) or omni.kit.app.GLOBAL_EVENT_ERROR_LOG and omni.kit.app.GLOBAL_EVENT_ERROR_LOG_IMMEDIATE (Python).

omni.ext Events#

omni.ext:script_changed and omni.ext:script_changed:immediate#

Dispatched when the extension model changes, such as when an extension is started or stopped, or when the extension registry changes.

The event with the :immediate suffix is sent during the first omni::ext::ExtensionManager::processAndApplyAllChanges(), call when a change occurs, but the primary event is queued and dispatched during the omni::kit::IApp::update() function.

The event should be sent a maximum of once per frame.

No parameters.

See omni::ext::kGlobalEventScriptChanged and omni::ext::kGlobalEventScriptChangedImmediate (C++) or omni.ext.GLOBAL_EVENT_SCRIPT_CHANGED and omni.ext.GLOBAL_EVENT_SCRIPT_CHANGED_IMMEDIATE (Python).


omni.ext:folder_changed and omni.ext:folder_changed:immediate#

Dispatched when extension paths change, such as when omni::ext::ExtensionManager::addPath() or omni::ext::ExtensionManager::removePath() is called. The event with the :immediate suffix is sent during the function call that changes the extension paths, but the primary event is queued and dispatched during the next omni::kit::IApp::update() function call.

The event should be sent a maximum of once per frame.

No parameters.

See omni::ext::kGlobalEventFolderChanged and omni::ext::kGlobalEventFolderChangedImmediate (C++) or omni.ext.GLOBAL_EVENT_FOLDER_CHANGED and omni.ext.GLOBAL_EVENT_FOLDER_CHANGED_IMMEDIATE (Python).


omni.ext:registry_refresh_begin and omni.ext:registry_refresh_begin:deferred#

Dispatched when registry refresh is about to begin. The event with the :deferred suffix is dispatched at the end of the frame, during the next omni::kit::IApp::update() function call.

No parameters.

See omni::ext::kGlobalEventRegistryRefreshBegin (C++) or omni.ext.GLOBAL_EVENT_REGISTRY_REFRESH_BEGIN (Python). See omni::ext::kGlobalEventRegistryRefreshBeginDeferred (C++) or omni.ext.GLOBAL_EVENT_REGISTRY_REFRESH_BEGIN_DEFERRED (Python).


omni.ext:registry_refresh_end#

Dispatched immediately when registry refresh has ended.

Parameters:

  • success (bool) - true if the registry was successfully refreshed; false otherwise.

See omni::ext::kGlobalEventRegistryRefreshEnd (C++) or omni.ext.GLOBAL_EVENT_REGISTRY_REFRESH_END (Python).


omni.ext:registry_refresh_end:success and omni.ext:registry_refresh_end:success:immediate#

Dispatched at the end of the frame during the next omni::kit::IApp::update() call when registry refresh has ended successfully. The event with the :immediate suffix does not wait for the end of the frame and is dispatched immediately upon the end of the successful registry refresh.

No parameters.

See omni::ext::kGlobalEventRegistryRefreshEndSuccess (C++) or omni.ext.GLOBAL_EVENT_REGISTRY_REFRESH_END_SUCCESS (Python). See omni::ext::kGlobalEventRegistryRefreshEndSuccessImmediate (C++) or omni.ext.GLOBAL_EVENT_REGISTRY_REFRESH_END_SUCCESS_IMMEDIATE (Python).


omni.ext:registry_refresh_end:failure and omni.ext:registry_refresh_end:failure:immediate#

Dispatched at the end of the frame during the next omni::kit::IApp::update() call when registry refresh has ended in failure. The event with the :immediate suffix does not wait for the end of the frame and is dispatched immediately upon the end of the failed registry refresh.

No parameters.

See omni::ext::kGlobalEventRegistryRefreshEndFailure (C++) or omni.ext.GLOBAL_EVENT_REGISTRY_REFRESH_END_FAILURE (Python). See omni::ext::kGlobalEventRegistryRefreshEndFailureImmediate (C++) or omni.ext.GLOBAL_EVENT_REGISTRY_REFRESH_END_FAILURE_IMMEDIATE (Python).


omni.ext:extension_pull_begin and omni.ext:extension_pull_begin:deferred#

Dispatched when an extension package is about to be pulled. The event with the :deferred suffix is dispatched at the end of the frame, during the next omni::kit::IApp::update() function call.

Parameters:

See omni::ext::kGlobalEventExtensionPullBegin (C++) or omni.ext.GLOBAL_EVENT_EXTENSION_PULL_BEGIN (Python). See omni::ext::kGlobalEventExtensionPullBeginDeferred (C++) or omni.ext.GLOBAL_EVENT_EXTENSION_PULL_BEGIN_DEFERRED (Python).


omni.ext:extension_pull_end#

Dispatched when an extension package pull has ended.

Parameters:

  • packageId (carb::RString) - The name of the extension package.

  • success (bool) - true if the extension was pulled successfully; false otherwise.

See omni::ext::kGlobalEventExtensionPullEnd (C++) or omni.ext.GLOBAL_EVENT_EXTENSION_PULL_END (Python).


omni.ext:extension_pull_end:success and omni.ext:extension_pull_end:success:immediate#

Dispatched at the end of the frame during the next omni::kit::IApp::update() call when an extension package pull has ended successfully. The event with the :immediate suffix does not wait for the end of the frame and is dispatched immediately upon the end of the successful extension pull.

Parameters:

See omni::ext::kGlobalEventExtensionPullEndSuccess (C++) or omni.ext.GLOBAL_EVENT_EXTENSION_PULL_END_SUCCESS (Python). See omni::ext::kGlobalEventExtensionPullEndSuccessImmediate (C++) or omni.ext.GLOBAL_EVENT_EXTENSION_PULL_END_SUCCESS_IMMEDIATE (Python).


omni.ext:extension_pull_end:failure and omni.ext:extension_pull_end:failure:immediate#

Dispatched at the end of the frame during the next omni::kit::IApp::update() call when an extension package pull has ended in failure. The event with the :immediate suffix does not wait for the end of the frame and is dispatched immediately upon the end of the failed extension pull.

Parameters:

See omni::ext::kGlobalEventExtensionPullEndSuccess (C++) or omni.ext.GLOBAL_EVENT_EXTENSION_PULL_END_SUCCESS (Python). See omni::ext::kGlobalEventExtensionPullEndSuccessImmediate (C++) or omni.ext.GLOBAL_EVENT_EXTENSION_PULL_END_SUCCESS_IMMEDIATE (Python).


Main RunLoop Events#

The main RunLoop is created via omni::kit::IApp::getRunLoop() and is typically managed by omni::kit::IRunLoopRunner.


preUpdate / update / postUpdate#

Each main loop during omni::kit::IApp::update() omni.kit.app will call the registered omni::kit::IRunLoopRunner’s update function. This function will dispatch preUpdate, followed by update, followed by postUpdate.

Typically, each update event will have the following parameters. However, this is up to the omni::kit::IRunLoopRunner implementation.

  • dt (double) - Effectively the length of time (in seconds) of the previous frame. The first frame is 1.0 / 60.0.

  • SWHFrameNumber (int64) - The frame number beginning at 0.

See omni::kit::kGlobalEventPreUpdate, omni::kit::kGlobalEventUpdate and omni::kit::kGlobalEventPostUpdate (C++), or omni.kit.app.GLOBAL_EVENT_PRE_UPDATE, omni.kit.app.GLOBAL_EVENT_UPDATE and omni.kit.app.GLOBAL_EVENT_POST_UPDATE (Python).


Other RunLoop Events#

omni::kit::RunLoop instances that are created with omni::kit::IApp::getRunLoop() with names other than main will also issue preUpdate, update, and postUpdate events, but they will be prefixed as follows:

runloop:<name>:<event>

For instance, a RunLoop named animation would dispatch the following events in order: runloop:animation:preUpdate, runloop:animation:update, and runloop:animation:postUpdate.

Typically, each update event will have the following parameters. However, this is up to the omni::kit::IRunLoopRunner implementation.

  • dt (double) - Effectively the length of time (in seconds) of the previous frame. The first frame is typically 1.0 / 60.0.

Note

As these events are composed, there are no constants that already define them. It is recommended to create constants referencing the various runloop events when a runloop is created.