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