You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

186 lines
5.7 KiB

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using UnityEngine;
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(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);
private delegate void DebugLogCallback([MarshalAs(UnmanagedType.LPStr)] string message);
[AOT.MonoPInvokeCallback(typeof(DebugLogCallback))]
private static void Callback_DebugLog(string message)
{
Debug.Log(message);
}
private delegate void DebugLogErrorCallback([MarshalAs(UnmanagedType.LPStr)] string message);
[AOT.MonoPInvokeCallback(typeof(DebugLogErrorCallback))]
private static void Callback_DebugLogError(string message)
{
Debug.LogError(message);
}
private delegate void ApplicationQuitCallback(int exitCode);
[AOT.MonoPInvokeCallback(typeof(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);
}
private delegate double RealtimeSinceStartupCallback();
[AOT.MonoPInvokeCallback(typeof(RealtimeSinceStartupCallback))]
private static double Callback_RealtimeSinceStartup()
{
return Time.realtimeSinceStartupAsDouble;
}
private class UnityCallbacks
{
private List<GCHandle> handles = new List<GCHandle>();
private UnityCallbacksContainer container;
private GCHandle containerHandle;
public UnityCallbacks()
{
container = new UnityCallbacksContainer
{
DebugLogPtr = CreateCallback<DebugLogCallback>(Callback_DebugLog),
DebugLogErrorPtr = CreateCallback<DebugLogErrorCallback>(Callback_DebugLogError),
ApplicationQuitPtr = CreateCallback<ApplicationQuitCallback>(Callback_ApplicationQuit),
RealtimeSinceStartupPtr = CreateCallback<RealtimeSinceStartupCallback>(Callback_RealtimeSinceStartup),
};
containerHandle = GCHandle.Alloc(container, GCHandleType.Pinned);
}
public IntPtr CallbacksPtr => containerHandle.AddrOfPinnedObject();
public void Destroy()
{
if (containerHandle.IsAllocated)
containerHandle.Free();
foreach (var handle in handles)
{
if (handle.IsAllocated)
handle.Free();
}
}
private IntPtr CreateCallback<TDelegate>(TDelegate callback)
{
handles.Add(GCHandle.Alloc(callback));
return Marshal.GetFunctionPointerForDelegate(callback);
}
}
[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;
}
}