UltimateZoneBuilder/Source/Plugins/VisplaneExplorer/VPOManager.cs

238 lines
6.5 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 */ };
#endregion
#region ================== VPO bindings
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr VPO_NewContext();
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
private static extern void VPO_DeleteContext(IntPtr handle);
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern string VPO_GetError(IntPtr handle);
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int VPO_LoadWAD(IntPtr handle, string filename);
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
private static extern int VPO_OpenMap(IntPtr handle, string mapname, ref bool isHexen);
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
private static extern void VPO_FreeWAD(IntPtr handle);
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
private static extern void VPO_CloseMap(IntPtr handle);
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
private static extern void VPO_OpenDoorSectors(IntPtr handle, int dir);
[DllImport("BuilderNative", CallingConvention = CallingConvention.Cdecl)]
private static extern int VPO_TestSpot(IntPtr handle, 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 List<Thread> threads = new List<Thread>();
// Map to load
private string filename;
private string mapname;
// Input and output queue and stop flag (all 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);
private bool stopflag;
#endregion
#region ================== Properties
// Use up to 75% of CPU cores available
public int NumThreads { get { return Math.Max((Environment.ProcessorCount * 3 + 2) / 4, 1); } }
#endregion
#region ================== Constructor / Destructor
// Constructor
public VPOManager()
{
}
// Disposer
public void Dispose()
{
Stop();
}
#endregion
#region ================== Processing
// The thread!
private void ProcessingThread()
{
IntPtr context = VPO_NewContext();
// Load the map
bool isHexen = General.Map.HEXEN;
if(VPO_LoadWAD(context, filename) != 0) throw new Exception("VPO is unable to read this file:" + (VPO_GetError(context) ?? "<unknown error>"));
if(VPO_OpenMap(context, mapname, ref isHexen) != 0) throw new Exception("VPO is unable to open this map:" + (VPO_GetError(context) ?? "<unknown error>"));
VPO_OpenDoorSectors(context, 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)
{
// Wait for work
if (points.Count == 0 && !stopflag)
Monitor.Wait(points);
// Flush done points to the results
int numdone = done.Count;
for(int i = 0; i < numdone; i++)
results.Enqueue(done.Dequeue());
if (stopflag)
break;
// 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());
}
// 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)VPO_TestSpot(context, p.x, p.y, BuilderPlug.InterfaceForm.ViewHeight, TEST_ANGLES[i],
ref pd.visplanes, ref pd.drawsegs, ref pd.openings, ref pd.solidsegs);
}
done.Enqueue(pd);
}
}
VPO_CloseMap(context);
VPO_FreeWAD(context);
VPO_DeleteContext(context);
}
#endregion
#region ================== Public Methods
// This loads a map
public void Start(string filename, string mapname)
{
Stop();
this.filename = filename;
this.mapname = mapname;
// Start a thread on each core
for(int i = 0; i < NumThreads; i++)
{
var thread = new Thread(ProcessingThread);
thread.Name = "Visplane Explorer " + i;
thread.Start();
threads.Add(thread);
}
}
// This frees the map
public void Stop()
{
lock (points)
{
stopflag = true;
Monitor.PulseAll(points);
}
foreach (Thread thread in threads)
{
thread.Join();
}
threads.Clear();
lock (points)
{
results.Clear();
points.Clear();
stopflag = false;
}
}
// 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);
Monitor.PulseAll(points);
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
}
}