QueueProcessor (class)

The QueueProcessor can be thought of as the air traffic controller for the SALSA LipSync suite. It will keep track of the activating and deactivating animations and resolve any conflicts to prevent jittery fighting over what an animation should be doing.

Allocating QueueProcessors

Each of the suite modules registers animations into the queue and the queue takes it from there. There can be any number of QueueProcessors in a scene; however, we recommend one per character. A QueueProcessor can only handle conflict resolution within itself so creating one queue per character is generally a great way to divide up responsibility and ensure the best performance for your implementation.

We do not recommend creating separate QueueProcessors for each module on a character. Doing so will separate the module animations and they will be unaware of each other, potentially creating conflicts.

Conflict Resolution

Conflict resolution or management comes in two basic varieties; internal conflicts and external conflicts. Internal conflicts arise when SALSA Suite modules (SALSA, Eyes, Emoter, etc.) attempt to animate the same underlying component (blendshape, bone, etc.) or components in separate configurations (viseme, emotes, eyes, lids, etc.) utilize the same underlying component. SALSA Suite is very good at handling its own internal animation conflicts. See below for information on queue hierarchy conflict resolution.

The second form of conflict, external influence, is not so easily remedied. Please read more about external influences and understand how to work with them, if possible.

Conflict Resolution in the Queue Hierarchy

There are different types of expressions available in the SALSA suite. NEW in version 2.3.0+ The QueueProcessor now has separate queues for each controller type that are hierarchically applied to the animation values. This eliminates conflict between controllers and can intelligently merge Suite animations backwards towards lower priority Queues (if enabled).

FOR EXAMPLE: Consider a character with an EmoteR emote applied for sleepy eyelids. When the Eyes module blinks the lids, the blink overrides the emote during the ON/HOLD phases. During the animation OFF phase, it then detects a lower-priority Suite queue is influencing the normal state of the character's lids and calculates MergeBack to smoothly return to the sleepy-emote's position.

Conflict resolution within the Queue hierarchy operates under the following rules (new and changed since v2.3.0) and it is expected that each new expression type registration will override itself:

  • Blink is the highest priority -- it is expected that involuntary blink options would override all other expression types.
  • LipSync is next in the list and technically it should be expected lipsync will not be interfered with by any other controller type, including Blink -- however, technically, Blink overrides Lipsync.
  • Emotes are next. In previous versions prior to 2.3.0, blinks were registered as Emotes -- this has changed and they now utilize the top-priority type above.
  • Lid tracking movement is next (if enabled/supported), followed by...
  • Eye movement, followed by...
  • Head movement.

In version 2.5.0+ of the SALSA Suite, control of influence detection has moved from the ExpressionController to the QueueProcessor since it is better equipped to process influence checks and intent. It now dynamically detects (when enabled) influence on each frame and may move in and out of MergeBack depending on detection of influence.

FOR EXAMPLE: Consider for a frame, an external influence may calculate the same 'position' as the ExpressionController calculated for the previous frame. There would be no influence detection for this frame, so the QueueProcessor would switch out of MergeBack for this frame. On the next frame, the external process has calculated a new position different from the ExpressionController and MergeBack is again calculated. To avoid instances where external influence is temporary, ExpressionControllers and the QueueProcessor continue to calculate throughout the animation OFF cycle and do not end early when the 'merge' appears to be complete.

Generally speaking, this is all handled behind the scenes for you. However, depending on your situation, you may have to plan your setup to ensure it operates as you intended. Ensure you have a single QueueProcessor for any expression types that need to be aware of other expression types for smoothly transitioning from one type to the next. See the above section on allocating QueueProcessors for more information.

There is now an option to enable intelligent MergeBack on a global basis. This option is located at the top of the QueueProcessor Inspector.
queueprocessor-mergeback-option

Also shown in this image, is a slider which controls the overall length of the component display window for the queue items. And a filter section for turning off the detail of the different controller type sections. This is useful for increasing performance of the component's Editor display as well as limiting the data available to the specific set you are looking at.

