CallbackDispatcher.cs 12 KB

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