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) - Atrue
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 becarb::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:
packageId
(carb::RString
) - The name of the extension package.
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:
packageId
(carb::RString
) - The name of the extension package.
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:
packageId
(carb::RString
) - The name of the extension package.
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 is1.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 typically1.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.