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.
 
 
 
 
 

216 lines
6.2 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();
private GCHandle selfHandle;
private double startTime;
void Start()
{
startTime = Time.timeAsDouble;
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,
};
selfHandle = GCHandle.Alloc(this);
UniQuake_Init(callbacks.CallbacksPtr, GCHandle.ToIntPtr(selfHandle), quakeParms);
}
void Update()
{
UniQuake_Update(Time.deltaTime);
}
private void OnDestroy()
{
callbacks.Destroy();
selfHandle.Free();
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;
}
}
}
}
}
private void SysPrint(string message)
{
Debug.Log(message);
}
private void SysError(string message)
{
Debug.LogError(message);
// TODO: kill execution of the DLL entirely (Sys_Quit expects immediate exit, which this doesn't do)
Application.Quit(1);
}
private void SysQuit()
{
Debug.Log($"Quitting application normally");
Application.Quit(0);
}
private double SysFloatTime()
{
return Time.timeAsDouble - startTime;
}
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
private static extern void UniQuake_Init(IntPtr callbacks, IntPtr callbackTarget, QuakeParms parms);
[DllImport(DllName, CallingConvention = CallingConvention.Cdecl)]
private static extern void UniQuake_Update(float deltaTime);
private delegate void SysPrintCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string message);
[AOT.MonoPInvokeCallback(typeof(SysPrintCallback))]
private static void Callback_SysPrint(IntPtr target, string message)
{
var self = (UniQuake)GCHandle.FromIntPtr(target).Target;
self.SysPrint(message);
}
private delegate void SysErrorCallback(IntPtr target, [MarshalAs(UnmanagedType.LPStr)] string message);
[AOT.MonoPInvokeCallback(typeof(SysErrorCallback))]
private static void Callback_SysError(IntPtr target, string message)
{
var self = (UniQuake)GCHandle.FromIntPtr(target).Target;
self.SysError(message);
}
private delegate void SysQuitCallback(IntPtr target);
[AOT.MonoPInvokeCallback(typeof(SysQuitCallback))]
private static void Callback_SysQuit(IntPtr target)
{
var self = (UniQuake)GCHandle.FromIntPtr(target).Target;
self.SysQuit();
}
private delegate double SysFloatTimeCallback(IntPtr target);
[AOT.MonoPInvokeCallback(typeof(SysFloatTimeCallback))]
private static double Callback_SysFloatTime(IntPtr target)
{
var self = (UniQuake)GCHandle.FromIntPtr(target).Target;
return self.SysFloatTime();
}
private class UnityCallbacks
{
private List<GCHandle> handles = new List<GCHandle>();
private UnityCallbacksContainer container;
private GCHandle containerHandle;
public UnityCallbacks()
{
container = new UnityCallbacksContainer
{
SysPrint = CreateCallback<SysPrintCallback>(Callback_SysPrint),
SysError = CreateCallback<SysErrorCallback>(Callback_SysError),
SysQuit = CreateCallback<SysQuitCallback>(Callback_SysQuit),
SysFloatTime = CreateCallback<SysFloatTimeCallback>(Callback_SysFloatTime),
};
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 SysPrint;
public IntPtr SysError;
public IntPtr SysQuit;
public IntPtr SysFloatTime;
}
[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;
}
}