#if UNITY_EDITOR ////////////////////////////////////////////////////////////////////// // // Copyright (c) 2020 Audiokinetic Inc. / All Rights Reserved // ////////////////////////////////////////////////////////////////////// using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Threading.Tasks; /// /// This class wraps the client that communicates with the Wwise Authoring application via WAAPI. /// Given that only one request can be pending on the websocket, a queue is used to consume all calls sequentially. /// Messages sent to WAAPI use the JSON format and are serialized by Unity Json serialization. /// Helper classes (\ref WaapiHelper) for serialization, keywords for WAAPI commands (\ref WaapiKeywords), and classes for serializing message arguments and deserializing responses are found in AkWaapiHelper.cs. /// Uri.cs contains classes with fields containing URI strings for WAAPI calls and error messages. /// [UnityEditor.InitializeOnLoad] public partial class AkWaapiUtilities { static private AkWaapiClient m_WaapiClient; static bool isDisconnecting; static Dictionary m_ItemTransports; public static string ErrorMessage; /// /// Fired when the connection is closing or closed, the bool parameter represents whether the socket connection is still open (for cleaning up subscriptions). /// public static System.Action Disconnecting; /// /// Fired when the connection is established, should be used by external classes to subscribe to topics they are interested in. /// public static System.Action Connected; /// /// Fired when all commands in the queue have been executed. /// public static System.Action QueueConsumed; private static ConcurrentQueue waapiCommandQueue = new ConcurrentQueue(); /// /// Generic delegate function for callbacks that expect to receive a list of objects in response to a WAAPI request. /// /// public delegate void GetResultListDelegate(List result); /// /// Generic delegate function for callbacks that expect to receive a single object in response to a WAAPI request. /// /// public delegate void GetResultDelegate(T result); /// /// Used to store store UnityEngine.Application.dataPath because we can't access it outside of the main loop /// private static string dataPath; /// /// Bind disconnection method to compilation started delegate and start the async Waapi loop. /// static AkWaapiUtilities() { #if UNITY_2019_1_OR_NEWER UnityEditor.Compilation.CompilationPipeline.compilationStarted += (object context) => FireDisconnect(true); #else UnityEditor.Compilation.CompilationPipeline.assemblyCompilationStarted += (string assemblyPath) => { if (assemblyPath == "Library/ScriptAssemblies/AK.Wwise.Unity.API.dll") FireDisconnect(true); }; #endif isDisconnecting = false; dataPath = UnityEngine.Application.dataPath; Loop(); } /// /// A simple structure containing an async payload function that will be executed when it is consumed by the command queue. /// public struct WaapiCommand { System.Func payload; public WaapiCommand(System.Func payload) { this.payload = payload; } public async Task Execute() { await payload.Invoke(); } } /// /// Class used to store information about a specific subscription. /// public class SubscriptionInfo { public string Uri; public Wamp.PublishHandler Callback; public uint SubscriptionId; public SubscriptionInfo(string uri, Wamp.PublishHandler cb) { Uri = uri; Callback = cb; SubscriptionId = 0; } }; /// /// Holds information about a playing transport. /// struct TransportInfo { public int TransportID; public uint SubscriptionID; public TransportInfo(int transID, uint subsID) { TransportID = transID; SubscriptionID = subsID; } }; /// /// Stores TransportInfo of playing Events. /// private static Dictionary ItemTransports { get { if (m_ItemTransports == null) m_ItemTransports = new Dictionary(); return m_ItemTransports; } } /// /// WAAPI client wrapping WAMP calls. Lazy instantiated. /// private static AkWaapiClient WaapiClient { get { if (m_WaapiClient == null) { m_WaapiClient = new AkWaapiClient(); m_WaapiClient.Disconnected += Disconnected; } return m_WaapiClient; } } /// /// Check whether the client is currently connected. /// /// public static bool IsConnected() { if (m_WaapiClient == null) return false; return WaapiClient.IsConnected(); } private static bool kill; private static int loopSleep = 0; private static bool projectConnected = false; /// /// Main loop for the WAAPI API. Checks if the client is connected and consumes all commands. /// private static async void Loop() { try { ErrorMessage = ""; if (await CheckConnection()) { await ConsumeCommandQueue(); } if (!kill) { if (loopSleep > 0) { await Task.Delay(loopSleep * 1000); } } } //Handle socket issues caused by closing Wwise Authoring. catch (System.Net.WebSockets.WebSocketException) { UnityEngine.Debug.Log("Wwise Unity : WAAPI disconnected because Wwise Authoring was closed"); Disconnecting?.Invoke(false); waapiCommandQueue = new ConcurrentQueue(); projectConnected = false; try { await m_WaapiClient.Close(); } //Closing the client will throw other exceptions because it tries to send messages to a closed socket. catch (System.Net.Sockets.SocketException) { } } catch (Wamp.WampNotConnectedException e) { ErrorMessage = e.Message; } finally { UnityEditor.EditorApplication.delayCall += () => Loop(); } } /// /// Consumes all WAAPICommands in the queue and then fires QueueConsumed. /// /// Awaitable Task private static async Task ConsumeCommandQueue() { bool shouldUpdate = false; while (waapiCommandQueue.Count > 0) { if (waapiCommandQueue.TryDequeue(out WaapiCommand cmd)) { try { await cmd.Execute(); shouldUpdate = true; ErrorMessage = ""; } catch (Wamp.ErrorException e) { ErrorMessage msg = UnityEngine.JsonUtility.FromJson(e.Json); if (msg != null) { if (msg.message != null) ErrorMessage = msg.message; } switch (e.Uri) { case ak.wwise.error.unavailable: case ak.wwise.error.unexpected_error: case ak.wwise.error.wwise_console: case ak.wwise.error.locked: case ak.wwise.error.file_error: waapiCommandQueue.Enqueue(cmd); break; case ak.wwise.error.invalid_object: case ak.wwise.error.invalid_property: case ak.wwise.error.invalid_query: case ak.wwise.error.invalid_reference: case ak.wwise.error.invalid_options: case ak.wwise.error.invalid_json: case ak.wwise.error.invalid_arguments: default: UnityEngine.Debug.Log(ErrorMessage); break; } break; } catch (Wamp.WampNotConnectedException e) { waapiCommandQueue.Enqueue(cmd); throw (e); } } } if (shouldUpdate) { QueueConsumed?.Invoke(); } } /// /// Checks the global WAAPI settings and disconnects if WAAPI is disabled or connection settings have changed. /// If disconnected, try to connect with current settings. /// /// True if the client is connected private static async Task CheckConnection() { if (AkWwiseEditorSettings.Instance.UseWaapi) { // If WAAPI connection settings have changed, unsubcribe and close the connection. if (ConnectionSettingsChanged() && WaapiClient.IsConnected()) { FireDisconnect(false); return true; } if (!WaapiClient.IsConnected()) { try { await m_WaapiClient.Connect(GetUri()); } catch (System.Exception) { ConnectionFailed("Connection refused"); } } if (WaapiClient.IsConnected()) { var projectOpen = await CheckProjectLoaded(); if (!projectConnected && projectOpen) { projectConnected = true; loopSleep = 0; Connected?.Invoke(); } else if (projectConnected && !projectOpen) { FireDisconnect(false); return true; } } } else { if (WaapiClient.IsConnected() && !isDisconnecting) { FireDisconnect(false); return true; } } return WaapiClient.IsConnected() && projectConnected; } /// /// Tries to communicate with Wwise and compares the current open project with the project path specified in the Unity Wwise Editor settings. /// /// True if the correct wwise project is open in Wwise. private async static Task CheckProjectLoaded() { try { var result = await GetProjectInfo(); if (result.Count == 0) { throw new Wamp.ErrorException("Did not get a response from Wwise project"); } var projectInfo = result[0]; #if UNITY_EDITOR_OSX var d1 = AkUtilities.ParseOsxPathFromWinePath(projectInfo.filePath); #else var d1 = projectInfo.filePath; #endif var d2 = AkUtilities.GetFullPath(dataPath, AkWwiseEditorSettings.Instance.WwiseProjectPath); d1 = d1.Replace("/", "\\"); d2 = d2.Replace("/", "\\"); if (d1 != d2) { ConnectionFailed($"The wrong project({projectInfo.name}) is open in Wwise"); return false; } } catch (Wamp.ErrorException e) { if (e.Json != null) { ErrorMessage msg = UnityEngine.JsonUtility.FromJson(e.Json); if (msg != null) { if (msg.message != null) ErrorMessage = msg.message; } } if (e.Uri == "ak.wwise.locked") { return true; } ConnectionFailed($"No project is open in Wwise yet"); return false; } return true; } private static void ConnectionFailed(string message) { loopSleep = Math.Min(Math.Max(loopSleep * 2, 1), 32); ErrorMessage = $"{message} - Retrying in {loopSleep}s"; } /// /// Starts the diconnection process. /// Invokes Disconnecting() so that other classes using WAAPI can clean up and add commands to unsubscribe from topics. /// Consumes the last batch of commands in the command queue then closes the client. /// private static void FireDisconnect(bool killLoop) { projectConnected = false; isDisconnecting = true; Disconnecting?.Invoke(true); waapiCommandQueue.Enqueue(new WaapiCommand( async () => await CloseClient(killLoop))); } private static async Task CloseClient(bool killLoop) { await WaapiClient.Close(); isDisconnecting = false; if (killLoop) { kill = true; } } /// /// Invoked after the client has disconnected from Wwise authoring. /// public static void Disconnected() { Disconnecting?.Invoke(false); } private static string GetUri() { return $"ws://{AkWwiseEditorSettings.Instance.WaapiIP}:{AkWwiseEditorSettings.Instance.WaapiPort}/waapi"; } static string ip; static string port; static string projectPath; private static bool ConnectionSettingsChanged() { bool changed = false; if (ip != AkWwiseEditorSettings.Instance.WaapiIP) { ip = AkWwiseEditorSettings.Instance.WaapiIP; changed = true; } if (port != AkWwiseEditorSettings.Instance.WaapiPort) { port = AkWwiseEditorSettings.Instance.WaapiPort; changed = true; } if (projectPath != AkWwiseEditorSettings.Instance.WwiseProjectPath) { projectPath = AkWwiseEditorSettings.Instance.WwiseProjectPath; changed = true; } return changed; } /// /// Returns a rich text string representing the current WAAPI connection status. /// /// public static string GetStatusString() { var returnString = ""; if (!AkWwiseEditorSettings.Instance.UseWaapi) { returnString += " Waapi disabled in project settings "; } else if (WaapiClient.wamp != null) { var state = WaapiClient.wamp.SocketState(); switch (state) { case System.Net.WebSockets.WebSocketState.Open: returnString += " Connected"; break; case System.Net.WebSockets.WebSocketState.Closed: returnString += " Disconnected "; break; case System.Net.WebSockets.WebSocketState.Connecting: returnString += $" Connecting to { GetUri()}"; break; default: returnString += $" Connecting to { GetUri()}"; break; } } else { returnString += " Disconnected "; } if (ErrorMessage != string.Empty) returnString += $" {ErrorMessage}"; return returnString; } private static async Task> GetProjectInfo() { var args = new WaqlArgs($"from type {WaapiKeywords.PROJECT}"); var options = new ReturnOptions(new string[] { "filePath" }); var result = await WaapiClient.Call(ak.wwise.core.@object.get, args, options); var ret = UnityEngine.JsonUtility.FromJson(result).@return; return ParseObjectInfo(ret); } /// /// Use this function to enqueue a command with no expected return object. /// /// The URI of the waapi command /// The command-specific arguments /// The command-specific options public static void QueueCommand(string uri, string args, string options) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await WaapiClient.Call(uri, args, options))); } /// /// Use this function to enqueue a command with an expected return object of type T. /// The command will deserialize the respone as type T and pass it to the callback. /// /// /// Type of the expected return object /// The URI of the waapi command /// The command-specific arguments /// The command-specific options /// Function accepting an argument of type T public static void QueueCommandWithReturnType(string uri, GetResultDelegate callback, string args = null, string options = null) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => { var result = await WaapiClient.Call(uri, args, options); callback(UnityEngine.JsonUtility.FromJson(result)); })); } /// /// Enqueues a command with a payload that desirializes the list of wwise objects from the response. /// /// /// /// public static void QueueCommandWithReturnWwiseObjects(WaqlArgs args, ReturnOptions options, GetResultListDelegate callback) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => { var result = await WaapiClient.Call(ak.wwise.core.@object.get, args, options); var ret = UnityEngine.JsonUtility.FromJson>(result); callback.Invoke(ret.@return); })); } /// /// Generic function for fetching a Wwise object with custom return options. /// /// Type of the object to be deserialized from the response. /// GUID of the target object. /// Specifies which object properties to include in the response /// Function accteping a list of T objects. public static void GetWwiseObject(System.Guid guid, ReturnOptions options, GetResultListDelegate callback) { GetWwiseObjects(new List() { guid }, options, callback); } /// /// Generic function for fetching a list of Wwise objects with custom return options. /// /// Type of the object to be deserialized from the response. /// GUIDs of the target objects. /// Specifies which object properties to include in the response /// Function accteping a list of T objects. public static void GetWwiseObjects(List guids, ReturnOptions options, GetResultListDelegate callback) { string guidString = ""; foreach (var guid in guids) { guidString += $"{guid:B} ,"; } var args = new WaqlArgs($"from object \"{guidString}\" "); QueueCommandWithReturnWwiseObjects(args, options, callback); } /// /// Enqueues a waapi command to fetch the specified object and all of its ancestors in the hierarchy. /// Passes the list of WwiseObjectInfo containing the specified object and ancestors to the callback. /// /// GUID of the target object. /// Specifies which object properties to include in the response /// Function accepting a list of WwiseObjectInfo. public static void GetWwiseObjectAndAncestors(System.Guid guid, ReturnOptions options, GetResultListDelegate callback) { var args = new WaqlArgs($"from object \"{guid:B}\" select this, ancestors orderby path"); QueueCommandWithReturnWwiseObjects(args, options, callback); } /// /// Enqueues a waapi comand to fetch the specified object and all of its descendants in the hierarchy to a specified depth. /// Passes the list of WwiseObjectInfo containing the specified object and descendants to the callback. /// /// GUID of the target object. /// Specifies which object properties to include in the response /// Depth of descendants to fetch. If -1, fetches all descendants. /// Function accepting a list of WwiseObjectInfo. public static void GetWwiseObjectAndDescendants(System.Guid guid, ReturnOptions options, int depth, GetResultListDelegate callback) { GetWwiseObjectAndDescendants(guid.ToString("B"), options, depth, callback); } /// /// Composes a WAQL "from object" request based on the parameters and enqueues a WAAPI command. /// Passes the list of WwiseObjectInfo containing the results to the callback /// /// Can bethe target object GUID or path within the hierarchy. /// Specifies which object properties to include in the response /// Depth of descendants to fetch. If -1, fetches all descendants. /// Function accepting a list of WwiseObjectInfo. public static void GetWwiseObjectAndDescendants(string identifier, ReturnOptions options, int depth, GetResultListDelegate callback) { WaqlArgs args; if (depth > 0) { string selectString = System.String.Join(" ", ArrayList.Repeat(" select this, children", depth).ToArray()); args = new WaqlArgs($"from object \"{identifier}\" {selectString} orderby path"); } else { args = new WaqlArgs($"from object \"{identifier}\" select descendants orderby path"); } QueueCommandWithReturnWwiseObjects(args, options, callback); } /// /// Composes a WAQL "search" request based on the parameters and enqueues a WAAPI command. /// Passes the list of WwiseObjectInfo containing the search results to the callback /// /// Characters to search for. /// Specifies which object properties to include in the response /// An optional object type used to filter search results. /// Function accepting a list of WwiseObjectInfo. public static void Search(string searchString, WwiseObjectType objectType, ReturnOptions options, GetResultListDelegate callback) { WaqlArgs args; if (objectType == WwiseObjectType.None) { args = new WaqlArgs($"from search \"{searchString}\" orderby path"); } else { args = new WaqlArgs($"from search \"{searchString}\" where type=\"{WaapiKeywords.WwiseObjectTypeStrings[objectType]}\" orderby path"); } QueueCommandWithReturnWwiseObjects(args, options, callback); } /// /// Get the children of a given object. /// /// GUID of the target object. /// Specifies which object properties to include in the response /// Function accepting a list of WwiseObjectInfo. public static void GetChildren(System.Guid guid, ReturnOptions options, GetResultListDelegate callback) { if (guid == System.Guid.Empty) return; var args = new WaqlArgs($"from object \"{guid:B}\" select children orderby path"); QueueCommandWithReturnWwiseObjects(args, options, callback); } /// /// Get the WwiseObjectInfo for the project. /// /// Function accepting a list of WwiseObjectInfo. The first element of the list will be the project info. /// Specifies which object properties to include in the response public static void GetProject(GetResultListDelegate callback, ReturnOptions options) { var args = new WaqlArgs($"from type {WaapiKeywords.PROJECT}"); QueueCommandWithReturnWwiseObjects(args, options, callback); } /// /// Parse the response WwiseObjectInfoJsonObject of a "from object" request and implicit cast the objects to WwiseObjectInfo. /// /// /// public static List ParseObjectInfo(List returnObjects) { var returnInfo = new List(returnObjects.Count); foreach (var info in returnObjects) { returnInfo.Add(info); } return returnInfo; } /// /// Select the object in Wwise Authoring. /// Creates a WaapiCommand object containing a lambda call to SelectObjectInAuthoringAsync and adds it to the waapiCommandQueue. /// /// GUID of the object to be selected. public static void SelectObjectInAuthoring(System.Guid guid) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await SelectObjectInAuthoringAsync(guid))); } /// /// Creates and sends a WAAPI command to select a Wwise object. /// /// GUID of the object to be selected. /// static private async Task SelectObjectInAuthoringAsync(System.Guid guid) { if (guid == System.Guid.Empty) return; var args = new ArgsCommand(WaapiKeywords.FIND_IN_PROJECT_EXPLORER, new string[] { guid.ToString("B") }); await WaapiClient.Call(ak.wwise.ui.commands.execute, args, null); } /// /// Open the OS file browser to the folder containing this object's Work Unit. /// Creates a WaapiCommand object containing a lambda call to OpenWorkUnitInExplorerAsync and adds it to the waapiCommandQueue. /// /// GUID of the object to be found. public static void OpenWorkUnitInExplorer(System.Guid guid) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await OpenWorkUnitInExplorerAsync(guid))); } /// /// Open the OS file browser to the folder containing the generated SoundBank. /// Creates a WaapiCommand object containing a lambda call to OpenSoundBankInExplorer and adds it to the waapiCommandQueue. /// /// GUID of the SoundBank to be found. public static void OpenSoundBankInExplorer(System.Guid guid) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await OpenSoundBankInExplorerAsync(guid))); } /// /// Uses a waapi call to get the object's file path, then opens the containing folder in the system's file browser. /// /// GUID of the object to be found. /// Awaitable Task. private static async Task OpenWorkUnitInExplorerAsync(System.Guid guid) { var args = new WaqlArgs($"from object \"{guid:B}\""); var options = new ReturnOptions(new string[] { "filePath" }); var result = await WaapiClient.Call(ak.wwise.core.@object.get, args, options); var ret = UnityEngine.JsonUtility.FromJson(result); var filePath = ret.@return[0].filePath; filePath = filePath.Replace("\\", "/"); #if UNITY_EDITOR_OSX filePath = AkUtilities.ParseOsxPathFromWinePath(filePath); #endif UnityEditor.EditorUtility.RevealInFinder(filePath); } /// /// Uses a waapi call to get the SoundBank's generated bank path, then opens the containing folder in the system's file browser. /// /// GUID of the object to be found. /// Awaitable Task. private static async Task OpenSoundBankInExplorerAsync(System.Guid guid) { var args = new WaqlArgs($"from object \"{guid:B}\""); var options = new ReturnOptions(new string[] { "soundbankBnkFilePath" }); var result = await WaapiClient.Call(ak.wwise.core.@object.get, args, options); var ret = UnityEngine.JsonUtility.FromJson(result); var filePath = ret.@return[0].soundbankBnkFilePath; #if UNITY_EDITOR_OSX filePath = AkUtilities.ParseOsxPathFromWinePath(filePath); #endif UnityEditor.EditorUtility.RevealInFinder(filePath); } /// /// Rename an object in Wwise authoring. /// Creates a WaapiCommand object containing a lambda call to RenameAsync and adds it to the waapiCommandQueue. /// /// GUID of the object to be renamed. /// New name for the wwise object. public static void Rename(System.Guid guid, string newName) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await RenameAsync(guid, newName) )); } /// /// Sends a WAAPI command to rename a Wwise object. /// /// GUID of the object to be renamed. /// New name for the wwise object. /// Awaitable Task. private static async Task RenameAsync(System.Guid guid, string newName) { var args = new ArgsRename(guid.ToString("B"), newName); await WaapiClient.Call(ak.wwise.core.@object.setName, args, null); } /// /// Delete an object in wwise authoring. Work Units cannot be deleted in this manner. /// Creates a WaapiCommand object containing a lambda call to DeleteAsync and adds it to the waapiCommandQueue. /// /// GUID of the object to be deleted. public static void Delete(System.Guid guid) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await DeleteAsync(guid) )); } /// /// Sends three WAAPI commands: /// 1. Begin an undo group. /// 2. Delete the specified object. /// 3. Close the undo group. /// /// GUID of the object to be deleted. /// Awaitable Task. private static async Task DeleteAsync(System.Guid guid) { await WaapiClient.Call(ak.wwise.core.undo.beginGroup); await WaapiClient.Call(ak.wwise.core.@object.delete, new ArgsObject(guid.ToString("b"))); await WaapiClient.Call(ak.wwise.core.undo.endGroup, new ArgsDisplayName(WaapiKeywords.DELETE_ITEMS)); } /// /// Checks if Wwise object is playable. /// /// WwiseObjectType of object to check. /// True if playable public static bool IsPlayable(WwiseObjectType type) { return (type == WwiseObjectType.Event); } /// /// Play or pause an object in Wwise authoring. /// Creates a WaapiCommand object containing a lambda call to TogglePlayEventAsync and adds it to the waapiCommandQueue. /// /// Used to check whether the object is playable. /// GUID of the object to be played. static public void TogglePlayEvent(WwiseObjectType objectType, System.Guid guid) { if (IsPlayable(objectType)) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await TogglePlayEventAsync(guid))); } } /// /// Play or pause an object in Wwise authoring. Opens a new transport in wwise to play the sound if it does not exist yet. /// /// GUID of the object to be played. /// static async private Task TogglePlayEventAsync(System.Guid guid) { var transportID = await GetTransport(guid); var args = new ArgsPlay(WaapiKeywords.PLAYSTOP, transportID); var result = await WaapiClient.Call(ak.wwise.core.transport.executeAction, args, null); } /// /// Find the open transport in ItemTransports or create a new one. /// /// GUID of the object. /// static async private Task GetTransport(System.Guid guid) { TransportInfo transportInfo; if (!ItemTransports.TryGetValue(guid, out transportInfo)) { transportInfo = await CreateTransport(guid); } return transportInfo.TransportID; } /// /// Send a WAAPI call to create a transport in Wwise. /// Subscribe to the ak.wwise.core.transport.stateChanged topic of the new transport. /// Add the transport info to ItemTransports. /// /// GUID of the Event /// static async private Task CreateTransport(System.Guid guid) { var args = new ArgsObject(guid.ToString("B")); var result = await WaapiClient.Call(ak.wwise.core.transport.create, args, null, timeout: 1000); int transportID = UnityEngine.JsonUtility.FromJson(result).transport; var options = new TransportOptions(transportID); uint subscriptionID = await WaapiClient.Subscribe(ak.wwise.core.transport.stateChanged, options, HandleTransportStateChanged); var transport = new TransportInfo(transportID, subscriptionID); ItemTransports.Add(guid, transport); return transport; } /// /// Handle the messages published by a transport when its state is changed. /// If stopped, enqueue a command with DestroyTransport as its payload. /// /// static private void HandleTransportStateChanged(string message) { TransportState transport = UnityEngine.JsonUtility.FromJson(message); System.Guid itemID = new System.Guid(transport.@object); int transportID = transport.transport; if (transport.state == WaapiKeywords.STOPPED) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await DestroyTransport(itemID))); } else if (transport.state == WaapiKeywords.PLAYING && !ItemTransports.ContainsKey(itemID)) { ItemTransports.Add(itemID, new TransportInfo(transportID, 0)); } } /// /// Send a WAAPI command to stop the specific transport. /// /// ID of the transport. /// static private async Task StopTransport(int in_transportID) { var args = new ArgsPlay(WaapiKeywords.STOP, in_transportID); var result = await WaapiClient.Call(ak.wwise.core.transport.executeAction, args, null); } /// /// Unsubscribe from the transport topic and send a WAAPI command to destroy the transport in Wwise. /// /// GUID of the Event. /// static async Task DestroyTransport(System.Guid in_itemID) { if (!ItemTransports.ContainsKey(in_itemID)) return null; if (ItemTransports[in_itemID].SubscriptionID != 0) await WaapiClient.Unsubscribe(ItemTransports[in_itemID].SubscriptionID); var args = new ArgsTransport(ItemTransports[in_itemID].TransportID); var result = await WaapiClient.Call(ak.wwise.core.transport.destroy, args, null); ItemTransports.Remove(in_itemID); return result; } /// /// Stops all playing transports. /// Creates a WaapiCommand object containing a lambda call to StopAllTransportsAsync and adds it to the waapiCommandQueue. /// static public void StopAllTransports() { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await StopAllTransportsAsync())); } /// /// Stops all playing transports. /// /// Awaitable task. static private async Task StopAllTransportsAsync() { foreach (var item in ItemTransports) { await StopTransport(item.Value.TransportID); } } /// /// Subscribe to WAAPI topic. Refer to WAAPI reference documentation for a list of topics and their options. /// Creates a WaapiCommand object containing a lambda call to SubscribeAsync and adds it to the waapiCommandQueue. /// /// The topic URI to subscribe to. /// Delegate function to call when the topic is published. /// Action to be executed once the subscription has been made. /// This should store the subscription ID so that the subscription can be cleaned up when it is no longer needed. public static void Subscribe(string topic, Wamp.PublishHandler subscriptionCallback, System.Action handshakeCallback) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => handshakeCallback(await SubscribeAsync(new SubscriptionInfo(topic, subscriptionCallback))))); } /// /// Subscribe to WAAPI topic. Refer to WAAPI reference documentation for a list of topics and their options. /// Creates and sends a WAAPI command to subscribe to the topic. /// /// SubscriptionInfo object containing the topic URI and the message handling callback. /// Updated SubscriptionInfo object containing the subscription ID (uint). private static async Task SubscribeAsync(SubscriptionInfo subscription) { var options = new ReturnOptions(new string[] { "id", "parent", "name", "type", "childrenCount", "path", "workunitType" }); uint id = await WaapiClient.Subscribe(subscription.Uri, options, subscription.Callback); subscription.SubscriptionId = id; return subscription; } /// /// Unsubscribe from an existing subscription. /// Creates a WaapiCommand object containing a lambda call to UnsubscribeAsync and adds it to the waapiCommandQueue. /// /// The subscription ID received from the initial subscription. public static void Unsubscribe(uint id) { waapiCommandQueue.Enqueue(new WaapiCommand( async () => await UnsubscribeAsync(id))); } /// /// Unsubscribe from a subscription. /// /// The subscription ID received from the initial subscription. /// Awaitable Task. private static async Task UnsubscribeAsync(uint id) { await WaapiClient.Unsubscribe(id); } /// /// Deserializes the objects published by the ak.wwise.ui.selectionChanged topic. /// /// Json string containing the message. /// List of WwiseObjectInfo objects. public static List ParseSelectedObjects(string json) { var info = UnityEngine.JsonUtility.FromJson(json); var ret = new List(); foreach (var child in info.objects) { ret.Add(child); } return ret; } /// /// Deserializes the object published by the ak.wwise.core.object.nameChanged topic. /// /// Json string containing the message. /// WwiseRenameInfo containing the object information and the new name. public static WwiseRenameInfo ParseRenameObject(string json) { var info = UnityEngine.JsonUtility.FromJson(json); info.ParseInfo(); return info; } /// /// Deserializes the object published by the ak.wwise.core.object.childAdded or ak.wwise.core.object.childRemoved topic. /// /// Json string containing the message. /// WwiseChildModifiedInfo containing parent and child object information. public static WwiseChildModifiedInfo ParseChildAddedOrRemoved(string json) { var info = UnityEngine.JsonUtility.FromJson(json); info.ParseInfo(); return info; } } #endif