using System; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using UnityEngine; using UnityEngine.Events; public class Uniquake: MonoBehaviour { private const string DllName = "uniquake.dll"; private const int MemSize = 0x4000000; // 64 MB of heap space private const int MaxArgs = 50; // Corresponds with MAX_NUM_ARGVS in engine private QuakeParms quakeParms; private UnityCallbacks callbacks = new UnityCallbacks(); void Start() { string[] arguments = { "", "-window", "-width", "1440", "-height", "1080", }; IntPtr[] argv = new IntPtr[MaxArgs]; for (int i = 0; i < arguments.Length && i < MaxArgs; ++i) { argv[i] = Marshal.StringToHGlobalAnsi(arguments[i]); } quakeParms = new QuakeParms { baseDir = Application.persistentDataPath, cacheDir = null, argc = arguments.Length, argv = argv, memBase = Marshal.AllocHGlobal(MemSize), memSize = MemSize, }; Uniquake_Init(callbacks.CallbacksPtr, quakeParms); } void Update() { Uniquake_Update(Time.deltaTime); } private void OnDestroy() { callbacks.Destroy(); if (quakeParms != null) { if (quakeParms.memBase != IntPtr.Zero) { Marshal.FreeHGlobal(quakeParms.memBase); quakeParms.memBase = IntPtr.Zero; } if (quakeParms.argv != null) { for (int i = 0; i < quakeParms.argv.Length; ++i) { if (quakeParms.argv[i] != IntPtr.Zero) { Marshal.FreeHGlobal(quakeParms.argv[i]); quakeParms.argv[i] = IntPtr.Zero; } } } } } [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] private static extern void Uniquake_Echo(UnityCallbacks.DebugLogCallback logCallback, [MarshalAs(UnmanagedType.LPStr)] string message); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] private static extern void Uniquake_Init(IntPtr callbacks, QuakeParms parms); [DllImport(DllName, CallingConvention = CallingConvention.Cdecl)] private static extern void Uniquake_Update(float deltaTime); [AOT.MonoPInvokeCallback(typeof(UnityCallbacks.DebugLogCallback))] private static void Callback_DebugLog(string message) { Debug.Log(message); } [AOT.MonoPInvokeCallback(typeof(UnityCallbacks.DebugLogErrorCallback))] private static void Callback_DebugLogError(string message) { Debug.LogError(message); } [AOT.MonoPInvokeCallback(typeof(UnityCallbacks.ApplicationQuitCallback))] private static void Callback_ApplicationQuit(int exitCode) { Debug.Log($"Quitting application with exit code: {exitCode}"); // TODO: kill execution of the DLL entirely (Sys_Quit expects immediate exit, which Application.Quit doesn't do) Application.Quit(exitCode); } [AOT.MonoPInvokeCallback(typeof(UnityCallbacks.RealtimeSinceStartupCallback))] private static double Callback_RealtimeSinceStartup() { return Time.realtimeSinceStartupAsDouble; } private class UnityCallbacks { private UnityCallbacksContainer container; private GCHandle containerHandle; public UnityCallbacks() { container = new UnityCallbacksContainer(); CreateCallback(Callback_DebugLog, out DebugLogHandle, out container.DebugLogPtr); CreateCallback(Callback_DebugLogError, out DebugLogErrorHandle, out container.DebugLogErrorPtr); CreateCallback(Callback_ApplicationQuit, out ApplicationQuitHandle, out container.ApplicationQuitPtr); CreateCallback(Callback_RealtimeSinceStartup, out RealtimeSinceStartupHandle, out container.RealtimeSinceStartupPtr); containerHandle = GCHandle.Alloc(container, GCHandleType.Pinned); } public IntPtr CallbacksPtr => containerHandle.AddrOfPinnedObject(); public void Destroy() { containerHandle.Free(); DebugLogHandle.Free(); DebugLogErrorHandle.Free(); ApplicationQuitHandle.Free(); RealtimeSinceStartupHandle.Free(); } private static void CreateCallback(TDelegate callback, out GCHandle handle, out IntPtr ptr) { handle = GCHandle.Alloc(callback); ptr = Marshal.GetFunctionPointerForDelegate(callback); } public delegate void DebugLogCallback([MarshalAs(UnmanagedType.LPStr)] string message); private GCHandle DebugLogHandle; public delegate void DebugLogErrorCallback([MarshalAs(UnmanagedType.LPStr)] string message); private GCHandle DebugLogErrorHandle; public delegate void ApplicationQuitCallback(int exitCode); private GCHandle ApplicationQuitHandle; public delegate double RealtimeSinceStartupCallback(); private GCHandle RealtimeSinceStartupHandle; } [StructLayout(LayoutKind.Sequential, Pack = 0)] private class UnityCallbacksContainer { public IntPtr DebugLogPtr; public IntPtr DebugLogErrorPtr; public IntPtr ApplicationQuitPtr; public IntPtr RealtimeSinceStartupPtr; } [StructLayout(LayoutKind.Sequential, Pack = 0)] private class QuakeParms { [MarshalAs(UnmanagedType.LPStr)] public string baseDir; [MarshalAs(UnmanagedType.LPStr)] public string cacheDir; public int argc; [MarshalAs(UnmanagedType.LPArray, SizeConst = MaxArgs)] public IntPtr[] argv; public IntPtr memBase; public int memSize; } }