Edit: Solved. Accepted the answer because it was more useful for Unity users. However, as you may have suspected, it turns out I didn’t sufficiently test my native code. In particular, after I wrote my own game module, I found a lot of chunks were being indexed inconsistently, leading to attempts to access pointers far beyond their bounds (I once accessed a chunk pointer with the world offset instead of the chunk offset, for example). There was another problem too: apparently, ERROR_ALREADY_EXISTS is different from ERROR_FILE_EXISTS so the chunk files were only being mapped if they didn’t already exist.
Hi,
I have a set of routines in C++ that samples many megabytes of volumetric data from several memory-mapped files on-demand, i.e., whenever it needs to sample or modify a particular point it either generates it or loads it from a file-backed virtual memory. I expose two methods for this: GetVoxel() and SetVoxel(). Then I have another function StartWorld(), that creates a thread, which as long as the process is running updates an efficient curved bsp structure from all the voxel data, from a producer queue of chunks that need to be updated. I have a third function, Raycast(), that performs a software occlusion query or cone visibility approximation using a signed distance to the b-rep. I need to make a native plugin from them on Windows but they crash…only when invoked from Unity.
I have tested all of these in a simple C++11 console app and they work just fine, running all tests without issue on both Windows and Linux. The functions I expose to mono are extern “C” and I can verify it is running them from the dll with sane arguments. But when I try to run the plugin (call StartWorld(), then SetVoxel() a number of times, then RayCast() about 3200 times) in the editor it crashes the entire editor; a similar thing happens in the game build. I get an access violation somewhere trying to access my memory-mapped file. But here’s the annoying thing: this is the exact sequence of calls I make in my C++ tests, which also use two threads and have no issues with concurrency, and my files are being created, and the pointers being returned by MapViewOfFile are valid (I logged the addresses, and it does indeed sample more than 16kb of voxel data before suddenly crashing).
What could be causing this disparity between how my code behaves in C++ and when invoked from mono in Unity?
I have this code in C#:
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class VoxelWorldManager : MonoBehaviour {
[DllImport ("Fiefdom", EntryPoint = "GetVoxel", CallingConvention = CallingConvention.Cdecl)]
public static extern int GetVoxel(int x, int y, int z);//this will create a new memory-mapped file view if the voxel coordinate doesn't exist. The corresponding c++ function is thread-safe.
[DllImport ("Fiefdom", EntryPoint = "SetVoxel", CallingConvention = CallingConvention.Cdecl)]
public static extern void SetVoxel(int x, int y, int z, int voxel);//this will create a new memory-mapped file view if the voxel coordinate doesn't exist. The corresponding c++ function is thread-safe.
[DllImport("Fiefdom", EntryPoint = "LoadMap", CallingConvention = CallingConvention.Cdecl)]
public static extern void LoadMap([MarshalAs(UnmanagedType.LPStr)]string filename);//this simply passes the filename to the c++ code as a parameter. I know it works because files are being created with this name.
[DllImport ("Fiefdom", EntryPoint ="StartWorld", CallingConvention= CallingConvention.Cdecl)]
public static extern void StartWorld();//this spawns a new thread but in C++ when I use it it works.
[DllImport ("Fiefdom", EntryPoint = "Raycast", CallingConvention = CallingConvention.Cdecl)]
private static extern void Raycast([MarshalAs(UnmanagedType.LPArray, SizeConst = 5)] int[] voxel, float rx, float ry, float rz, float ex, float ey, float ez);//my code hasn't got as far as this yet from Unity but in C++ I can raycast as many times as I want, even while the chunks are being updated, critical sections protect from any synchronization issues.
}
Plus some start and update routines, etc…
Start() calls the initialization routines then sets many voxels from a heightmap;
Update() does a large number of very quick raycasts only after Start() has finished.
The same sequence works when I do it in native code.
}