mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-12-12 13:12:20 +00:00
300 lines
8.7 KiB
C#
Executable file
300 lines
8.7 KiB
C#
Executable file
#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();
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
}
|