/// <summary> /// Read from the queue into the consumer callable. /// /// <paramref name="consumer"/> is expected to return the number of bytes /// consumed, sans the header. /// </summary> /// <param name="consumer"></param> public int Read(Func <string, InteropMessageHeader, IntPtr, int> consumer) { return(messageConsumer.Read((ptr) => { int bytesRead = 0; // Read target name (varying length string) int targetSize = FastStructure.PtrToStructure <int>(ptr + bytesRead); bytesRead += FastStructure.SizeOf <int>(); string targetName = ""; if (targetSize > 0) { byte[] target = new byte[targetSize]; FastStructure.ReadBytes(target, ptr + bytesRead, 0, targetSize); targetName = Encoding.UTF8.GetString(target); bytesRead += targetSize; } // Read message header var headerSize = FastStructure.SizeOf <InteropMessageHeader>(); var header = FastStructure.PtrToStructure <InteropMessageHeader>(ptr + bytesRead); bytesRead += headerSize; // Call consumer to handle the rest of the payload bytesRead += consumer(targetName, header, ptr + bytesRead); // InteropLogger.Debug($"Consume {bytesRead} bytes - {header.type} for `{targetName}`"); return bytesRead; }, 0)); }
internal unsafe void ReadArray <T>(long position, T[] buffer, int index, int count) where T : struct { uint elementSize = (uint)FastStructure.SizeOf <T>(); if (buffer == null) { throw new ArgumentNullException("buffer"); } if (position > this._view.Size - (elementSize * count)) { throw new ArgumentOutOfRangeException("position"); } try { byte *ptr = null; _view.SafeMemoryMappedViewHandle.AcquirePointer(ref ptr); ptr += _view.ViewStartOffset + position; FastStructure.ReadArray <T>(buffer, (IntPtr)ptr, index, count); //for (var i = 0; i < count; i++) //{ // PtrToStructure(ptr + (i * elementSize), out buffer[index + i]); //} } finally { _view.SafeMemoryMappedViewHandle.ReleasePointer(); } }
/// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="type"></param> /// <param name="target"></param> /// <param name="buffer"></param> public void QueueArray <T>(RpcRequest type, string target, IArray <T> buffer) where T : struct { var headerSize = FastStructure.SizeOf <InteropMessageHeader>(); var elementSize = FastStructure.SizeOf <T>(); if (headerSize + elementSize * buffer.Length > messageProducer.NodeBufferSize) { throw new Exception($"Cannot queue {buffer.Length} elements of {typeof(T)} - will not fit in a single message"); } // Construct a header with metadata for the array var header = new InteropMessageHeader { type = type, length = buffer.MaxLength, index = buffer.Offset, count = buffer.Length }; // Remove any queued messages with the same outbound header RemoveQueuedMessage(target, ref header); InteropLogger.Debug($" QA-> {target}:{type:F}"); outboundQueue.Enqueue(new InteropMessage { target = target, header = header, producer = (tar, hdr, ptr) => { buffer.CopyTo(ptr, 0, buffer.Length); return(elementSize * buffer.Length); } }); }
public void ReadWrite_MyTestStruct_DataMatches() { string name = Guid.NewGuid().ToString(); int nodeSize = Marshal.SizeOf(typeof(MyTestStruct)); using (var smr = new CircularBuffer(name, 2, nodeSize)) using (var sm2 = new CircularBuffer(name)) { MyTestStruct obj = new MyTestStruct { Prop1 = 1, Prop2 = 2, Prop3 = 3, Prop4 = 4 }; smr.Write(ref obj); MyTestStruct read; int bytesRead = sm2.Read(out read); if (bytesRead > 0) { Assert.AreEqual(FastStructure.SizeOf <MyTestStruct>(), bytesRead); Assert.AreEqual(obj, read); } else { Assert.Fail(); } } }
/// <summary> /// Queue an outbound message containing one or more <typeparamref name="T"/> values. /// /// <para> /// If we cannot fit the entire dataset into a single message, and /// <paramref name="allowSplitMessages"/> is true then the payload will /// be split into multiple messages, each with a distinct /// <see cref="InteropMessageHeader.index"/> and <see cref="InteropMessageHeader.count"/> /// range. /// </para> /// </summary> /// <typeparam name="T"></typeparam> /// <param name="type"></param> /// <param name="target"></param> /// <param name="data"></param> /// <param name="allowSplitMessages"></param> public bool ReplaceOrQueueArray <T>(RpcRequest type, string target, T[] data, bool allowSplitMessages) where T : struct { var headerSize = FastStructure.SizeOf <InteropMessageHeader>(); var elementSize = FastStructure.SizeOf <T>(); // TODO: Splitting. Right now assume fit or fail. if (headerSize + elementSize * data.Length > messageProducer.NodeBufferSize) { throw new Exception($"Cannot queue {data.Length} elements of {typeof(T)} - will not fit in a single message"); } var header = new InteropMessageHeader { type = type, index = 0, count = data.Length }; // TODO: If the source array size changes - find queued won't be correct. // We assume ReplaceOrQueue because of the below TODO - multiple queued arrays // would be pointing to the same data anyway. // If it's already queued, we don't need to do anything. var queued = FindQueuedMessage(target, ref header); if (queued != null) { return(true); } outboundQueue.Enqueue(new InteropMessage { target = target, header = header, producer = (tar, hdr, ptr) => { if (hdr.count < 1 || hdr.index + hdr.count > data.Length) { throw new Exception($"Producer out of range of dataset - {hdr.type} - {tar}"); } // TODO: My concern here would be what happens if the buffer changes before this is sent? // This would send the updated buffer - BUT that probably wouldn't be a problem because // we're trying to send the most recent data at all times anyway, right? // Even if it's sitting in queue for a while. // Also seems like this should be an implicit QueueOrReplace - because if multiple // queued messsages point to the same array - they're going to send the same array data. // Could leave this up to the QueueArray caller - passing in this Func<...> // and we're just responsible for adjusting the header to the ranges that fit. FastStructure.WriteArray(ptr, data, hdr.index, hdr.count); return(elementSize * hdr.count); } }); return(false); }
private int OnConnect(string target, IntPtr ptr) { unityState = FastStructure.PtrToStructure <InteropUnityState>(ptr); IsConnectedToUnity = true; InteropLogger.Debug($"{target} - {unityState.version} connected. Flavor Blasting."); SendAllSceneData(); return(FastStructure.SizeOf <InteropUnityState>()); }
/// <summary> /// Copy the RenderTexture data from the ViewportController into shared memory with Blender. /// /// <para> /// The <paramref name="pixelsRGB24Func"/> callback is executed IFF we have room in the /// buffer to write - letting us skip the heavy pixel copy operations if the consumer /// is backed up in processing data. /// </para> /// </summary> internal void PublishRenderTexture(ViewportController viewport, Func <byte[]> pixelsRGB24Func) { if (!IsConnected) { Debug.LogWarning("Cannot send RT - No connection"); } Profiler.BeginSample("Write wait on pixelsProducer"); int bytesWritten = pixelsProducer.Write((ptr) => { // If we have a node we can write on, actually do the heavy lifting // of pulling the pixel data from the RenderTexture (in the callback) // and write into the buffer. var pixelsRGB24 = pixelsRGB24Func(); Profiler.BeginSample("Write Pixels into Shared Memory"); // Pack a header into shared memory var header = new InteropRenderHeader { viewportId = viewport.ID, width = viewport.Width, height = viewport.Height }; var headerSize = FastStructure.SizeOf <InteropRenderHeader>(); FastStructure.WriteBytes(ptr, FastStructure.ToBytes(ref header), 0, headerSize); // Copy render image data into shared memory FastStructure.WriteBytes(ptr + headerSize, pixelsRGB24, 0, pixelsRGB24.Length); /*InteropLogger.Debug($"Writing {pixelsRGB24.Length} bytes with meta {header.width} x {header.height} and pix 0 is " + * $"{pixelsRGB24[0]}, {pixelsRGB24[1]}, {pixelsRGB24[2]}" * );*/ Profiler.EndSample(); return(headerSize + pixelsRGB24.Length); }, WRITE_WAIT); /* * if (bytesWritten < 1) * { * Debug.LogWarning("pixelsProducer buffer is backed up. Skipped write."); * }*/ Profiler.EndSample(); }
/// <summary> /// Estimate how much of shared memory will be allocated between Unity and Blender given current settings /// </summary> public string CalculateSharedMemoryUsage() { var bufferHeaderSize = FastStructure.SizeOf <SharedHeader>(); var messageBufferSize = nodeSize * nodeCount + bufferHeaderSize; var pixelsBufferSize = pixelsNodeCount * PixelsNodeSizeBytes + bufferHeaderSize; var expectedSharedMemorySize = (messageBufferSize + pixelsBufferSize) / 1024.0 / 1024.0; var units = "MB"; if (expectedSharedMemorySize > 1024) { expectedSharedMemorySize /= 1024.0; units = "GB"; } return($"{expectedSharedMemorySize:F2} {units}"); }
public void CanAllocHGlobalReadWrite() { IntPtr mem = Marshal.AllocHGlobal(FastStructure.SizeOf <ComplexStructure>()); ComplexStructure n = new ComplexStructure(); n.Compatible.Integer1 = 1; n.Compatible.Bookend = 2; n.FirstElement = 3; n.FinalElement = 9; unsafe { n.Compatible.Contents[0] = 4; n.Compatible.Contents[7] = 5; } FastStructure.StructureToPtr(ref n, mem); // Assert that the reading and writing result in same structure ComplexStructure m = FastStructure.PtrToStructure <ComplexStructure>(mem); Assert.Equal(n, m); Assert.Equal(n.Compatible.Integer1, m.Compatible.Integer1); Assert.Equal(n.Compatible.Bookend, m.Compatible.Bookend); unsafe { Assert.Equal(n.Compatible.Contents[0], m.Compatible.Contents[0]); Assert.Equal(n.Compatible.Contents[7], m.Compatible.Contents[7]); } // Assert that Marshal.PtrToStructure is compatible m = (ComplexStructure)Marshal.PtrToStructure(mem, typeof(ComplexStructure)); Assert.Equal(n, m); Assert.Equal(n.Compatible.Integer1, m.Compatible.Integer1); Assert.Equal(n.Compatible.Bookend, m.Compatible.Bookend); unsafe { Assert.Equal(n.Compatible.Contents[0], m.Compatible.Contents[0]); Assert.Equal(n.Compatible.Contents[7], m.Compatible.Contents[7]); } Marshal.FreeHGlobal(mem); }
/// <summary> /// Write <paramref name="message"/> into the next available /// node's buffer at <paramref name="ptr"/>. /// </summary> /// <param name="message"></param> /// <param name="ptr"></param> /// <returns></returns> private int WriteMessage(InteropMessage message, IntPtr ptr) { int bytesWritten = 0; // Write the target name (varying length string) byte[] target = Encoding.UTF8.GetBytes(message.target); int targetLen = target.Length; FastStructure.StructureToPtr(ref targetLen, ptr + bytesWritten); bytesWritten += FastStructure.SizeOf <int>(); if (targetLen > 0) { FastStructure.WriteBytes(ptr + bytesWritten, target, 0, targetLen); bytesWritten += targetLen; } // Write the message header var headerSize = FastStructure.SizeOf <InteropMessageHeader>(); var header = message.header; FastStructure.StructureToPtr(ref header, ptr + bytesWritten); bytesWritten += headerSize; // If there's a custom producer, execute it for writing the payload if (message.producer != null) { bytesWritten += message.producer(message.target, header, ptr + bytesWritten); } // If there's a payload included with the message, copy it if (message.payload != null) { FastStructure.WriteBytes(ptr + bytesWritten, message.payload, 0, message.payload.Length); bytesWritten += message.payload.Length; } // InteropLogger.Debug($"Produce {bytesWritten} bytes - {header.type} for `{message.target}`"); return(bytesWritten); }
/// <summary> /// Read from the viewport image buffer and copy /// pixel data into the appropriate viewport. /// </summary> internal void ConsumePixels() { if (pixelsConsumer == null || pixelsConsumer.ShuttingDown) { return; } pixelsConsumer.Read((ptr) => { var headerSize = FastStructure.SizeOf <InteropRenderHeader>(); var header = FastStructure.PtrToStructure <InteropRenderHeader>(ptr); if (!viewports.ContainsKey(header.viewportId)) { InteropLogger.Warning($"Got render texture for unknown viewport {header.viewportId}"); return(headerSize); } var viewport = viewports[header.viewportId]; var pixelDataSize = viewport.ReadPixelData(header, ptr + headerSize); return(headerSize + pixelDataSize); }, READ_WAIT); }
private int OnUpdateUnityState(string target, IntPtr ptr) { unityState = FastStructure.PtrToStructure <InteropUnityState>(ptr); return(FastStructure.SizeOf <InteropUnityState>()); }
private void DrawAdvanced() { EditorGUILayout.LabelField("Shared Memory Buffer Settings", EditorStyles.boldLabel); EditorGUI.BeginDisabledGroup(Sync.IsSetup); Settings.bufferName = EditorGUILayout.TextField("Buffer Name", Settings.bufferName); Settings.nodeCount = EditorGUILayout.IntSlider( "Node Count", Settings.nodeCount, MIN_NODE_COUNT, MAX_NODE_COUNT ); Settings.nodeSize = EditorGUILayout.IntPopup( "Node Size", Settings.nodeSize, new string[] { "1 MB", "2 MB", "4 MB", "8 MB", "16 MB", "32 MB", "64 MB" }, new int[] { 1, 2, 4, 8, 16, 32, 64 } ); Settings.pixelsNodeCount = EditorGUILayout.IntSlider( "Pixels Node Count", Settings.pixelsNodeCount, MIN_NODE_COUNT, MAX_NODE_COUNT ); Settings.maxViewportWidth = EditorGUILayout.IntField( "Max Viewport Width", Settings.maxViewportWidth ); Settings.maxViewportHeight = EditorGUILayout.IntField( "Max Viewport Height", Settings.maxViewportHeight ); var bufferHeaderSize = FastStructure.SizeOf <SharedHeader>(); var messageBufferSize = Settings.NodeSizeBytes * Settings.nodeCount + bufferHeaderSize; var pixelsBufferSize = Settings.pixelsNodeCount * Settings.PixelsNodeSizeBytes + bufferHeaderSize; var expectedSharedMemorySize = (messageBufferSize + pixelsBufferSize) / 1024.0 / 1024.0; var units = "MB"; if (expectedSharedMemorySize > 1024) { expectedSharedMemorySize /= 1024.0; units = "GB"; } EditorGUI.EndDisabledGroup(); EditorGUILayout.HelpBox( $"{expectedSharedMemorySize:F2} {units} of shared memory will be used between Unity and Blender", MessageType.Info ); if (Sync.IsSetup) { EditorGUILayout.HelpBox( "The above settings cannot be modified while Coherence is running", MessageType.Warning ); } }