UltimateZoneBuilder/Source/Plugins/VisplaneExplorer/VPOManager.cs
Magnus Norddahl 8eb522c873 Move vpo native code into BuilderNative as it is easier to manage. The plugins folder doesn't support including native dlls properly anyway.
Fix visplane explorer busy looping when waiting for data and reduce the used core count to 75% of the total available
Made vpo native code thread safe, removing the need for ungodly DLL patching hacks
2020-04-19 15:56:24 +02:00

239 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 */ };
private const int TEST_HEIGHT = 41 + 8;
#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.");
if(VPO_OpenMap(context, mapname, ref isHexen) != 0) throw new Exception("VPO is unable to open this map.");
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, TEST_HEIGHT, 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
}
}