CnC_Remastered_Collection

Command and Conquer: Red Alert
Log | Files | Refs | README | LICENSE

CallbackDispatcher.cs (12638B)


      1 // This file is provided under The MIT License as part of Steamworks.NET.
      2 // Copyright (c) 2013-2019 Riley Labrecque
      3 // Please see the included LICENSE.txt for additional information.
      4 
      5 // This file is automatically generated.
      6 // Changes to this file will be reverted when you update Steamworks.NET
      7 
      8 #if UNITY_ANDROID || UNITY_IOS || UNITY_TIZEN || UNITY_TVOS || UNITY_WEBGL || UNITY_WSA || UNITY_PS4 || UNITY_WII || UNITY_XBOXONE || UNITY_SWITCH
      9 #define DISABLESTEAMWORKS
     10 #endif
     11 
     12 #if !DISABLESTEAMWORKS
     13 
     14 #if UNITY_3_5 || UNITY_4_0 || UNITY_4_1 || UNITY_4_2 || UNITY_4_3 || UNITY_4_5 || UNITY_4_6
     15 	#error Unsupported Unity platform. Steamworks.NET requires Unity 4.7 or higher.
     16 #elif UNITY_4_7 || UNITY_5 || UNITY_2017 || UNITY_2017_1_OR_NEWER
     17 	#if UNITY_EDITOR_WIN || (UNITY_STANDALONE_WIN && !UNITY_EDITOR)
     18 		#define WINDOWS_BUILD
     19 	#endif
     20 #elif STEAMWORKS_WIN
     21 	#define WINDOWS_BUILD
     22 #elif STEAMWORKS_LIN_OSX
     23 	// So that we don't enter the else block below.
     24 #else
     25 	#error You need to define STEAMWORKS_WIN, or STEAMWORKS_LIN_OSX. Refer to the readme for more details.
     26 #endif
     27 
     28 // Unity 32bit Mono on Windows crashes with ThisCall/Cdecl for some reason, StdCall without the 'this' ptr is the only thing that works..?
     29 #if (UNITY_EDITOR_WIN && !UNITY_EDITOR_64) || (!UNITY_EDITOR && UNITY_STANDALONE_WIN && !UNITY_64)
     30 	#define STDCALL
     31 #elif STEAMWORKS_WIN
     32 	#define THISCALL
     33 #endif
     34 
     35 // Calling Conventions:
     36 // Unity x86 Windows        - StdCall (No this pointer)
     37 // Unity x86 Linux          - Cdecl
     38 // Unity x86 OSX            - Cdecl
     39 // Unity x64 Windows        - Cdecl
     40 // Unity x64 Linux          - Cdecl
     41 // Unity x64 OSX            - Cdecl
     42 // Microsoft x86 Windows    - ThisCall
     43 // Microsoft x64 Windows    - ThisCall
     44 // Mono x86 Linux           - Cdecl
     45 // Mono x86 OSX             - Cdecl
     46 // Mono x64 Linux           - Cdecl
     47 // Mono x64 OSX             - Cdecl
     48 // Mono on Windows is probably not supported.
     49 
     50 using System;
     51 using System.Runtime.InteropServices;
     52 
     53 namespace Steamworks {
     54 	public static class CallbackDispatcher {
     55 		// We catch exceptions inside callbacks and reroute them here.
     56 		// For some reason throwing an exception causes RunCallbacks() to break otherwise.
     57 		// If you have a custom ExceptionHandler in your engine you can register it here manually until we get something more elegant hooked up.
     58 		public static void ExceptionHandler(Exception e) {
     59 #if UNITY_STANDALONE
     60 			UnityEngine.Debug.LogException(e);
     61 #elif STEAMWORKS_WIN || STEAMWORKS_LIN_OSX
     62 			Console.WriteLine(e.Message);
     63 #endif
     64 		}
     65 	}
     66 
     67 	public sealed class Callback<T> : IDisposable {
     68 		private CCallbackBaseVTable m_CallbackBaseVTable;
     69 		private IntPtr m_pVTable = IntPtr.Zero;
     70 		private CCallbackBase m_CCallbackBase;
     71 		private GCHandle m_pCCallbackBase;
     72 
     73 		public delegate void DispatchDelegate(T param);
     74 		private event DispatchDelegate m_Func;
     75 
     76 		private bool m_bGameServer;
     77 		private readonly int m_size = Marshal.SizeOf(typeof(T));
     78 
     79 		private bool m_bDisposed = false;
     80 
     81 		/// <summary>
     82 		/// Creates a new Callback. You must be calling SteamAPI.RunCallbacks() to retrieve the callbacks.
     83 		/// <para>Returns a handle to the Callback.</para>
     84 		/// <para>This MUST be assigned to a member variable to prevent the GC from cleaning it up.</para>
     85 		/// </summary>
     86 		public static Callback<T> Create(DispatchDelegate func) {
     87 			return new Callback<T>(func, bGameServer: false);
     88 		}
     89 
     90 		/// <summary>
     91 		/// Creates a new GameServer Callback. You must be calling GameServer.RunCallbacks() to retrieve the callbacks.
     92 		/// <para>Returns a handle to the Callback.</para>
     93 		/// <para>This MUST be assigned to a member variable to prevent the GC from cleaning it up.</para>
     94 		/// </summary>
     95 		public static Callback<T> CreateGameServer(DispatchDelegate func) {
     96 			return new Callback<T>(func, bGameServer: true);
     97 		}
     98 
     99 		public Callback(DispatchDelegate func, bool bGameServer = false) {
    100 			m_bGameServer = bGameServer;
    101 			BuildCCallbackBase();
    102 			Register(func);
    103 		}
    104 
    105 		~Callback() {
    106 			Dispose();
    107 		}
    108 
    109 		public void Dispose() {
    110 			if (m_bDisposed) {
    111 				return;
    112 			}
    113 
    114 			GC.SuppressFinalize(this);
    115 
    116 			Unregister();
    117 
    118 			if (m_pVTable != IntPtr.Zero) {
    119 				Marshal.FreeHGlobal(m_pVTable);
    120 			}
    121 
    122 			if (m_pCCallbackBase.IsAllocated) {
    123 				m_pCCallbackBase.Free();
    124 			}
    125 
    126 			m_bDisposed = true;
    127 		}
    128 
    129 		// Manual registration of the callback
    130 		public void Register(DispatchDelegate func) {
    131 			if (func == null) {
    132 				throw new Exception("Callback function must not be null.");
    133 			}
    134 
    135 			if ((m_CCallbackBase.m_nCallbackFlags & CCallbackBase.k_ECallbackFlagsRegistered) == CCallbackBase.k_ECallbackFlagsRegistered) {
    136 				Unregister();
    137 			}
    138 
    139 			if (m_bGameServer) {
    140 				SetGameserverFlag();
    141 			}
    142 
    143 			m_Func = func;
    144 
    145 			// k_ECallbackFlagsRegistered is set by SteamAPI_RegisterCallback.
    146 			NativeMethods.SteamAPI_RegisterCallback(m_pCCallbackBase.AddrOfPinnedObject(), CallbackIdentities.GetCallbackIdentity(typeof(T)));
    147 		}
    148 
    149 		public void Unregister() {
    150 			// k_ECallbackFlagsRegistered is removed by SteamAPI_UnregisterCallback.
    151 			NativeMethods.SteamAPI_UnregisterCallback(m_pCCallbackBase.AddrOfPinnedObject());
    152 		}
    153 
    154 		public void SetGameserverFlag() {
    155 			m_CCallbackBase.m_nCallbackFlags |= CCallbackBase.k_ECallbackFlagsGameServer;
    156 		}
    157 
    158 		private void OnRunCallback(
    159 #if !STDCALL
    160 			IntPtr thisptr,
    161 #endif
    162 			IntPtr pvParam) {
    163 			try {
    164 				m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T)));
    165 			}
    166 			catch (Exception e) {
    167 				CallbackDispatcher.ExceptionHandler(e);
    168 			}
    169 		}
    170 
    171 		// Shouldn't ever get called here, but this is what C++ Steamworks does!
    172 		private void OnRunCallResult(
    173 #if !STDCALL
    174 			IntPtr thisptr,
    175 #endif
    176 			IntPtr pvParam, bool bFailed, ulong hSteamAPICall) {
    177 			try { 
    178 				m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T)));
    179 			}
    180 			catch (Exception e) {
    181 				CallbackDispatcher.ExceptionHandler(e);
    182 			}
    183 		}
    184 
    185 		private int OnGetCallbackSizeBytes(
    186 #if !STDCALL
    187 			IntPtr thisptr
    188 #endif
    189 			) {
    190 			return m_size;
    191 		}
    192 
    193 		// Steamworks.NET Specific
    194 		private void BuildCCallbackBase() {
    195 			m_CallbackBaseVTable = new CCallbackBaseVTable() {
    196 				m_RunCallResult = OnRunCallResult,
    197 				m_RunCallback = OnRunCallback,
    198 				m_GetCallbackSizeBytes = OnGetCallbackSizeBytes
    199 			};
    200 			m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CCallbackBaseVTable)));
    201 			Marshal.StructureToPtr(m_CallbackBaseVTable, m_pVTable, false);
    202 
    203 			m_CCallbackBase = new CCallbackBase() {
    204 				m_vfptr = m_pVTable,
    205 				m_nCallbackFlags = 0,
    206 				m_iCallback = CallbackIdentities.GetCallbackIdentity(typeof(T))
    207 			};
    208 			m_pCCallbackBase = GCHandle.Alloc(m_CCallbackBase, GCHandleType.Pinned);
    209 		}
    210 	}
    211 
    212 	public sealed class CallResult<T> : IDisposable {
    213 		private CCallbackBaseVTable m_CallbackBaseVTable;
    214 		private IntPtr m_pVTable = IntPtr.Zero;
    215 		private CCallbackBase m_CCallbackBase;
    216 		private GCHandle m_pCCallbackBase;
    217 
    218 		public delegate void APIDispatchDelegate(T param, bool bIOFailure);
    219 		private event APIDispatchDelegate m_Func;
    220 
    221 		private SteamAPICall_t m_hAPICall = SteamAPICall_t.Invalid;
    222 		public SteamAPICall_t Handle { get { return m_hAPICall; } }
    223 
    224 		private readonly int m_size = Marshal.SizeOf(typeof(T));
    225 
    226 		private bool m_bDisposed = false;
    227 
    228 		/// <summary>
    229 		/// Creates a new async CallResult. You must be calling SteamAPI.RunCallbacks() to retrieve the callback.
    230 		/// <para>Returns a handle to the CallResult.</para>
    231 		/// <para>This MUST be assigned to a member variable to prevent the GC from cleaning it up.</para>
    232 		/// </summary>
    233 		public static CallResult<T> Create(APIDispatchDelegate func = null) {
    234 			return new CallResult<T>(func);
    235 		}
    236 
    237 		public CallResult(APIDispatchDelegate func = null) {
    238 			m_Func = func;
    239 			BuildCCallbackBase();
    240 		}
    241 
    242 		~CallResult() {
    243 			Dispose();
    244 		}
    245 
    246 		public void Dispose() {
    247 			if (m_bDisposed) {
    248 				return;
    249 			}
    250 
    251 			GC.SuppressFinalize(this);
    252 
    253 			Cancel();
    254 
    255 			if (m_pVTable != IntPtr.Zero) {
    256 				Marshal.FreeHGlobal(m_pVTable);
    257 			}
    258 
    259 			if (m_pCCallbackBase.IsAllocated) {
    260 				m_pCCallbackBase.Free();
    261 			}
    262 
    263 			m_bDisposed = true;
    264 		}
    265 
    266 		public void Set(SteamAPICall_t hAPICall, APIDispatchDelegate func = null) {
    267 			// Unlike the official SDK we let the user assign a single function during creation,
    268 			// and allow them to skip having to do so every time that they call .Set()
    269 			if (func != null) {
    270 				m_Func = func;
    271 			}
    272 
    273 			if (m_Func == null) {
    274 				throw new Exception("CallResult function was null, you must either set it in the CallResult Constructor or via Set()");
    275 			}
    276 
    277 			if (m_hAPICall != SteamAPICall_t.Invalid) {
    278 				NativeMethods.SteamAPI_UnregisterCallResult(m_pCCallbackBase.AddrOfPinnedObject(), (ulong)m_hAPICall);
    279 			}
    280 
    281 			m_hAPICall = hAPICall;
    282 
    283 			if (hAPICall != SteamAPICall_t.Invalid) {
    284 				NativeMethods.SteamAPI_RegisterCallResult(m_pCCallbackBase.AddrOfPinnedObject(), (ulong)hAPICall);
    285 			}
    286 		}
    287 
    288 		public bool IsActive() {
    289 			return (m_hAPICall != SteamAPICall_t.Invalid);
    290 		}
    291 
    292 		public void Cancel() {
    293 			if (m_hAPICall != SteamAPICall_t.Invalid) {
    294 				NativeMethods.SteamAPI_UnregisterCallResult(m_pCCallbackBase.AddrOfPinnedObject(), (ulong)m_hAPICall);
    295 				m_hAPICall = SteamAPICall_t.Invalid;
    296 			}
    297 		}
    298 
    299 		public void SetGameserverFlag() {
    300 			m_CCallbackBase.m_nCallbackFlags |= CCallbackBase.k_ECallbackFlagsGameServer;
    301 		}
    302 
    303 		// Shouldn't ever get called here, but this is what C++ Steamworks does!
    304 		private void OnRunCallback(
    305 #if !STDCALL
    306 			IntPtr thisptr,
    307 #endif
    308 			IntPtr pvParam) {
    309 			m_hAPICall = SteamAPICall_t.Invalid; // Caller unregisters for us
    310 
    311 			try {
    312 				m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T)), false);
    313 			}
    314 			catch (Exception e) {
    315 				CallbackDispatcher.ExceptionHandler(e);
    316 			}
    317 		}
    318 
    319 		private void OnRunCallResult(
    320 #if !STDCALL
    321 			IntPtr thisptr,
    322 #endif
    323 			IntPtr pvParam, bool bFailed, ulong hSteamAPICall_) {
    324 			SteamAPICall_t hSteamAPICall = (SteamAPICall_t)hSteamAPICall_;
    325 			if (hSteamAPICall == m_hAPICall) {
    326 				m_hAPICall = SteamAPICall_t.Invalid; // Caller unregisters for us
    327 
    328 				try {
    329 					m_Func((T)Marshal.PtrToStructure(pvParam, typeof(T)), bFailed);
    330 				}
    331 				catch (Exception e) {
    332 					CallbackDispatcher.ExceptionHandler(e);
    333 				}
    334 			}
    335 		}
    336 
    337 		private int OnGetCallbackSizeBytes(
    338 #if !STDCALL
    339 			IntPtr thisptr
    340 #endif
    341 			) {
    342 			return m_size;
    343 		}
    344 
    345 		// Steamworks.NET Specific
    346 		private void BuildCCallbackBase() {
    347 			m_CallbackBaseVTable = new CCallbackBaseVTable() {
    348 				m_RunCallback = OnRunCallback,
    349 				m_RunCallResult = OnRunCallResult,
    350 				m_GetCallbackSizeBytes = OnGetCallbackSizeBytes
    351 			};
    352 			m_pVTable = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(CCallbackBaseVTable)));
    353 			Marshal.StructureToPtr(m_CallbackBaseVTable, m_pVTable, false);
    354 
    355 			m_CCallbackBase = new CCallbackBase() {
    356 				m_vfptr = m_pVTable,
    357 				m_nCallbackFlags = 0,
    358 				m_iCallback = CallbackIdentities.GetCallbackIdentity(typeof(T))
    359 			};
    360 			m_pCCallbackBase = GCHandle.Alloc(m_CCallbackBase, GCHandleType.Pinned);
    361 		}
    362 	}
    363 
    364 	[StructLayout(LayoutKind.Sequential)]
    365 	internal class CCallbackBase {
    366 		public const byte k_ECallbackFlagsRegistered = 0x01;
    367 		public const byte k_ECallbackFlagsGameServer = 0x02;
    368 
    369 		public IntPtr m_vfptr;
    370 		public byte m_nCallbackFlags;
    371 		public int m_iCallback;
    372 	}
    373 
    374 	[StructLayout(LayoutKind.Sequential)]
    375 	internal class CCallbackBaseVTable {
    376 #if STDCALL
    377 		private const CallingConvention cc = CallingConvention.StdCall;
    378 
    379 		[UnmanagedFunctionPointer(cc)]
    380 		public delegate void RunCBDel(IntPtr pvParam);
    381 		[UnmanagedFunctionPointer(cc)]
    382 		public delegate void RunCRDel(IntPtr pvParam, [MarshalAs(UnmanagedType.I1)] bool bIOFailure, ulong hSteamAPICall);
    383 		[UnmanagedFunctionPointer(cc)]
    384 		public delegate int GetCallbackSizeBytesDel();
    385 #else
    386 	#if THISCALL
    387 		private const CallingConvention cc = CallingConvention.ThisCall;
    388 	#else
    389 		private const CallingConvention cc = CallingConvention.Cdecl;
    390 	#endif
    391 
    392 		[UnmanagedFunctionPointer(cc)]
    393 		public delegate void RunCBDel(IntPtr thisptr, IntPtr pvParam);
    394 		[UnmanagedFunctionPointer(cc)]
    395 		public delegate void RunCRDel(IntPtr thisptr, IntPtr pvParam, [MarshalAs(UnmanagedType.I1)] bool bIOFailure, ulong hSteamAPICall);
    396 		[UnmanagedFunctionPointer(cc)]
    397 		public delegate int GetCallbackSizeBytesDel(IntPtr thisptr);
    398 #endif
    399 
    400 		// RunCallback and RunCallResult are swapped in MSVC ABI
    401 #if WINDOWS_BUILD
    402 		[NonSerialized]
    403 		[MarshalAs(UnmanagedType.FunctionPtr)]
    404 		public RunCRDel m_RunCallResult;
    405 #endif
    406 		[NonSerialized]
    407 		[MarshalAs(UnmanagedType.FunctionPtr)]
    408 		public RunCBDel m_RunCallback;
    409 #if !WINDOWS_BUILD
    410 		[NonSerialized]
    411 		[MarshalAs(UnmanagedType.FunctionPtr)]
    412 		public RunCRDel m_RunCallResult;
    413 #endif
    414 		[NonSerialized]
    415 		[MarshalAs(UnmanagedType.FunctionPtr)]
    416 		public GetCallbackSizeBytesDel m_GetCallbackSizeBytes;
    417 	}
    418 }
    419 
    420 #endif // !DISABLESTEAMWORKS