A Custom Addressable Chunk Streamer can be created to support the streaming in of non-prefab and non-scene addressable assets or to provide alternative implementations if you do not like the default Addressable Scene/Prefab Streamers.
You can choose to derive from the Chunk Streamer class or the AddressableBaseChunkStreamer in order to create a custom addressable chunk streamer. If you choose the first option, you should take a look at the Custom Streamers Section within this chapter.
This chapter is dedicated to the second option, which utilizes a preconfigured framework for streaming in Addressable Assets.
In order to create a custom Chunk Streamer that uses AddressableBaseChunkStreamer, create a new MonoBehaviour script that derives from the AddressableBaseChunkStreamer base abstract class. You should create this script in the same folder as the AddressableBaseChunkStreamer, which can be found in the Assets/Deep Space Labs/SAM/Scripts/AddressableDependent/RuntimeCode folder.
Placing the script in this folder will ensure that it is compiled with the AddressableBaseChunkStreamer and only when the Addressable Package is included in your project. If you place it outside of this folder and your project does not have the Addressables Package included, you will likely see an exception in the Console Log since the AddressableBaseChunkStreamer will not be compiled.
This abstract class defines several abstract methods which need to be overridden in order to implement the logic required by the Streamable Assets Manager to stream in Addressable Asset Chunks. The class definition should have already been placed inside of the DeepSpaceLabs.SAM namespace, however if it did not, please do so!
--Special Note--
An easy way to start with this class is to hover over your custom class name and choose the option Show potential fixes -> Implement abstract class. This will provide default overrides for all abstract class members.
In addition, we recommend adding the following attribute above your class, which will ensure the custom streamer is shown with other Chunk Streamers in menus:
[AddComponentMenu(GlobalValues.COMPONENT_ROOT_PATH + "Chunk Streamers/Custom Chunk Streamer Name")]
If your Custom Chunk Streamer is capable of single or double frame loading, the value returned here will determine which is used.
If you override the property to return true, the custom chunk streamer's PerformSingleFrameAttachmentPreload method will be called in one frame, and then LoadAndAttachChunksToCellsInSingleFrame will be called in the next.
If you override the property to return false, only LoadAndAttachChunksToCellsInSingleFrame will be called.
Note, this property and the methods mentioned are only used when the Component Manager's Initialize method is used. If you are strictly using the InitializeGradually method, you can leave the default implementations as is.
If your custom chunk streamer is incapable of single or double frame loading, the value returned does not matter; you should override the PerformSingleFrameAttachmentPreload and AddressableStreamerBaseUser.CheckForLoadAndAttachChunksToCellsInSingleFrameExceptions methods to throw exceptions and avoid using the Initialize method of the Component Manager (which may mean disabling the Initialize On Awake setting on the Manager).
Override this property with the file extension present in the Addresses of whatever Adressable Assets you are loading. If the addresses do not include extensions, you can return null.
The extension is only used if Append File Extension is enabled in the inspector. When this option is enabled, the file extension is appended to the end of the address of every Asset streamed in by any instances of your custom Streamer, so you should only utilize this option if all the Addresses have the file extension. If they don't, you will need to enable the Append Extra User Data option instead.
The AddressableBaseChunkStreamer class makes use of Unity's Awake method, so if you have code that you need to execute in Awake, you can override this method and put the code here. The method is automatically called by the AddressableBaseChunkStreamer after executing its own Awake code.
The AddressableBaseChunkStreamer class makes use of Unity's Start method, so if you have code that you need to execute in Start, you can override this method and put the code here. The method is automatically called by the AddressableBaseChunkStreamer after executing its own Start code.
The AddressableBaseChunkStreamer class makes use of Unity's OnDestroy method, so if you have code that you need to execute in OnDestroy, you can override this method and put the code here. The method is automatically called by the AddressableBaseChunkStreamer after executing its own OnDestroy code.
The AddressableBaseChunkStreamer provides a default implementation for this method which should be suitable in a majority of cases.
This default implementation will try to reuse asset resources when possible. If not possible, a new asset resource is loaded using your AddressableStreamerBaseUser custom class's LoadNewAssetAsyncUsingKey or LoadNewAssetAsyncUsingLocation method. The AsyncOperationHandle then has WaitForCompletion called on it to try and force the asset resource to be loaded in a single frame.
If your WaitForCompletion cannot be used or will always fail, you will either need to not use single/double frame loading or provide an alternative implementation for this method.
The method will attempt to retry failed downloads according to the Max Load Attempts setting you have configured in the inspector. After that, because the Addressable Error Repairer can only be used asynchronously, no repairs will be attempted. Instead, the method will immediately proceed with downloading fail-safe assets, according to how the Streamer is configured in the inspector.
If you override this method, you will need to create a LoadedAsyncOperationInfo object and add it to the AddressableStreamerBaseUser's internal collection using the AddressableStreamerBaseUser.AddLoadedAsyncOpInfo method. The key used to add the object to the collection should be the same key outputted via the TryGetReusableAssetLoadInfo method (if your custom streamer supports Asset Resource Reuse) or the GetNonReusableAssetKey method (if it does not). Note, the latter method requires an AsyncOperationHandle, so you should only use it after successfully loading an asset resource.
Because the unloading of assets varies considerably depending on the asset type, no default implementation of this method is provided; you must provide an override of this method that correctly unloads the Addressable Assets associated with the input World Cells.
There are a few operations that must be completed in order to ensure the internal collection of loaded async operations are updated correctly when unloading addressable assets. Please adhere to the following steps when performing an unload operation for an Asset Chunk belonging to a World Cell, which are provided for both the scenario of using Reusable Asset Resources and not using Reusable Asset Resources (since the steps vary slightly for each scenario).
--Using Reusable Asset Resources--
1) Call WorldCell.DetachChunksFromCell in order to remove the Asset Chunk reference from the World Cell while also gaining access to this reference yourself.
2) If the Asset Chunk is not null, destroy/unload it. Note, the Asset Chunk should only be null if you have destroyed it in some other place manually.
3) Retrieve the LoadedAsyncOperationInfo object via the AddressableStreamerBaseUser.TryGetAssetLoadInfo method using the key generated in step 2.
--Not Using Reusable Asset Resources--
1) Call WorldCell.DetachChunksFromCell in order to remove the Asset Chunk reference from the World Cell while also gaining access to this reference yourself.
2) Generate the key you used to store the LoadedAsyncOperationInfo object for the Asset Chunk (i.e., the key returned by the GetNonReusableAssetKey method), which in most cases should be object.GetInstanceID.
3) Retrieve the LoadedAsyncOperationInfo object via the AddressableStreamerBaseUser.TryGetAssetLoadInfo method using the key generated in step 2.
4) Unload the addressable asset by whatever means you wish
5) Remove the LoadedAsyncOperationInfo object from the AddressableStreamerBaseUser's internal collection using the AddressableStreamerBaseUser.RemoveLoadedAsyncOpInfo method. Note that this method will reset the LoadedAsyncOperationInfo object to prepare it to be reused, and as such, the handle object it holds will no longer be valid. Therefore, you must store a reference to the handle before calling this method if you need to use it after.
6) Release the AsyncOperationHandle object if whatever method you used in step 4 did not/will not release it automatically. If this is needed, ensure you store a reference to the handle before calling RemoveLoadedAsyncOpInfo in step 5!
Unlike when overriding the normal ChunkStreamer class, overriding the AddressableBaseChunkStreamer requires you to create a custom ChunkStreamerUser derived class (though note, the actual class you will derive from is AddressableStreamerBaseUser). When overridden, this method should return an instance of that custom class.
You can find more detailed information in the next sub-section (just below this one).
When deriving from the normal ChunkStreamer base abstract class, the implementation of the ChunkStreamer's LoadAndAttachChunksToCells drives the streaming of assets. When using the AddressableBaseChunkStreamer, LoadAndAttachChunksToCells is already implemented by the base class, which provides the backbone for loading Addressable Assets in a way that will work for a majority of projects..
You can modify the type of Assets that can be loaded, as well as how they are loaded, by creating a custom class that derives from AddressableBaseChunkStreamer.AddressableStreamerBaseUser. The following properties and methods are members that can/must be implemented on this custom class (remember, you should override CreateAddressableLoaderUser to return an instance of this custom class).
Override to define whether the addressable asset resources (associated with the load operation) loaded by your streamer implementation can be reused between World Cells using the same Streamable Grid Cell. For example, the default Addressable Scene Loader returns false for this, because scenes do not use an underlying asset resource that can be reused. The Addressable Prefab Streamer, on the other hand, returns true, because there is an underlying prefab asset resource that is loaded, which can be instantiated as many times as needed in order to be shared among different World Cells with the same Streamable Grid Cell index.
Reusing the underlying addressable asset is beneficial because it cuts down on the number of Async load operations needed. When this returns true, the key used to store each LoadedAsyncOperationInfo object is a combination of the Streamable Grid Cell used by the World Cell that triggered the load, and the chunk index of whatever chunk is needed by the Cell.
When other World Cells using the same Streamable Grid Cell need to have chunks loaded for them, they are able to use the Streamable Grid Cell (plus chunk number of the asset they need) to identify the reusable resource that has already been loaded, and use it to make a copy that can be used for that World Cell.
When this returns false, each World Cell will trigger an Async Load Operation, even if a World Cell using the same Streamable Grid Cell has already been loaded. The key used to store the LoadedAsyncOperationInfo should be generated using the Asset Chunk, so that if presented with the Asset Chunk in the future, you can regenerate the same key. This requirement is necessary because the Asset Chunks may be transferred to other World Cells (if Chunk Reuse is enabled via your Chunk Manager), and as such, you must be able to generate the key using the Asset Chunk itself rather than relying on information stored in the World Cell. A very common and useful method for generating the key is UnityEngine.Object.GetInstanceID, however you have full control over what key is generated via the overridable GetNonReusableAssetKey method.
Override to return the File Type recognized by the Addressable System that is used to load your Asset Chunks. You can identify the File Type via whatever method you are using to load the Addressable Assets, as the return type for that load method should be an AsyncOperationHandle
Determines if the Chunk Streamer used by the lowest quality LOD for the Streamable Grid associated with this user is compatible with the type of Addressable Chunk Streamer that created this user object. Typically it should be of the same type. This is only called when UseAltLODFailSafe would otherwise be true. If you return false from this method, UseAltLODFailSafe will be made false as well.
If the streamer is compatible, you can and should store it (casted to the derived type of streamer you are using) along with the chunkStreamerUserID to avoid having to look up the streamer and ID each time they are needed.
This method only needs to be overridden when the CanReuseAddressables property is overridden to return false. It is called for each asset loaded and should return a unique key that can be used to store the passed in load handle for the asset, plus some additional information about the load operation used to load the addressable asset.
When chunks associated with a World Cell need to be unloaded, you must be able to produce or access the correct key for the chunk stored on the World Cell. Note, however, that if your Chunk Manager uses pooling, or has chunk reuse enabled, the chunk stored in the World Cell may have been originally loaded for a different World Cell. Therefore, it is imperative to use a key that is associated with the chunks themselves rather than World Cell. If using a chunk that derives from UnityEngine.Object, you can use the GetInstanceID to get a unique int that can be used as the key.
This method is called at the beginning of the AddressableBaseChunkStreamer's LoadAndAttachChunksToCellsInSingleFrame method and should throw an exception if that method cannot be used by the custom AddressableBaseChunkStreamer.
For instance, the AddressableSceneChunkStreamer always throws an exception with this method, since addressable scene streaming is not compatible with single/double frame loading.
The AddressablePrefabChunkStreamer, on the other hand, only throws an exception if the Platform is WebGL.
Override to provide a way to load an Addressable Asset using an IResourceLocation. You can throw an exception if you know you will never need to load the Assets using their IResourceLocations.
The returned handle is the non-generic AsyncOperationHandle, however when loading the asset, your loading method will usually return a more specific generic type, such as AsyncOperationHandle
Override to provide a way to load an Addressable Asset using a string key. You can throw an exception if you know you will never need to load the Assets using a string key.
The returned handle is the non-generic AsyncOperationHandle, however when loading the asset, your loading method will usually return a more specific generic type, such as AsyncOperationHandle
This method should retrieve (from the AsyncOperationHandle) and setup the Asset Chunk that will be attached to the World Cell. Once the Asset Chunk is returned via this method it is simply attached to the World Cell; no other processing is done with it. As such, you must ensure the asset is configured exactly as you want. For instance, for Game Objects you should set the name of the object to readyOp.loadedAsyncOpInfo.chunkName, otherwise the default name will have Instance or something similar in it.
The AsyncOperationHandle passed to the method will be the same handle your returned via LoadNewAssetAsyncUsingLocation or LoadNewAssetAsyncUsingKey. You will likely need to convert this handle to the AsyncOperationHandle
The readyOp argument contains useful information about the Asset Chunk such as its correct name and chunk index, the World Cell which it will be attached to, as well as whether the asset is the correct asset for the given LOD (which will only not be the case if the correct asset fails to load and a lower quality LOD asset or fail-safe asset is loaded as a backup).
Asset Resource Reuse allows you to use the same underlying Addressable Asset Resource that has already been loaded in order to load many instances of the asset for use with multiple World Cell's. Only World Cell's using the same underlying Streamable Grid Cell can share asset resources, so asset resource reuse only comes into play in the following situations:
1) When using an Endless/Repeating World, in which case there may be multiple World Cells loaded with different Endless Grid Indexes but the same Streamable Grid Index.
2) When using a Streamable Grid and LOD Group that has Use Single Chunk Set For All Cells enabled, as the Asset Chunk Set for a single Streamable Grid Cell will be used with all World Cell's on the Grid.
Each reusable asset resource has a LoadedAsyncOperationInfo object associated with it, which can be accessed using the AddressableStreamerBaseUser.TryGetReusableAssetLoadInfo method. The LoadedAsyncOperationInfo object contains the AsyncOperationHandle used to load the asset, a bool indicating whether there are World Cell's currently using the asset, the Chunk Name of the asset, as well as a bool indicating whether the asset is the correct/original asset for said chunk (if false, it indicates that the asset is a lower quality LOD or fail-safe asset).
Note that when a lower quality LOD or fail-safe asset resource is loaded for a Streamable Grid Cell (SGC) on a Streamer using reusable asset resources, that asset resource will continue to be used by new World Cells using the same SGC until all World Cell's using the SGC are unloaded. It is only after the last World Cell using the SGC is unloaded that the Streamer will make another attempt to load the correct asset for the SGC. This is the most obvious drawback to using reusable asset resources.
Also note that Addressable Asset Reuse is different than Chunk Reuse. The former refers to reusing the asset resource via the Addressable System while the latter refers to reusing the Asset Chunk that is attached to a World Cell. Chunk Reuse is handled automatically by the World and Chunk Manager (if enabled).
It is very common for your AddressableStreamerBaseUser objects to need to access methods, properties, or fields contained in your custom AddressableBaseChunkStreamer implementation. As such, you should utilize a parameter of the same type as your custom AddressableStreamerBaseUser in your custom AddressableStreamerBaseUser class's constructor, and store that streamer object so that it can be accessed.
Note, because your custom AddressableStreamerBaseUser class is nested inside of your custom AddressableBaseChunkStreamer class, you can access its private members!
The registration of new LOD Group users is handled automatically by the base Chunk Streamer class. You can access the ChunkStreamerUser object of any registered LOD Group via the RegisteredUsers property, using a simple Indexer, where the index is the userID passed into each of the loading/unloading methods.
--Code Example--
ChunkStreamerUser user = RegisteredUsers[userID];
Do note that when using this Indexer, the returned object is always of type ChunkStreamerUser, so you will need to cast it to the custom AddressableStreamerBaseUser type you are using in order to access any custom data stored on the custom user object.
You can also iterate through all registered LOD Group users in a garbage free way using the RegistrantEntries method, which returns an enumerable struct object (we recommend using a simple foreach statement to perform the iteration).
While these should be the only members of the RegistrationHandler class you need to utilize, you are free to take a look at this class in more detail by viewing the Registration Handler Section within the Secondary Non Components Chapter.
Learn More About The Registration Handler
Load Progress is tracked automatically by the AddressableBaseChunkStreamer class, so you do not need to track it yourself. How wonderful!
When creating custom Addressable Chunk Streamers, you will need to create a custom Editor class so that the Global Chunk Streamer settings, as well as the Base Chunk Streamer settings, can be drawn/utilized properly. To do so:
1) Create a new script file with whatever name you wish (though we recommend CustomStreamerClassNameEditor.cs).
2) Place all the rest of this code inside of the DeepSpaceLabs.EditorSAM namespace.
3) Add using UnityEditor and using DeepSpaceLabs.SAM statements above your class definition.
4) Right above your class definition, add the following line:
[CustomEditor(typeof(CustomStreamerClass))]
5) Add a field of type AddressableBaseChunkStreamerEditor, like so:
AddressableBaseChunkStreamerEditor baseEditor;
6) Add a public override method called OnInspectorGUI:
public override void OnInspectorGUI(){}
7) Within the OnInspectorGUI method, check if baseEditor is null, and if it is, create a new instance of it, passing in serializedObject:
if (baseEditor == null) baseEditor = new AddressableBaseChunkStreamerEditor(serializedObject)
8) Finally, call baseEditor.OnInspectorGUI:
baseEditor.OnInspectorGUI();
This will make the Global Chunk Streamer fields and Base Addressable Chunk Streamer fields show, however if you need to add additional drawing or perform other stuff, feel free to add to the class's OnInspectorGUI method by drawing additional properties before and/or after the call to baseEditor.OnInspectorGUI().
You can take a look at the AddressablePrefabChunkStreamerEditor.cs or AddressableSceneChunkStreamerEditor.cs files if you are having trouble with the file. These can be found in the Assets/Deep Space Labs/SAM/Scripts/AddressableDependent folder.