#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 points = new Queue(EXPECTED_RESULTS_BUFFER); private readonly Queue results = new Queue(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 todo = new Queue(POINTS_PER_ITERATION); Queue done = new Queue(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 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 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 } }