Give Page Feedback | API

World Grouping Listeners - How To Create Custom World Grouping Listeners

Custom World Grouping Listeners are the most convenient means of adding functionality to the Streamable Assets Manager because they give you access to the World Cells and Asset Chunks which are loaded by SAM, allowing you to do whatever you want with them at several different stages of their "life cycle". For this reason, they are also the primary means of integrating 3rd Party Assets into SAM workflow and pipeline! Before getting into how to create a custom World Grouping Listener, however, it's necessary to talk a bit about how World Grouping Listeners function under the hood.

What Is A World Grouping Listener, Really?

At its core a World Grouping Listener is simply any class which implements the IWorldGroupingListener interface. This interface represents a contract that defines a robust set of methods (10 in total), allowing for the World to pass World Cells to the Listener at various points in its life cycle.

Because these methods all utilize the IEnumerator return type, their invocation is not necessarily cheap. In addition, most Listeners will only wish to utilize a subset of the full method group. As such, the interface also defines getter Properties for each method, which can be used to tell the World whether the associated method should actually be used/invoked. This eliminates the invocation of methods that are not used by the Listener.

Now, if you wish to create a custom class that implements the IWorldGroupingListener interface directly, we won't stop you! However, providing overrides for every single method and property, even if most will not be used, sounds like a dumb idea, don't you think?

Enter automated method implementation detection with our convenient abstract classes!

Here's how it works. If you want to create a Listener that is also a MonoBehaviour, simply derive from the WorldGroupingListener base abstract class. If you want to create a non MonoBehaviour Listener, derive from the WorldGroupingListenerNonComponent base abstract class.

When you derive from one of these two classes, you only need to override the methods that you wish to use. The base classes are able to detect which methods have been overridden and automatically set the values of those methods' associated properties to return either true (if the method is overridden) or false (if it is not overridden).

ISN'T THAT COOL!?

--Special Note 1--
If you decide to create a non MonoBehaviour Listener, obviously it cannot be assigned to the World Grouping via the inspector. Don't worry! You can assign the World Grouping Listener to the World Grouping using the World's AddWorldGroupingListener method, before or after the World has been initialized. Any listeners added this way are added to the end of the listener array. However, note that listeners added this way will not persist between game saves, so you will need to re-add them each time the game is run.

Now, I know what you're thinking . . . "BUT WHICH METHODS DO I OVERRIDE!?"

No need to scream! In just a minute, I'm going to give you all the information you need to make an intelligently informed decision. First, however, we need to cover a few other members that you may wish to override (or at least know about).

--Special Note 2--
You should place the class definition inside of theDeepSpaceLabs.SAM; namespace in order to use the interface or abstract classes! No matter which class or interface you decide to derrive from, an easy way to start with your custom class is to hover over your custom class name or interface and choose the option Show potential fixes -> Implement abstract class/Implement interface. This will provide default overrides/implementations for all abstract class/interface members.

In addition, we recommend adding the following attribute above your class, which will ensure the custom listener is shown with other World Grouping Listeners in menus:

[AddComponentMenu(GlobalValues.COMPONENT_ROOT_PATH + "World Grouping Listeners/Custom Listener Name")]

Component Based Listener API

Non Component Based Listener API

