mirror of
https://git.do.srb2.org/STJr/UltimateZoneBuilder.git
synced 2024-11-27 06:02:11 +00:00
8eb522c873
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
239 lines
6.5 KiB
C#
Executable file
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
|
|
}
|
|
}
|