// This file is provided under The MIT License as part of Steamworks.NET. // Copyright (c) 2013-2022 Riley Labrecque // Please see the included LICENSE.txt for additional information. // This file is automatically generated. // Changes to this file will be reverted when you update Steamworks.NET #if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX) #define DISABLESTEAMWORKS #endif #if !DISABLESTEAMWORKS #if UNITY_3_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6 #error Unsupported Unity platform. Steamworks.NET requires Unity 4.7 or higher. #elif UNITY_4_7 || UNITY_5 || UNITY_2017 || UNITY_2017_1_OR_NEWER #if UNITY_EDITOR_WIN || (UNITY_STANDALONE_WIN && !UNITY_EDITOR) #define WINDOWS_BUILD #endif #elif STEAMWORKS_WIN #define WINDOWS_BUILD #elif STEAMWORKS_LIN_OSX // So that we don't enter the else block below. #else #error You need to define STEAMWORKS_WIN, or STEAMWORKS_LIN_OSX. Refer to the readme for more details. #endif using System; using System.Collections.Generic; using System.Runtime.InteropServices; namespace Steamworks { public static class CallbackDispatcher { // We catch exceptions inside callbacks and reroute them here. // For some reason throwing an exception causes RunCallbacks() to break otherwise. // If you have a custom ExceptionHandler in your engine you can register it here manually until we get something more elegant hooked up. public static void ExceptionHandler(Exception e) { #if UNITY_STANDALONE UnityEngine.Debug.LogException(e); #elif STEAMWORKS_WIN || STEAMWORKS_LIN_OSX Console.WriteLine(e.Message); #endif } private static Dictionary> m_registeredCallbacks = new Dictionary>(); private static Dictionary> m_registeredGameServerCallbacks = new Dictionary>(); private static Dictionary> m_registeredCallResults = new Dictionary>(); private static object m_sync = new object(); private static IntPtr m_pCallbackMsg; private static int m_initCount; public static bool IsInitialized { get { return m_initCount > 0; } } internal static void Initialize() { lock (m_sync) { if (m_initCount == 0) { NativeMethods.SteamAPI_ManualDispatch_Init(); m_pCallbackMsg = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CallbackMsg_t))); } ++m_initCount; } } internal static void Shutdown() { lock (m_sync) { --m_initCount; if (m_initCount == 0) { UnregisterAll(); Marshal.FreeHGlobal(m_pCallbackMsg); m_pCallbackMsg = IntPtr.Zero; } } } internal static void Register(Callback cb) { int iCallback = CallbackIdentities.GetCallbackIdentity(cb.GetCallbackType()); var callbacksRegistry = cb.IsGameServer ? m_registeredGameServerCallbacks : m_registeredCallbacks; lock (m_sync) { List callbacksList; if (!callbacksRegistry.TryGetValue(iCallback, out callbacksList)) { callbacksList = new List(); callbacksRegistry.Add(iCallback, callbacksList); } callbacksList.Add(cb); } } internal static void Register(SteamAPICall_t asyncCall, CallResult cr) { lock (m_sync) { List callResultsList; if (!m_registeredCallResults.TryGetValue((ulong)asyncCall, out callResultsList)) { callResultsList = new List(); m_registeredCallResults.Add((ulong)asyncCall, callResultsList); } callResultsList.Add(cr); } } internal static void Unregister(Callback cb) { int iCallback = CallbackIdentities.GetCallbackIdentity(cb.GetCallbackType()); var callbacksRegistry = cb.IsGameServer ? m_registeredGameServerCallbacks : m_registeredCallbacks; lock (m_sync) { List callbacksList; if (callbacksRegistry.TryGetValue(iCallback, out callbacksList)) { callbacksList.Remove(cb); if (callbacksList.Count == 0) callbacksRegistry.Remove(iCallback); } } } internal static void Unregister(SteamAPICall_t asyncCall, CallResult cr) { lock (m_sync) { List callResultsList; if (m_registeredCallResults.TryGetValue((ulong)asyncCall, out callResultsList)) { callResultsList.Remove(cr); if (callResultsList.Count == 0) m_registeredCallResults.Remove((ulong)asyncCall); } } } private static void UnregisterAll() { List callbacks = new List(); List callResults = new List(); lock (m_sync) { foreach (var pair in m_registeredCallbacks) { callbacks.AddRange(pair.Value); } m_registeredCallbacks.Clear(); foreach (var pair in m_registeredGameServerCallbacks) { callbacks.AddRange(pair.Value); } m_registeredGameServerCallbacks.Clear(); foreach (var pair in m_registeredCallResults) { callResults.AddRange(pair.Value); } m_registeredCallResults.Clear(); foreach (var callback in callbacks) { callback.SetUnregistered(); } foreach (var callResult in callResults) { callResult.SetUnregistered(); } } } internal static void RunFrame(bool isGameServer) { if (!IsInitialized) throw new InvalidOperationException("Callback dispatcher is not initialized."); HSteamPipe hSteamPipe = (HSteamPipe)(isGameServer ? NativeMethods.SteamGameServer_GetHSteamPipe() : NativeMethods.SteamAPI_GetHSteamPipe()); NativeMethods.SteamAPI_ManualDispatch_RunFrame(hSteamPipe); var callbacksRegistry = isGameServer ? m_registeredGameServerCallbacks : m_registeredCallbacks; while (NativeMethods.SteamAPI_ManualDispatch_GetNextCallback(hSteamPipe, m_pCallbackMsg)) { CallbackMsg_t callbackMsg = (CallbackMsg_t)Marshal.PtrToStructure(m_pCallbackMsg, typeof(CallbackMsg_t)); try { // Check for dispatching API call results if (callbackMsg.m_iCallback == SteamAPICallCompleted_t.k_iCallback) { SteamAPICallCompleted_t callCompletedCb = (SteamAPICallCompleted_t)Marshal.PtrToStructure(callbackMsg.m_pubParam, typeof(SteamAPICallCompleted_t)); IntPtr pTmpCallResult = Marshal.AllocHGlobal((int)callCompletedCb.m_cubParam); bool bFailed; if (NativeMethods.SteamAPI_ManualDispatch_GetAPICallResult(hSteamPipe, callCompletedCb.m_hAsyncCall, pTmpCallResult, (int)callCompletedCb.m_cubParam, callCompletedCb.m_iCallback, out bFailed)) { lock (m_sync) { List callResults; if (m_registeredCallResults.TryGetValue((ulong)callCompletedCb.m_hAsyncCall, out callResults)) { m_registeredCallResults.Remove((ulong)callCompletedCb.m_hAsyncCall); foreach (var cr in callResults) { cr.OnRunCallResult(pTmpCallResult, bFailed, (ulong)callCompletedCb.m_hAsyncCall); cr.SetUnregistered(); } } } } Marshal.FreeHGlobal(pTmpCallResult); } else { List callbacks; if (callbacksRegistry.TryGetValue(callbackMsg.m_iCallback, out callbacks)) { List callbacksCopy; lock (m_sync) { callbacksCopy = new List(callbacks); } foreach (var callback in callbacksCopy) { callback.OnRunCallback(callbackMsg.m_pubParam); } } } } catch (Exception e) { ExceptionHandler(e); } finally { NativeMethods.SteamAPI_ManualDispatch_FreeLastCallback(hSteamPipe); } } } } public abstract class Callback { public abstract bool IsGameServer { get; } internal abstract Type GetCallbackType(); internal abstract void OnRunCallback(IntPtr pvParam); internal abstract void SetUnregistered(); } public sealed class Callback : Callback, IDisposable { public delegate void DispatchDelegate(T param); private event DispatchDelegate m_Func; private bool m_bGameServer; private bool m_bIsRegistered; private bool m_bDisposed = false; /// /// Creates a new Callback. You must be calling SteamAPI.RunCallbacks() to retrieve the callbacks. /// Returns a handle to the Callback. /// This MUST be assigned to a member variable to prevent the GC from cleaning it up. /// public static Callback Create(DispatchDelegate func) { return new Callback(func, bGameServer: false); } /// /// Creates a new GameServer Callback. You must be calling GameServer.RunCallbacks() to retrieve the callbacks. /// Returns a handle to the Callback. /// This MUST be assigned to a member variable to prevent the GC from cleaning it up. /// public static Callback CreateGameServer(DispatchDelegate func) { return new Callback(func, bGameServer: true); } public Callback(DispatchDelegate func, bool bGameServer = false) { m_bGameServer = bGameServer; Register(func); } ~Callback() { Dispose(); } public void Dispose() { if (m_bDisposed) { return; } GC.SuppressFinalize(this); if (m_bIsRegistered) Unregister(); m_bDisposed = true; } // Manual registration of the callback public void Register(DispatchDelegate func) { if (func == null) { throw new Exception("Callback function must not be null."); } if (m_bIsRegistered) { Unregister(); } m_Func = func; CallbackDispatcher.Register(this); m_bIsRegistered = true; } public void Unregister() { CallbackDispatcher.Unregister(this); m_bIsRegistered = false; } public override bool IsGameServer { get { return m_bGameServer; } } internal override Type GetCallbackType() { return typeof(T); } internal override void OnRunCallback(IntPtr pvParam) { try { m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T))); } catch (Exception e) { CallbackDispatcher.ExceptionHandler(e); } } internal override void SetUnregistered() { m_bIsRegistered = false; } } public abstract class CallResult { internal abstract Type GetCallbackType(); internal abstract void OnRunCallResult(IntPtr pvParam, bool bFailed, ulong hSteamAPICall); internal abstract void SetUnregistered(); } public sealed class CallResult : CallResult, IDisposable { public delegate void APIDispatchDelegate(T param, bool bIOFailure); private event APIDispatchDelegate m_Func; private SteamAPICall_t m_hAPICall = SteamAPICall_t.Invalid; public SteamAPICall_t Handle { get { return m_hAPICall; } } private bool m_bDisposed = false; /// /// Creates a new async CallResult. You must be calling SteamAPI.RunCallbacks() to retrieve the callback. /// Returns a handle to the CallResult. /// This MUST be assigned to a member variable to prevent the GC from cleaning it up. /// public static CallResult Create(APIDispatchDelegate func = null) { return new CallResult(func); } public CallResult(APIDispatchDelegate func = null) { m_Func = func; } ~CallResult() { Dispose(); } public void Dispose() { if (m_bDisposed) { return; } GC.SuppressFinalize(this); Cancel(); m_bDisposed = true; } public void Set(SteamAPICall_t hAPICall, APIDispatchDelegate func = null) { // Unlike the official SDK we let the user assign a single function during creation, // and allow them to skip having to do so every time that they call .Set() if (func != null) { m_Func = func; } if (m_Func == null) { throw new Exception("CallResult function was null, you must either set it in the CallResult Constructor or via Set()"); } if (m_hAPICall != SteamAPICall_t.Invalid) { CallbackDispatcher.Unregister(m_hAPICall, this); } m_hAPICall = hAPICall; if (hAPICall != SteamAPICall_t.Invalid) { CallbackDispatcher.Register(hAPICall, this); } } public bool IsActive() { return (m_hAPICall != SteamAPICall_t.Invalid); } public void Cancel() { if (IsActive()) CallbackDispatcher.Unregister(m_hAPICall, this); } internal override Type GetCallbackType() { return typeof(T); } internal override void OnRunCallResult(IntPtr pvParam, bool bFailed, ulong hSteamAPICall_) { SteamAPICall_t hSteamAPICall = (SteamAPICall_t)hSteamAPICall_; if (hSteamAPICall == m_hAPICall) { try { m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T)), bFailed); } catch (Exception e) { CallbackDispatcher.ExceptionHandler(e); } } } internal override void SetUnregistered() { m_hAPICall = SteamAPICall_t.Invalid; } } } #endif // !DISABLESTEAMWORKS