IsEnabled (Property - Implementation Varies

Required when implementing the IWorldGroupingListener interface directly.

Optional when deriving from WorldGroupingListener or WorldGroupingListenerNonComponent.

This property is queried by the World every time the Listener would have one of its methods invoked. If it returns false, whatever method that would have been invoked is skipped, and the World proceeds to the next Listener. This offers a convenient way to disable Listeners (perhaps temporarily) at runtime without needing to remove them completely (which, by the way, you can do using the World's RemoveWorldGroupingListener method).

If implementing the IWorldGroupingListener interface directly, you will need to provide an implementation for this property. If deriving from WorldGroupingListenerNonComponent, the property is implemented to always return true, however you can override it to provide custom logic. If you are instead deriving from the WorldGroupingListener class described above (which, why wouldn't you!?), you can override the property, however that's only necessary if you don't like its default logic, which is . . .

If the Listener component is enabled and the game object it is on is active, return true; otherwise, return false.

Returning false will disable the Listener for all LODs. If you only want to disable the Listener for a select set of LODs, use the UseWithLOD method instead.

IgnoreLODTransitions (Property - Implementation Required)

This property is very important.

The value returned indicates whether the listener's methods will be called during LOD transitions. When an LOD transition occurs, technically a World Cell is deactivated (the old LOD) and a World Cell is activated (the new LOD), however generally it is better to think of these two World Cells as the same Cell that is just changing its Asset Chunks out. With that said, a lot of logic may be directed at the World Cells' Asset Chunks, and if that is the case with your WorldGroupingListener, you should set this property to return false, because the chunks will change during an LOD transition. The actual WorldCell object instance will also change, so if you're storing these WorldCell objects in some way, you will also need to set this to false so that you can remove the old World Cells and add the new ones.

If, on the other hand, your WorldGroupingListeners only utilize data related to the World Cells (such as position, Streamable Grid Indexes, etc.), this data will not change during an LOD Transition, so you can ignore the transitions.

YieldBehaviorAfterMethodExecution (Property - Implementation Required)

This property tells the World how it should handle yielding after the World finishes executing/iterating one of the Listener's methods. Why is this important? For two reasons:

1) It's possible that one or more Listeners need to run in the same frame as the last Listener whose method was executed. In these cases, you must specify NeverYield as the ListenerYieldBehavior so that the World does not yield control to other components your game might be running (or the Unity Engine code) in the current frame.

Note that the World will still adhere to any yield statements in your Listener's methods, so you must ensure any code within different Listeners that needs to run in the same frame is configured correctly (i.e., not in between yield statements).

2) Some Listener logic may require a frame to pass before allowing the World to continue executing (including other Listeners). In order to carry out this "fermentation", you must specify AlwaysYield as the ListenerYieldBehavior.

The ListenerYieldBehavior value of YieldOrContinue can be used if your Listener is indifferent to whether the World yields control or not. Using this value, the decision is passed on to the Execution Controller being used by the World..

One final note. There is no requirement that this property uses a fixed value. You are free to change the value returned at any time, according to whatever logic you wish to use (for example, perhaps you want to return AlwaysYield after one method has run and NeverYield after a different one runs).

DetermineMethodCalls (Method - Implementation Varies)

Required when implementing the IWorldGroupingListener interface directly.

Cannot be used when deriving from WorldGroupingListener or WorldGroupingListenerNonComponent.

This method is called once for every World Grouping that uses the listener, or once each time the listener is added to a World Grouping at runtime.

It is intended to give you a chance to set the values of the CallOn... properties described below, in situations where their values can be pre-calculated.

For example, the WorldGroupingListener class implements this method to determine which methods have been overriden, and sets the properties based on that information (as such, you cannot provide an implementation for this method when deriving from WorldGroupingListener.

Because the method can be called multiple times, you should have a safeguard (a simple bool value will do) that stops your logic from running more than once.

PrecomputeUseWithLODValues (Method - Implementation Varies)

Required when implementing the IWorldGroupingListener interface directly.

Cannot be used if deriving from WorldGroupingListener.

Optional when deriving from WorldGroupingListenerNonComponent.

This method is called once for every World Grouping that uses the listener, or once each time the listener is added to a World Grouping at runtime.

It is intended to give you a chance to pre-calculate and cache any data required by the UseWithLOD method, because in many instances the values returned by this method are the same throughout the lifetime of the application.

For example, the WorldGroupingListener class implements this method to take the LOD Filter configured in the editor and turn it into actionable data that the UseWithLOD method can make use of.

If deriving from a WorldGroupingListener and if you want to use additional data in place of or in addition to the LOD Filter, you will need to override the PrecomputeUseWithLODValues_Extended method instead of this one.

If you do not need to pre-compute any data, simply implement an empty method.

PrecomputeUseWithLODValues_Extended (Method - Implementation Optional)

This method can be used when deriving from WorldGroupingListener in instances where you want to replace or add to the LOD Filter based logic used by UseWithLOD, when you are deriving, since implementing the normal PrecomputeUseWithLODValues method is not possible.

Use With LOD (Method - Implementation Varies)

Required when implementing the IWorldGroupingListener interface directly.

Optional if deriving from WorldGroupingListener or WorldGroupingListenerNonComponent.

Like the IsEnabled property, this method is queried each time one of the main On... methods would be called by the World for a batch of World Cells. The method is passed the active LOD of the batch of World Cells, and if it returns false, the current method that would be invoked is not invoked.

This is useful in situations where you only want the World Grouping Listener to operate on World cells from specific LODs, perhaps because only certain LODs contain Asset Chunks designed to work with the Listener.

Before Proceeding . . .

I should probably explain a bit to you about World and World Grouping Updates before talking about the various methods you can override . . .

The World is constantly receiving requests from external sources (usually Active Grids) to add or remove users for particular World Cells on a World Grouping and LOD. It processes these requests to determine whether new Cells need to be activated, or existing Cells need to be deactivated or transitioned from one LOD to another. When at least one Cell falls into one of these categories, a World Update is performed, which updates each World Grouping.

The deactivation and transitioning of Cells occurs top down, with the largest numbered World Grouping, then the next smallest, and so on until finally the Base World Grouping is updated.

After, the activation of new Cells occurs starting with the Base World Grouping and continuing with the next largest Grouping (2, then 3, and so on). Once a World Update starts, the Cells that will be deactivated, transitioned, and activated are locked in. Any new requests the World receives will not be processed until its next Update.

--Special Note--
All of the methods below use the special return type of IEnumerator. This return type is special as it allows the method to be iterated over, possibly over multiple frames. This allows the execution's performance impact to be spread out over multiple frames, which is awesome! There are two strategies for implementing methods with this return type:

1) Simply include yield return statements within the method's body where the returned object is either null or an instance of a YieldInstruction.

The compiler will auto generate a state machine class, which will be used to iterate the method's logic. The drawback of this technique is every time the method is called, a new instance of the auto generated class is created, resulting in garbage generation throughout the lifetime of the game.

2) Implement one of the reusable enumerator classes found within the API.

