ZoneBuilder/Source/Plugins/VisplaneExplorer/VPOManager.cs

308 lines
8.5 KiB
C#

#region === Copyright (c) 2010 Pascal van der Heiden ===
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
#endregion
namespace CodeImp.DoomBuilder.Plugins.VisplaneExplorer
{
internal class VPOManager : IDisposable
{
#region ================== Constants
public const int POINTS_PER_ITERATION = 100;
private const int EXPECTED_RESULTS_BUFFER = 200000;
private readonly int[] TEST_ANGLES = new[] { 0, 90, 180, 270, 45, 135, 225, 315 /*, 22, 67, 112, 157, 202, 247, 292, 337 */ };
private const int TEST_HEIGHT = 41 + 8;
#endregion
#region ================== APIs
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadLibrary(string filename);
[DllImport("kernel32.dll")]
private static extern IntPtr GetProcAddress(IntPtr modulehandle, string procedurename);
[DllImport("kernel32.dll")]
private static extern bool FreeLibrary(IntPtr modulehandle);
#endregion
#region ================== Delegates
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate string VPO_GetError();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int VPO_LoadWAD(string filename);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int VPO_OpenMap(string mapname, ref bool isHexen);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void VPO_FreeWAD();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate void VPO_CloseMap();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)] //mxd
private delegate void VPO_OpenDoorSectors(int dir);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate int VPO_TestSpot(int x, int y, int dz, int angle,
ref int visplanes, ref int drawsegs, ref int openings, ref int solidsegs);
#endregion
#region ================== Variables
// Main objects
private readonly string[] tempfiles;
private IntPtr[] dlls;
private Thread[] threads;
// Map to load
private string filename;
private string mapname;
// Input and output queue (both require a lock on 'points' !)
private readonly Queue<TilePoint> points = new Queue<TilePoint>(EXPECTED_RESULTS_BUFFER);
private readonly Queue<PointData> results = new Queue<PointData>(EXPECTED_RESULTS_BUFFER);
#endregion
#region ================== Properties
public int NumThreads { get { return Environment.ProcessorCount; } }
#endregion
#region ================== Constructor / Destructor
// Constructor
public VPOManager()
{
// Load a DLL for each thread
dlls = new IntPtr[NumThreads];
tempfiles = new string[NumThreads];
// We must write the DLL file with a unique name for every thread,
// because LoadLibrary will share loaded libraries with the same
// names and LoadLibraryEx does not have a flag to force loading
// it multiple times.
Assembly thisasm = Assembly.GetExecutingAssembly();
Stream dllstream = thisasm.GetManifestResourceStream("CodeImp.DoomBuilder.Plugins.VisplaneExplorer.Resources.vpo.dll");
dllstream.Seek(0, SeekOrigin.Begin);
byte[] dllbytes = new byte[dllstream.Length];
dllstream.Read(dllbytes, 0, dllbytes.Length);
for(int i = 0; i < dlls.Length; i++)
{
// Write file with unique filename
tempfiles[i] = BuilderPlug.MakeTempFilename(".dll");
File.WriteAllBytes(tempfiles[i], dllbytes);
// Load it
dlls[i] = LoadLibrary(tempfiles[i]);
if(dlls[i] == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error(); //mxd
throw new Exception("Error " + error + " while loading vpo.dll: " + new Win32Exception(error).Message);
}
}
}
// Disposer
public void Dispose()
{
if(threads != null) Stop();
if(dlls != null)
{
for(int i = 0; i < dlls.Length; i++)
{
FreeLibrary(dlls[i]);
File.Delete(tempfiles[i]);
}
dlls = null;
}
}
#endregion
#region ================== Processing
// The thread!
private void ProcessingThread(object index)
{
// Get function pointers
//VPO_GetError GetError = (VPO_GetError)Marshal.GetDelegateForFunctionPointer(GetProcAddress(dlls[(int)index], "VPO_GetError"), typeof(VPO_GetError));
VPO_LoadWAD LoadWAD = (VPO_LoadWAD)Marshal.GetDelegateForFunctionPointer(GetProcAddress(dlls[(int)index], "VPO_LoadWAD"), typeof(VPO_LoadWAD));
VPO_OpenMap OpenMap = (VPO_OpenMap)Marshal.GetDelegateForFunctionPointer(GetProcAddress(dlls[(int)index], "VPO_OpenMap"), typeof(VPO_OpenMap));
VPO_FreeWAD FreeWAD = (VPO_FreeWAD)Marshal.GetDelegateForFunctionPointer(GetProcAddress(dlls[(int)index], "VPO_FreeWAD"), typeof(VPO_FreeWAD));
VPO_CloseMap CloseMap = (VPO_CloseMap)Marshal.GetDelegateForFunctionPointer(GetProcAddress(dlls[(int)index], "VPO_CloseMap"), typeof(VPO_CloseMap));
VPO_OpenDoorSectors OpenDoors = (VPO_OpenDoorSectors)Marshal.GetDelegateForFunctionPointer(GetProcAddress(dlls[(int)index], "VPO_OpenDoorSectors"), typeof(VPO_OpenDoorSectors)); //mxd
VPO_TestSpot TestSpot = (VPO_TestSpot)Marshal.GetDelegateForFunctionPointer(GetProcAddress(dlls[(int)index], "VPO_TestSpot"), typeof(VPO_TestSpot));
try
{
// Load the map
bool isHexen = General.Map.HEXEN;
if(LoadWAD(filename) != 0) throw new Exception("VPO is unable to read this file.");
if(OpenMap(mapname, ref isHexen) != 0) throw new Exception("VPO is unable to open this map.");
OpenDoors(BuilderPlug.InterfaceForm.OpenDoors ? 1 : -1); //mxd
// Processing
Queue<TilePoint> todo = new Queue<TilePoint>(POINTS_PER_ITERATION);
Queue<PointData> done = new Queue<PointData>(POINTS_PER_ITERATION);
while(true)
{
lock(points)
{
// Flush done points to the results
int numdone = done.Count;
for(int i = 0; i < numdone; i++)
results.Enqueue(done.Dequeue());
// Get points from the waiting queue into my todo queue for processing
int numtodo = Math.Min(POINTS_PER_ITERATION, points.Count);
for(int i = 0; i < numtodo; i++)
todo.Enqueue(points.Dequeue());
}
// Don't keep locking!
if(todo.Count == 0)
Thread.Sleep(31);
// Process the points
while(todo.Count > 0)
{
TilePoint p = todo.Dequeue();
PointData pd = new PointData();
pd.point = p;
for(int i = 0; i < TEST_ANGLES.Length; i++)
{
pd.result = (PointResult)TestSpot(p.x, p.y, TEST_HEIGHT, TEST_ANGLES[i],
ref pd.visplanes, ref pd.drawsegs, ref pd.openings, ref pd.solidsegs);
}
done.Enqueue(pd);
}
}
}
catch(ThreadInterruptedException)
{
}
finally
{
CloseMap();
FreeWAD();
}
}
#endregion
#region ================== Public Methods
// This loads a map
public void Start(string filename, string mapname)
{
this.filename = filename;
this.mapname = mapname;
results.Clear();
// Start a thread on each core
threads = new Thread[dlls.Length];
for(int i = 0; i < threads.Length; i++)
{
threads[i] = new Thread(ProcessingThread);
threads[i].Priority = ThreadPriority.BelowNormal;
threads[i].Name = "Visplane Explorer " + i;
threads[i].Start(i);
}
}
// This frees the map
public void Stop()
{
if(threads != null)
{
lock(points)
{
// Stop all threads
for(int i = 0; i < threads.Length; i++)
{
threads[i].Interrupt();
threads[i].Join();
}
threads = null;
points.Clear();
results.Clear();
}
}
}
//mxd
internal void Restart()
{
Stop();
Start(filename, mapname);
}
// This clears the list of enqueued points
public void ClearPoints()
{
lock(points)
{
points.Clear();
}
}
// This gives points to process and returns the total points left in the buffer
public int EnqueuePoints(IEnumerable<TilePoint> newpoints)
{
lock(points)
{
foreach(TilePoint p in newpoints)
points.Enqueue(p);
return points.Count;
}
}
// This fetches results (in 'data') and returns the number of points
// remaining to be processed.
public int DequeueResults(List<PointData> data)
{
lock(points)
{
int numresults = results.Count;
if(data.Capacity - data.Count < numresults)
data.Capacity = data.Count + numresults;
for(int i = 0; i < numresults; i++)
data.Add(results.Dequeue());
return points.Count;
}
}
// This returns the number of points left in the buffer
public int GetRemainingPoints()
{
lock(points)
{
return points.Count;
}
}
#endregion
}
}