NOTE: section headers will still be visible for the specific controller types whether the detail is filtered out or not. Also note: the filter options are in hierarchical order from lowest to highest (Head is lower than Blink), meaning an Emo (emote) will be overwritten by a Blink.

Editor Performance

Unless you are trying to troubleshoot your configuration, we recommend keeping the QueueProcessor component collapsed in your inspector. While the QueueProcessor inspector is open/expanded, there is a visible impact on scene performance. It is only intended for troubleshooting purposes.

QueueProcessor API

The QueueProcessor is designed to be a black-box of operation where the designer and developer need not contribute to or change control. However, with version 2.3.0, the option to enable automatic handling of MergeBack is configurable in the QueueProcessor.

bool useMergeWithInfluencer

Enable global MergeBack for all component registrations. The QueueProcessor and ExpressionController will attempt to smoothly merge animations back to externally influenced values (i.e. Mecanim, custom script, etc.) changed during the Update cycle. If this option is not enabled, it may be enabled in individual component configurations.


bool ignoreScaledTime {true = ignore, false = respect}

New in v2.5.0+
Enable to put the QueueProcessor into a mode which ignores Unity's time scale functionality. If you want time to slow down or speed up in your scene, but leave one or more SALSA Suite-enabled characters operating at normal speed, enable this. The SALSA Suite core modules will then ignore any time scaling effects. SALSA, EmoteR, and Eyes all read their time values based on this QueueProcessor boolean setting. Since each character should have their own QueueProcessor, this allows you to set SALSA Suite characters individually with respect to their handling of scaled time. If you wish to have the character switch back to scaled time, simply setting this boolean to false is not enough. Instead, it will be necessary to call RespectScaledTime() [see method explanation below].

CrazyMinnow.SALSA.QueueProcessor qp;
qp.ignoreScaledTime = true; // any core module linked to this QueueProcessor will ignore time scaling.

qp.RespectScaledTime(); // re-instates time scaling on core modules linked to this QueueProcessor.

void Flush()

New in v2.5.0+
Call this function to clear all queues. NOTE: This will remove all queue entries without regard for the underlying component settings. In other words, this would remove a component while in midstream animation and will leave the animation partially ON if it has not completed its animation cycle. This may or may not be desirable or appropriate for your design decision. It may be more appropriate to gracefully shutdown components in a specific queue, if so, see ShutdownActiveQueue().


void ShutdownActiveQueue(int queueIndex)

New in v2.5.0+
This method will set all active queue items (in a specific queue -- see index list below) to OFF as though they had reached their apex and are now turning OFF. For example: this is a good function to call when you are programmatically redefining a configuration for SALSA, pointing the configuration to a different set of SkinnedMeshRenderers for a different LOD. It will close down all components and empty them from the queue, preventing zombie animation artifacts from occuring.

QueueProcessor QueueOrderOfOperation Indexes:
0 = Heads
1 = Eyes
2 = Lid Tracking
3 = Emotes
4 = LipSync
5 = Blinks

CrazyMinnow.SALSA.QueueProcessor qp;
// turns off all emote and lipsync components and empty them from the queue.
qp.ShutdownActiveQueue(3);
qp.ShutdownActiveQueue(4);

void RespectScaledTime() [experimental]

New in v2.5.0+
This is a somewhat experimental feature that works hand-in-hand with the ignoreScaledTime boolean field [see above]. SALSA, EmoteR, and Eyes are all linked to a QueueProcessor, preferably the same QueueProcessor. Each has its own timer calculation and that calculation needs to be reset if the time value is currently being read from non-scaled time and needs to read from scaled time. Simply switching the ignoreScaledTime boolean back to false will eventually switch the character into scaled time mode, but it will take quite some time to do so. In the meantime, the character will essentially be doing nothing. RespectScaledTime() fires off an event that each module listens for and when called, the cores reset their time counters to scaled time, instantly switching them back to scaled time.

NOTE: Calling this method will automatically set ignoreScaledTime to false.

CrazyMinnow.SALSA.QueueProcessor qp;
qp.RespectScaledTime();