This strategy is harder to implement as you will need to implement the state machine logic yourself, as well as perform some other code related task necessary to use the reusable enumerators. It is recommended for experienced coders only!

If you elect to go with option 2, you can find detailed information on how to use the Reusable Enumerator classes in the Yield Enumerator Section within the Secondary Non Components Chapter.

Yield Enumerator Classes

Overridable Methods

There are 10 methods in total; 4 Cell Activation related methods, 4 Cell Deactivation Related methods, and 2 non Cell Related methods marking the beginning and end of each World Grouping's Update.

All methods include an immediateMode parameter which tells the method whether its execution will occur in a single frame (where yielding for a frame or fixed time is not possible). Immediate Mode is only used when the Component Manager is initialized in a non-gradual manner (via the Initialize method or enabling Initialize On Awake in its inspector), so if you don't plan to ever use non-gradual initialization, you can ignore immediateMode. When using immediate mode, the World skips over all yield statements in the methods. If this would cause issues for the correct operation of your method's logic, you should implement logic that takes into account whether immediateMode is true or false.

Each non Cell Related method will only be called once per World Grouping Update, however the Cell related methods may be called multiple times for different batches of Cells.

A batch of World Cells consist of a group of Cells from the same World, World Grouping, and LOD. Each of these methods has parameters for the batchNumber and totalBatchesToExpect (which also signifies the total number of times each method will be called during the Grouping Update [if overridden]). You can use this information to perform logic that should only be performed once before/after any/all batches have been processed by the method. For each method, a batch of cells is guaranteed to be processed by every Listener before moving on to the next batch.

