ccl4/blueberryPeak/Assets/Wwise/API/Runtime/Handwritten/WAAPI/AkWaapiUtilities.cs
2025-06-12 10:36:40 +02:00

1098 lines
37 KiB
C#

#if UNITY_EDITOR
/*******************************************************************************
The content of this file includes portions of the proprietary AUDIOKINETIC Wwise
Technology released in source code form as part of the game integration package.
The content of this file may not be used without valid licenses to the
AUDIOKINETIC Wwise Technology.
Note that the use of the game engine is subject to the Unity(R) Terms of
Service at https://unity3d.com/legal/terms-of-service
License Usage
Licensees holding valid licenses to the AUDIOKINETIC Wwise Technology may use
this file in accordance with the end user license agreement provided with the
software or, alternatively, in accordance with the terms contained
in a written agreement between you and Audiokinetic Inc.
Copyright (c) 2025 Audiokinetic Inc.
*******************************************************************************/
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading.Tasks;
/// <summary>
/// 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.
/// </summary>
[UnityEditor.InitializeOnLoad]
public class AkWaapiUtilities
{
static private AkWaapiClient m_WaapiClient;
static bool isDisconnecting;
static Dictionary<System.Guid, TransportInfo> m_ItemTransports;
public static string ErrorMessage;
/// <summary>
/// Fired when the connection is closing or closed, the bool parameter represents whether the socket connection is still open (for cleaning up subscriptions).
/// </summary>
public static System.Action<bool> Disconnecting;
/// <summary>
/// Fired when the connection is established, should be used by external classes to subscribe to topics they are interested in.
/// </summary>
public static System.Action Connected;
/// <summary>
/// Fired when all commands in the queue have been executed.
/// </summary>
public static System.Action QueueConsumed;
private static ConcurrentQueue<WaapiCommand> waapiCommandQueue = new ConcurrentQueue<WaapiCommand>();
/// <summary>
/// Generic delegate function for callbacks that expect to receive a list of objects in response to a WAAPI request.
/// </summary>
/// <param name="result"></param>
public delegate void GetResultListDelegate<T>(List<T> result);
/// <summary>
/// Generic delegate function for callbacks that expect to receive a single object in response to a WAAPI request.
/// </summary>
/// <param name="result"></param>
public delegate void GetResultDelegate<T>(T result);
/// <summary>
/// Used to store store UnityEngine.Application.dataPath because we can't access it outside of the main loop
/// </summary>
private static string dataPath;
/// <summary>
/// Bind disconnection method to compilation started delegate and start the async Waapi loop.
/// </summary>
static AkWaapiUtilities()
{
if (UnityEditor.AssetDatabase.IsAssetImportWorkerProcess())
{
return;
}
#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();
}
/// <summary>
/// A simple structure containing an async payload function that will be executed when it is consumed by the command queue.
/// </summary>
public struct WaapiCommand
{
System.Func<Task> payload;
public WaapiCommand(System.Func<Task> payload)
{
this.payload = payload;
}
public async Task Execute()
{
await payload.Invoke();
}
}
/// <summary>
/// Class used to store information about a specific subscription.
/// </summary>
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;
}
};
/// <summary>
/// Holds information about a playing transport.
/// </summary>
struct TransportInfo
{
public int TransportID;
public uint SubscriptionID;
public TransportInfo(int transID, uint subsID)
{
TransportID = transID;
SubscriptionID = subsID;
}
};
/// <summary>
/// Stores TransportInfo of playing Events.
/// </summary>
private static Dictionary<System.Guid, TransportInfo> ItemTransports
{
get
{
if (m_ItemTransports == null)
m_ItemTransports = new Dictionary<System.Guid, TransportInfo>();
return m_ItemTransports;
}
}
/// <summary>
/// WAAPI client wrapping WAMP calls. Lazy instantiated.
/// </summary>
private static AkWaapiClient WaapiClient
{
get
{
if (m_WaapiClient == null)
{
m_WaapiClient = new AkWaapiClient();
m_WaapiClient.Disconnected += Disconnected;
}
return m_WaapiClient;
}
}
/// <summary>
/// Check whether the client is currently connected.
/// </summary>
/// <returns></returns>
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;
/// <summary>
/// Main loop for the WAAPI API. Checks if the client is connected and consumes all commands.
/// </summary>
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<WaapiCommand>();
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();
}
}
/// <summary>
/// Consumes all WAAPICommands in the queue and then fires QueueConsumed.
/// </summary>
/// <returns>Awaitable Task</returns>
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<ErrorMessage>(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();
}
}
/// <summary>
/// Checks the global WAAPI settings and disconnects if WAAPI is disabled or connection settings have changed.
/// If disconnected, try to connect with current settings.
/// </summary>
/// <returns>True if the client is connected</returns>
private static async Task<bool> 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;
}
/// <summary>
/// Tries to communicate with Wwise and compares the current open project with the project path specified in the Unity Wwise Editor settings.
/// </summary>
/// <returns>True if the correct wwise project is open in Wwise.</returns>
private async static Task<bool> 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<ErrorMessage>(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";
}
/// <summary>
/// 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.
/// </summary>
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;
}
}
/// <summary>
/// Invoked after the client has disconnected from Wwise authoring.
/// </summary>
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;
}
/// <summary>
/// Returns a rich text string representing the current WAAPI connection status.
/// </summary>
/// <returns></returns>
public static string GetStatusString()
{
var returnString = "";
if (!AkWwiseEditorSettings.Instance.UseWaapi)
{
returnString += "<color=red> Waapi disabled in project settings </color>";
}
else if (WaapiClient.wamp != null)
{
var state = WaapiClient.wamp.SocketState();
switch (state)
{
case System.Net.WebSockets.WebSocketState.Open:
returnString += "<color=green> Connected</color>";
break;
case System.Net.WebSockets.WebSocketState.Closed:
returnString += "<color=red> Disconnected </color>";
break;
case System.Net.WebSockets.WebSocketState.Connecting:
returnString += $"<color=orange> Connecting to { GetUri()}</color>";
break;
default:
returnString += $"<color=orange> Connecting to { GetUri()}</color>";
break;
}
}
else
{
returnString += "<color=red> Disconnected </color>";
}
if (ErrorMessage != string.Empty)
returnString += $" <color=red>{ErrorMessage}</color>";
return returnString;
}
private static async Task<List<WwiseObjectInfo>> 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<ReturnWwiseObjects>(result).@return;
return ParseObjectInfo(ret);
}
/// <summary>
/// Use this function to queue a command with no expected return object.
/// </summary>
/// <param name="uri">The URI of the WAAPI command</param>
/// <param name="args">Array of command-specific arguments, or <tt>{}</tt> if there are none.</param>
/// <param name="options">Array of command-specific options, or <tt>{}</tt> if there are none.</param>
public static void QueueCommand(string uri, string args, string options)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await WaapiClient.Call(uri, args, options)));
}
/// <summary>
/// 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.
/// /// </summary>
/// <typeparam name="T"> Type of the expected return object</typeparam>
/// <param name="uri">The URI of the waapi command</param>
/// <param name="args">The command-specific arguments</param>
/// <param name="options">The command-specific options</param>
/// <param name="callback">Function accepting an argument of type T</param>
public static void QueueCommandWithReturnType<T>(string uri, GetResultDelegate<T> callback, string args = null, string options = null)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () =>
{
var result = await WaapiClient.Call(uri, args, options);
callback(UnityEngine.JsonUtility.FromJson<T>(result));
}));
}
/// <summary>
/// Enqueues a command with a payload that desirializes the list of wwise objects from the response.
/// </summary>
/// <param name="args"></param>
/// <param name="options"></param>
/// <param name="callback"></param>
public static void QueueCommandWithReturnWwiseObjects<T>(WaqlArgs args, ReturnOptions options, GetResultListDelegate<T> callback)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () =>
{
var result = await WaapiClient.Call(ak.wwise.core.@object.get, args, options);
var ret = UnityEngine.JsonUtility.FromJson<ReturnWwiseObjects<T>>(result);
callback.Invoke(ret.@return);
}));
}
/// <summary>
/// Generic function for fetching a Wwise object with custom return options.
/// </summary>
/// <typeparam name="T"> Type of the object to be deserialized from the response.</typeparam>
/// <param name="guid">GUID of the target object.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
/// <param name="callback">Function accteping a list of T objects.</param>
public static void GetWwiseObject<T>(System.Guid guid, ReturnOptions options, GetResultListDelegate<T> callback)
{
GetWwiseObjects(new List<System.Guid>() { guid }, options, callback);
}
/// <summary>
/// Generic function for fetching a list of Wwise objects with custom return options.
/// </summary>
/// <typeparam name="T"> Type of the object to be deserialized from the response.</typeparam>
/// <param name="guids">GUIDs of the target objects.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
/// <param name="callback">Function accteping a list of T objects.</param>
public static void GetWwiseObjects<T>(List<System.Guid> guids, ReturnOptions options, GetResultListDelegate<T> callback)
{
string guidString = "";
foreach (var guid in guids)
{
guidString += $"{guid:B} ,";
}
var args = new WaqlArgs($"from object \"{guidString}\" ");
QueueCommandWithReturnWwiseObjects(args, options, callback);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="guid">GUID of the target object.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
/// <param name="callback">Function accepting a list of WwiseObjectInfo.</param>
public static void GetWwiseObjectAndAncestors<T>(System.Guid guid, ReturnOptions options, GetResultListDelegate<T> callback)
{
var args = new WaqlArgs($"from object \"{guid:B}\" select this, ancestors orderby path");
QueueCommandWithReturnWwiseObjects(args, options, callback);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="guid">GUID of the target object.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
/// <param name="depth"> Depth of descendants to fetch. If -1, fetches all descendants.</param>
/// <param name="callback">Function accepting a list of WwiseObjectInfo.</param>
public static void GetWwiseObjectAndDescendants<T>(System.Guid guid, ReturnOptions options, int depth, GetResultListDelegate<T> callback)
{
GetWwiseObjectAndDescendants(guid.ToString("B"), options, depth, callback);
}
/// <summary>
/// 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
/// </summary>
/// <param name="identifier">Can bethe target object GUID or path within the hierarchy.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
/// <param name="depth">Depth of descendants to fetch. If -1, fetches all descendants.</param>
/// <param name="callback">Function accepting a list of WwiseObjectInfo.</param>
public static void GetWwiseObjectAndDescendants<T>(string identifier, ReturnOptions options, int depth, GetResultListDelegate<T> 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);
}
/// <summary>
/// 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
/// </summary>
/// <param name="searchString">Characters to search for.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
/// <param name="objectType">An optional object type used to filter search results.</param>
/// <param name="callback">Function accepting a list of WwiseObjectInfo.</param>
public static void Search<T>(string searchString, WwiseObjectType objectType, ReturnOptions options, GetResultListDelegate<T> 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);
}
/// <summary>
/// Get the children of a given object.
/// </summary>
/// <param name="guid">GUID of the target object.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
/// <param name="callback">Function accepting a list of WwiseObjectInfo.</param>
public static void GetChildren<T>(System.Guid guid, ReturnOptions options, GetResultListDelegate<T> callback)
{
if (guid == System.Guid.Empty)
return;
var args = new WaqlArgs($"from object \"{guid:B}\" select children orderby path");
QueueCommandWithReturnWwiseObjects(args, options, callback);
}
/// <summary>
/// Get the WwiseObjectInfo for the project.
/// </summary>
/// <param name="callback">Function accepting a list of WwiseObjectInfo. The first element of the list will be the project info.</param>
/// <param name="options">Specifies which object properties to include in the response</param>
public static void GetProject<T>(GetResultListDelegate<T> callback, ReturnOptions options)
{
var args = new WaqlArgs($"from type {WaapiKeywords.PROJECT}");
QueueCommandWithReturnWwiseObjects(args, options, callback);
}
/// <summary>
/// Parse the response WwiseObjectInfoJsonObject of a "from object" request and implicit cast the objects to WwiseObjectInfo.
/// </summary>
/// <param name="returnObjects"></param>
/// <returns></returns>
public static List<WwiseObjectInfo> ParseObjectInfo(List<WwiseObjectInfoJsonObject> returnObjects)
{
var returnInfo = new List<WwiseObjectInfo>(returnObjects.Count);
foreach (var info in returnObjects)
{
returnInfo.Add(info);
}
return returnInfo;
}
/// <summary>
/// Select the object in Wwise Authoring.
/// Creates a WaapiCommand object containing a lambda call to SelectObjectInAuthoringAsync and adds it to the waapiCommandQueue.
/// </summary>
/// <param name="guid">GUID of the object to be selected.</param>
public static void SelectObjectInAuthoring(System.Guid guid)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await SelectObjectInAuthoringAsync(guid)));
}
/// <summary>
/// Creates and sends a WAAPI command to select a Wwise object.
/// </summary>
/// <param name="guid">GUID of the object to be selected.</param>
/// <returns></returns>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="guid">GUID of the object to be found.</param>
public static void OpenWorkUnitInExplorer(System.Guid guid)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await OpenWorkUnitInExplorerAsync(guid)));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="guid">GUID of the SoundBank to be found.</param>
public static void OpenSoundBankInExplorer(System.Guid guid)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await OpenSoundBankInExplorerAsync(guid)));
}
/// <summary>
/// Uses a waapi call to get the object's file path, then opens the containing folder in the system's file browser.
/// </summary>
/// <param name="guid">GUID of the object to be found.</param>
/// <returns>Awaitable Task.</returns>
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<ReturnWwiseObjects>(result);
var filePath = ret.@return[0].filePath;
filePath = filePath.Replace("\\", "/");
#if UNITY_EDITOR_OSX
filePath = AkUtilities.ParseOsxPathFromWinePath(filePath);
#endif
UnityEditor.EditorUtility.RevealInFinder(filePath);
}
/// <summary>
/// Uses a waapi call to get the SoundBank's generated bank path, then opens the containing folder in the system's file browser.
/// </summary>
/// <param name="guid">GUID of the object to be found.</param>
/// <returns>Awaitable Task.</returns>
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<ReturnWwiseObjects>(result);
var filePath = ret.@return[0].soundbankBnkFilePath;
#if UNITY_EDITOR_OSX
filePath = AkUtilities.ParseOsxPathFromWinePath(filePath);
#endif
UnityEditor.EditorUtility.RevealInFinder(filePath);
}
/// <summary>
/// Rename an object in Wwise authoring.
/// Creates a WaapiCommand object containing a lambda call to RenameAsync and adds it to the waapiCommandQueue.
/// </summary>
/// <param name="guid">GUID of the object to be renamed.</param>
/// <param name="newName">New name for the wwise object.</param>
public static void Rename(System.Guid guid, string newName)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await RenameAsync(guid, newName)
));
}
/// <summary>
/// Sends a WAAPI command to rename a Wwise object.
/// </summary>
/// <param name="guid">GUID of the object to be renamed.</param>
/// <param name="newName">New name for the wwise object.</param>
/// <returns>Awaitable Task.</returns>
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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="guid">GUID of the object to be deleted.</param>
public static void Delete(System.Guid guid)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await DeleteAsync(guid)
));
}
/// <summary>
/// Sends three WAAPI commands:
/// 1. Begin an undo group.
/// 2. Delete the specified object.
/// 3. Close the undo group.
/// </summary>
/// <param name="guid">GUID of the object to be deleted.</param>
/// <returns>Awaitable Task.</returns>
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));
}
/// <summary>
/// Checks if Wwise object is playable.
/// </summary>
/// <param name="type">WwiseObjectType of object to check.</param>
/// <returns>True if playable</returns>
public static bool IsPlayable(WwiseObjectType type)
{
return (type == WwiseObjectType.Event);
}
/// <summary>
/// Play or pause an object in Wwise authoring.
/// Creates a WaapiCommand object containing a lambda call to TogglePlayEventAsync and adds it to the waapiCommandQueue.
///</summary>
/// <param name="objectType">Used to check whether the object is playable.</param>
/// <param name="guid">GUID of the object to be played.</param>
static public void TogglePlayEvent(WwiseObjectType objectType, System.Guid guid)
{
if (IsPlayable(objectType))
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await TogglePlayEventAsync(guid)));
}
}
/// <summary>
/// Play or pause an object in Wwise authoring. Opens a new transport in wwise to play the sound if it does not exist yet.
/// </summary>
/// <param name="guid">GUID of the object to be played.</param>
/// <returns></returns>
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);
}
/// <summary>
/// Find the open transport in ItemTransports or create a new one.
/// </summary>
/// <param name="guid">GUID of the object.</param>
/// <returns></returns>
static async private Task<int> GetTransport(System.Guid guid)
{
TransportInfo transportInfo;
if (!ItemTransports.TryGetValue(guid, out transportInfo))
{
transportInfo = await CreateTransport(guid);
}
return transportInfo.TransportID;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="guid">GUID of the Event</param>
/// <returns></returns>
static async private Task<TransportInfo> 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<ReturnTransport>(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;
}
/// <summary>
/// Handle the messages published by a transport when its state is changed.
/// If stopped, enqueue a command with DestroyTransport as its payload.
/// </summary>
/// <param name="message"></param>
static private void HandleTransportStateChanged(string message)
{
TransportState transport = UnityEngine.JsonUtility.FromJson<TransportState>(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));
}
}
/// <summary>
/// Send a WAAPI command to stop the specific transport.
/// </summary>
/// <param name="in_transportID">ID of the transport.</param>
/// <returns></returns>
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);
}
/// <summary>
/// Unsubscribe from the transport topic and send a WAAPI command to destroy the transport in Wwise.
/// </summary>
/// <param name="in_itemID"> GUID of the Event.</param>
/// <returns></returns>
static async Task<string> 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;
}
/// <summary>
/// Stops all playing transports.
/// Creates a WaapiCommand object containing a lambda call to StopAllTransportsAsync and adds it to the waapiCommandQueue.
/// </summary>
static public void StopAllTransports()
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await StopAllTransportsAsync()));
}
/// <summary>
/// Stops all playing transports.
/// </summary>
/// <returns>Awaitable task.</returns>
static private async Task StopAllTransportsAsync()
{
foreach (var item in ItemTransports)
{
await StopTransport(item.Value.TransportID);
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="topic">The topic URI to subscribe to.</param>
/// <param name="subscriptionCallback">Delegate function to call when the topic is published.</param>
/// <param name="handshakeCallback">Action to be executed once the subscription has been made.</param>
/// <param name="returnOptions">The options the subscription.
/// This should store the subscription ID so that the subscription can be cleaned up when it is no longer needed.</param>
public static void Subscribe(string topic, Wamp.PublishHandler subscriptionCallback, System.Action<SubscriptionInfo> handshakeCallback, ReturnOptions returnOptions = null)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => handshakeCallback(await SubscribeAsync(new SubscriptionInfo(topic, subscriptionCallback), returnOptions))));
}
/// <summary>
/// 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.
/// </summary>
/// <param name="subscription">SubscriptionInfo object containing the topic URI and the message handling callback.</param>
/// <param name="returnOptions">The options the subscription.</param>
/// <returns>Updated SubscriptionInfo object containing the subscription ID (uint). </returns>
private static async Task<SubscriptionInfo> SubscribeAsync(SubscriptionInfo subscription, ReturnOptions returnOptions)
{
uint id = await WaapiClient.Subscribe(subscription.Uri, returnOptions, subscription.Callback);
subscription.SubscriptionId = id;
return subscription;
}
/// <summary>
/// Unsubscribe from an existing subscription.
/// Creates a WaapiCommand object containing a lambda call to UnsubscribeAsync and adds it to the waapiCommandQueue.
/// </summary>
/// <param name="id"> The subscription ID received from the initial subscription.</param>
public static void Unsubscribe(uint id)
{
waapiCommandQueue.Enqueue(new WaapiCommand(
async () => await UnsubscribeAsync(id)));
}
/// <summary>
/// Unsubscribe from a subscription.
/// </summary>
/// <param name="id"> The subscription ID received from the initial subscription.</param>
/// <returns>Awaitable Task.</returns>
private static async Task UnsubscribeAsync(uint id)
{
await WaapiClient.Unsubscribe(id);
}
/// <summary>
/// Deserializes the objects published by the ak.wwise.ui.selectionChanged topic.
/// </summary>
/// <param name="json">Json string containing the message.</param>
/// <returns>List of WwiseObjectInfo objects. </returns>
public static List<WwiseObjectInfo> ParseSelectedObjects(string json)
{
var info = UnityEngine.JsonUtility.FromJson<SelectedWwiseObjects>(json);
var ret = new List<WwiseObjectInfo>();
foreach (var child in info.objects)
{
ret.Add(child);
}
return ret;
}
/// <summary>
/// Deserializes the object published by the ak.wwise.core.object.nameChanged topic.
/// </summary>
/// <param name="json">Json string containing the message.</param>
/// <returns>WwiseRenameInfo containing the object information and the new name.</returns>
public static WwiseRenameInfo ParseRenameObject(string json)
{
var info = UnityEngine.JsonUtility.FromJson<WwiseRenameInfo>(json);
info.ParseInfo();
return info;
}
/// <summary>
/// Deserializes the object published by the ak.wwise.core.object.childAdded or ak.wwise.core.object.childRemoved topic.
/// </summary>
/// <param name="json">Json string containing the message.</param>
/// <returns> WwiseChildModifiedInfo containing parent and child object information.</returns>
public static WwiseChildModifiedInfo ParseChildAddedOrRemoved(string json)
{
var info = UnityEngine.JsonUtility.FromJson<WwiseChildModifiedInfo>(json);
info.ParseInfo();
return info;
}
}
#endif