The methods also include a parameter (cellsArePartOfLODTransition)that specifies whether the World Cells are part of an LOD transition, which may call for different logic to be run. You can ignore this value, however, if your IgnoreLODTransitions property is overridden to return true, as the value is guaranteed to always be false.

Each method has certain guarantees about the state of the World Cells, their Asset Chunks, and their neighboring World Cells (and their Asset Chunks), which is how you will determine which methods need to be overridden. The only guarantee across all methods is that the data that describes the World Cells will be configured correctly. We will go over those guarantees that differ in each method's sub section below (the methods are listed in the same order they would be called for each World Grouping and batch of World Cells).

Remember, if implementing the IWorldGroupingListener interface directly, all of these methods need to be overridden! If deriving from WorldGroupingListener or WorldGroupingListenerNonComponent, you only need to override the methods you wish to make usue of.

OnBeforeGroupingUpdated

This method is called just once for each Grouping during a World Update, before a Grouping has started being updated, which is to say, right before any Cells have been deactivated or transitioned. It is called for every World Grouping, even if that Grouping does not actually have Cells that need to be deactivated, transitioned, or activated.

OnBeforeAnyCellsDeactivated

Called for World Cells that are to be deactivated, before any Cells on the Grouping have been deactivated

Guarantees:

1) Each World Cell in the batch WILL have its own Asset Chunks present, and those Asset Chunks will be in an ACTIVATED state.

2) Each World Cell in the batch WILL have cell neighbors assigned, and the neighbors' Asset Chunks will be in an ACTIVATED state.

Ideal For:

1) Situations where you need to perform neighbor related Cell specific actions that are not time sensitive, since the actual deactivation of the Cells will take place some frames after this method is called.

2) Removing World Cell references or World Cell related data from an internal collection such as a Dictionary.

OnBeforeAnyCellsActivated

Called for World Cells that are to be activated, before any Cells on the Grouping have been activated.

Guarantees:

1) Each World Cell in the batch WILL NOT have its own Asset Chunks present.

2) Each World Cell in the batch WILL NOT have cell neighbors assigned.

Ideal For:

1) Situations where you need to perform non Asset Chunk and non neighbor related Cell specific actions that are not time sensitive, since the actual activation of the Cells will take place some frames after this method is called.

2) Adding World Cells to internal collections such as Dictionaries, however only use it if it doesn't matter that the neighbors and Asset Chunks for the Cell's are not assigned. Otherwise use OnAfterAllCellsActivated

OnBeforeCellsInBatchDeactivated

Called just before a batch of World Cells are deactivated.

Guarantees:

1) Each World Cell in the batch WILL have its own Asset Chunks present, and those Asset Chunks will be in an ACTIVATED state.

2) Each World Cell in the batch WILL have Cell neighbors assigned, however the state of the neighbors cannot be predicted, since this method may be called before or after other Cells that are to be deactivated have been deactivated.

Ideal For:

1) Situations where you need to perform non neighbor related cell specific actions right after a Cell is deactivated. Do note, however, that there will likely be a delay between the actual deactivation of the batch of Cell's Asset Chunks and this method being called. If you need very precise timing, you should disable Auto Deactivate Chunks When Removing Cells and create a custom Cell Visual Transition Controller where you perform the necessary logic just before you make the Cell's Asset Chunks Invisible and deactivate them.

OnAfterCellsInBatchDeactivated

Called just after a batch of World Cells have been deactivated.

Guarantees:

1) Each World Cell in the batch WILL have its own Asset Chunks present, and those Asset Chunks will be in a DEACTIVATED state.

2) Each World Cell in the batch WILL have Cell neighbors assigned, however the state of the neighbors cannot be predicted, since this method may be called before or after other Cells that are to be deactivated have been deactivated.

Ideal For:

1) Situations where you need to perform non neighbor related cell specific actions right after a Cell is deactivated. However, do note that there will likely be a small delay between this method being called and the actual deactivation of the batch of Cell's Asset Chunks. If you need very precise timing, you should disable Auto Deactivate Chunks When Removing Cells and create a custom Cell Visual Transition Controller where you perform the necessary logic just after you make the Cell's Asset Chunks Invisible and deactivate them.

OnBeforeCellsInBatchActivated

Called just before a batch of World Cells are activated.

Guarantees:

1) Each World Cell in the batch WILL have its own Asset Chunks present, and those Asset Chunks will be in a DEACTIVATED state.

2) Each World Cell in the batch WILL NOT have cell neighbors assigned.

Ideal For:

1) Situations where you need to perform non neighbor related cell specific actions right before a Cell is activated. Do note, however, that there will likely be a delay between this method being called and the actual activation of the batch of Cell's Asset Chunks. If you need very precise timing, you should disable Auto Activate Chunks When Adding Cells and create a custom Cell Visual Transition Controller where you perform the necessary logic just before you activate and make the Cell's Asset Chunks Visible.

OnAfterCellsInBatchActivated

Called just after a batch of World Cells have been activated.

Guarantees:

1) Each World Cell in the batch WILL have its own Asset Chunks present, and those Asset Chunks will be in a ACTIVATED state.

2) Each World Cell in the batch WILL have cell neighbors assigned, and any non LOD related data of the neighbors can be considered accurate. However the state of the neighbors' Asset Chunks cannot be relied upon, as even if they are assigned, they may be replaced shortly if the neighbor is undergoing and LOD transition (though do note, the neighbor is guaranteed to not be null in the future of the current World Update, since the deactivation of Cells which need to be completely removed is performed prior to this method being called for the first time).

Ideal For:

1) Situations where you need to perform non neighbor related cell specific actions right after a Cell is activated. Do note, however, that there will likely be a delay between the actual activation of the batch of Cell's Asset Chunks and this method being called. If you need very precise timing, you should disable Auto Activate Chunks When Adding Cells and create a custom Cell Visual Transition Controller where you perform the necessary logic just after you activate and make the Cell's Asset Chunks Visible.

OnAfterAllCellsDeactivated

Called for World Cells that have been deactivated, after all Cells on the Grouping have been deactivated.

Guarantees:

1) Each World Cell in the batch WILL NOT have its own Asset Chunks present.

2) Each World Cell in the batch WILL NOT have cell neighbors assigned.

Ideal For:

1) Situations where you need to perform some logic that needs to run after all World Cells have been deactivated, and does not need to use neighbors or Asset Chunks. This is also a place where you might remove the World Cells from an internal collection such as a Dictionary, in situations where you need the Cell's to stay in that collection until they are completly deactivated.

OnAfterAllCellsActivated

Called for World Cells that have been activated, after all Cells on the Grouping have been activated.

Guarantees:

1) Each World Cell in the batch WILL have its own Asset Chunks present, and those Asset Chunks will be in an ACTIVATED state.

2) Each World Cell in the batch WILL have cell neighbors set, and the Asset Chunks of the cell neighbors will be in an ACTIVATED state. Please note, there is no guarantee that a Cell will have a neighbor, as if it's on the edge of the world or next to a Disabled Streamable Grid Cell, there may be no World Cell to serve as its neighbor.

Ideal For:

1) Situations where you need to perform neighbor related logic where you want a guarantee that the cell neighbors will be present (if they exist) and accurate (all neighbors will be true neighbors, and not possibly LOD cells that are in the process of transitioning, or cells which will be deactivated; i.e., neighbors which will be nulled out or replaced in the future of this World Grouping Update).

2) Adding World Cell references or World Cell related data to an internal collection such as a Dictionary, when you only want to add those Cell's to the collection after they have been fully activated and had neighbors and Asset Chunks assigned.

OnAfterGroupingUpdated

This method is called just once for each Grouping Update, after a Grouping has finished updating, which is to say, right after all new Cells have been activated. It is called for every World Grouping, even if that Grouping does not actually have Cells that need to be deactivated, transitioned, or activated.