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(); } } }
internal unsafe void WriteArray <T>(long position, T[] buffer, int index, int count) where T : struct { uint elementSize = (uint)Marshal.SizeOf(typeof(T)); 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.WriteArray <T>((IntPtr)ptr, buffer, index, count); //for (var i = 0; i < count; i++) //{ // StructureToPtr(ref buffer[index + i], ptr + (i * elementSize)); //} } finally { _view.SafeMemoryMappedViewHandle.ReleasePointer(); } }
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(); } }
public bool ReplaceOrQueue <T>(RpcRequest type, string target, ref T data) where T : struct { var header = new InteropMessageHeader { type = type, index = 0, count = 0 }; var payload = FastStructure.ToBytes(ref data); // If it's already queued, replace the payload var queued = FindQueuedMessage(target, ref header); if (queued != null) { queued.payload = payload; return(true); } outboundQueue.Enqueue(new InteropMessage { target = target, header = header, payload = payload }); return(false); }
/// <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)); }
/// <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); } }); }
internal static unsafe void PtrToStructure <T>(byte *ptr, out T structure) where T : struct { structure = FastStructure.PtrToStructure <T>((IntPtr)ptr); //var tr = __makeref(structure); //*(IntPtr*)&tr = (IntPtr)ptr; //structure = __refvalue( tr,T); }
/// <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> /// Queue an outbound message containing a <typeparamref name="T"/> payload /// </summary> /// <typeparam name="T"></typeparam> /// <param name="target"></param> /// <param name="type"></param> /// <param name="data"></param> public void Queue <T>(RpcRequest type, string target, ref T data) where T : struct { outboundQueue.Enqueue(new InteropMessage { target = target, header = new InteropMessageHeader { type = type, index = 0, count = 0 }, payload = FastStructure.ToBytes(ref data) }); }
public virtual void SimpleImageCaptureLoop() { using (var consumer = new CircularBuffer(name: "MySharedMemory", nodeCount: 4, nodeBufferSize: ((8294400 + FastStructure <MetaDataStruct> .Size)) * 2)) // Should be large enough to store 2 full hd raw image data + 4 Size of Struct { Stopwatch stopwatch = new Stopwatch(); do { stopwatch.Reset(); stopwatch.Start(); try { consumer.Read(intPtr => { MetaDataStruct metaData = FastStructure.PtrToStructure <MetaDataStruct>(intPtr); if (metaData.length > 0) { byte[] byteArray = new byte[metaData.length]; FastStructure.ReadArray <byte>(byteArray, intPtr, 0, byteArray.Length); using (var bm = byteArray.ToBitmap(metaData.width, metaData.height, metaData.pitch, metaData.format)) { byte[] compressedJpgByteArray = bm.ToByteCompessedArray(); Bitmap bitmap = compressedJpgByteArray.ToBitmap(); pictureBox1.Invoke(new MethodInvoker(delegate() { if (pictureBox1.Image != null) { pictureBox1.Image.Dispose(); } pictureBox1.Image = bitmap; })); } } return(0); }, timeout: MAX_SLEEP_TIME); } catch (TimeoutException exception) { Console.WriteLine(exception); } catch (Exception ex) { Console.WriteLine(ex); } int timeout = (int)(MAX_SLEEP_TIME - stopwatch.ElapsedMilliseconds); Thread.Sleep(timeout >= 0 ? timeout : 0); } while (true); } }
public T this[int index] { get { if (index >= Length || index < 0) { throw new IndexOutOfRangeException(); } int offset = ElementSize * index; return(FastStructure.PtrToStructure <T>( IntPtr.Add(ptr, offset) )); } }
protected void ProcessFrame(int width, int height, int pitch, PixelFormat format, IntPtr pBits) { if (format == PixelFormat.Undefined) { DebugMessage("Unsupported render target format"); return; } if (pBits == IntPtr.Zero) { DebugMessage("No image data"); return; } // Copy the image data from the buffer int size = height * pitch; var data = new byte[size]; try { try { Marshal.Copy(pBits, data, 0, size); producer.Write(intPtr => { MetaDataStruct metaData = new MetaDataStruct(data.Length, width, height, pitch, format); FastStructure.StructureToPtr(ref metaData, intPtr); intPtr += FastStructure <MetaDataStruct> .Size; FastStructure.WriteArray <byte>(intPtr, data, 0, data.Length); return(0); }, 0); // skip frame if we cannot write to circular buffer immediately } catch (TimeoutException) { // If we could not acquire write lock skip frame } catch (AccessViolationException) { // In this specifc case we are ignoring CSE (corrupted state exception) // It could happen during Window resizing. // If someone knows are better way please feel free to contribute // Because there is a timout in the resizing hook this exception should never be thrown } } catch (ObjectDisposedException) { // swallow exception to not crash hooked process } }
/// <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> /// Fill the buffer from the source memory location and mark dirty /// </summary> /// <param name="ptr">The source memory location</param> /// <param name="index">The start index within this buffer</param> /// <param name="count">The number of elements to read</param> public void CopyFrom(IntPtr src, int index, int count) { if (Offset > 0) { throw new NotImplementedException( "Cannot CopyFrom into a subarray" ); } if (index + count > Length) { throw new OverflowException( $"index({index}) + count({count}) is larger than Length({Length})" ); } FastStructure.ReadArray(data, src, index, count); Dirty(index, index + count - 1); }
/// <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); }
public bool ReplaceOrQueue <T>(RpcRequest type, string target, ref T data) where T : struct { var header = new InteropMessageHeader { type = type, index = 0, length = 0, count = 0 }; var payload = FastStructure.ToBytes(ref data); // If it's already queued, replace the payload /*var queued = FindQueuedMessage(target, ref header); * if (queued != null) * { * queued.payload = payload; * return true; * }*/ // Remove the old one to then queue up one at the end // This ensures messages that are queued up together // remain in their queued order. RemoveQueuedMessage(target, ref header); InteropLogger.Debug($" ROQ-> {target}:{header.type:F}"); outboundQueue.Enqueue(new InteropMessage { target = target, header = header, payload = payload }); return(false); }
private int OnUpdateUnityState(string target, IntPtr ptr) { unityState = FastStructure.PtrToStructure <InteropUnityState>(ptr); return(FastStructure.SizeOf <InteropUnityState>()); }
/// <summary> /// Handle any messages coming from Blender /// </summary> private void ConsumeMessages() { Profiler.BeginSample("Consume Message"); var disconnected = false; // TODO: Some messages should be skipped if !IsConnected. // Otherwise we may get a bad state. E.g. we see a disconnect // from Blender and THEN some other viewport/object data messages. messages.Read((target, header, ptr) => { ObjectController obj; switch (header.type) { case RpcRequest.Connect: blenderState = FastStructure.PtrToStructure <InteropBlenderState>(ptr); OnConnectToBlender(); break; case RpcRequest.UpdateBlenderState: blenderState = FastStructure.PtrToStructure <InteropBlenderState>(ptr); break; case RpcRequest.Disconnect: // Don't call OnDisconnectFromBlender() from within // the read handler - we want to release the read node // safely first before disposing the connection. disconnected = true; break; case RpcRequest.AddViewport: AddViewport( target, FastStructure.PtrToStructure <InteropViewport>(ptr) ); break; case RpcRequest.RemoveViewport: RemoveViewport(target); break; case RpcRequest.UpdateViewport: GetViewport(target).UpdateFromInterop( FastStructure.PtrToStructure <InteropViewport>(ptr) ); break; case RpcRequest.UpdateVisibleObjects: var visibleObjectIds = new int[header.count]; FastStructure.ReadArray(visibleObjectIds, ptr, 0, header.count); GetViewport(target).SetVisibleObjects( visibleObjectIds ); break; case RpcRequest.AddObjectToScene: AddObject( target, FastStructure.PtrToStructure <InteropSceneObject>(ptr) ); break; case RpcRequest.RemoveObjectFromScene: RemoveObject(target); break; case RpcRequest.UpdateSceneObject: GetObject(target).UpdateFromInterop( FastStructure.PtrToStructure <InteropSceneObject>(ptr) ); break; case RpcRequest.UpdateTriangles: obj = GetObject(target); FastStructure.ReadArray(obj.GetOrCreateTriangleBuffer(), ptr, header.index, header.count); obj.OnUpdateTriangleRange(header.index, header.count); break; case RpcRequest.UpdateVertices: obj = GetObject(target); FastStructure.ReadArray(obj.GetOrCreateVertexBuffer(), ptr, header.index, header.count); obj.OnUpdateVertexRange(header.index, header.count); break; case RpcRequest.UpdateNormals: obj = GetObject(target); FastStructure.ReadArray(obj.GetOrCreateNormalBuffer(), ptr, header.index, header.count); obj.OnUpdateNormalRange(header.index, header.count); break; // TODO: ... and so on for UV/weights default: Debug.LogWarning($"Unhandled request type {header.type} for {target}"); break; } // TODO: Necessary to count bytes? We won't read anything off this // buffer at this point so it's safe to drop the whole thing. return(0); }); // Handle any disconnects that may have occured during the read if (disconnected) { OnDisconnectFromBlender(); } Profiler.EndSample(); }
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 ); } }
/// <summary> /// Copy a subset of this buffer to the given memory address. /// /// The current <see cref="Offset"/> will be applied during copy. /// </summary> /// <param name="ptr">The destination memory location</param> /// <param name="index">The start index within this buffer</param> /// <param name="count">The number of elements to write</param> public void CopyTo(IntPtr dst, int index, int count) { FastStructure.WriteArray(dst, data, Offset + index, count); }
internal static unsafe void StructureToPtr <T>(ref T structure, byte *ptr) where T : struct { FastStructure.StructureToPtr <T>(ref structure, (IntPtr)ptr); }
/// <summary> /// Consume a single message off the interop message queue /// </summary> /// <returns>Number of bytes read</returns> private int ConsumeMessage() { var disconnected = false; var bytesRead = messages.Read((target, header, ptr) => { // While not connected - only accept connection requests if (!IsConnected) { if (header.type != RpcRequest.Connect) { Debug.LogWarning($"Unhandled request type {header.type} for {target} - expected RpcRequest.Connect"); } blenderState = FastStructure.PtrToStructure <InteropBlenderState>(ptr); OnConnectToBlender(); return(0); } switch (header.type) { case RpcRequest.UpdateBlenderState: blenderState = FastStructure.PtrToStructure <InteropBlenderState>(ptr); break; case RpcRequest.Disconnect: // Don't call OnDisconnectFromBlender() from within // the read handler - we want to release the read node // safely first before disposing the connection. disconnected = true; break; // Viewport messages case RpcRequest.AddViewport: AddViewport( target, FastStructure.PtrToStructure <InteropViewport>(ptr) ); break; case RpcRequest.RemoveViewport: RemoveViewport(target); break; case RpcRequest.UpdateViewport: GetViewport(target).UpdateFromInterop( FastStructure.PtrToStructure <InteropViewport>(ptr) ); break; case RpcRequest.UpdateVisibleObjects: var visibleObjectIds = new int[header.count]; FastStructure.ReadArray(visibleObjectIds, ptr, 0, header.count); GetViewport(target).SetVisibleObjects( visibleObjectIds ); break; // Object messages case RpcRequest.AddObjectToScene: AddObject( target, FastStructure.PtrToStructure <InteropSceneObject>(ptr) ); break; case RpcRequest.RemoveObjectFromScene: RemoveObject(target); break; case RpcRequest.UpdateSceneObject: UpdateObject( target, FastStructure.PtrToStructure <InteropSceneObject>(ptr) ); break; // Mesh messages case RpcRequest.UpdateTriangles: GetOrCreateMesh(target) .triangles .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; case RpcRequest.UpdateVertices: GetOrCreateMesh(target) .vertices .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; case RpcRequest.UpdateNormals: GetOrCreateMesh(target) .normals .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; case RpcRequest.UpdateUV: GetOrCreateMesh(target) .uv .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; case RpcRequest.UpdateUV2: GetOrCreateMesh(target) .uv2 .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; case RpcRequest.UpdateUV3: GetOrCreateMesh(target) .uv3 .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; case RpcRequest.UpdateUV4: GetOrCreateMesh(target) .uv4 .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; case RpcRequest.UpdateVertexColors: GetOrCreateMesh(target) .colors .Resize(header.length) .CopyFrom(ptr, header.index, header.count); break; // TODO: ... and so on for weights/bones/etc case RpcRequest.UpdateMesh: GetOrCreateMesh(target).UpdateFromInterop( FastStructure.PtrToStructure <InteropMesh>(ptr) ); break; // Texture messages case RpcRequest.UpdateTexture: GetTexture(target).UpdateFromInterop( FastStructure.PtrToStructure <InteropTexture>(ptr) ); break; case RpcRequest.UpdateTextureData: GetTexture(target).CopyFrom( ptr, header.index, header.count, header.length ); break; default: Debug.LogWarning($"Unhandled request type {header.type} for {target}"); break; } // TODO: Necessary to count bytes? We won't read anything off this // buffer at this point so it's safe to drop the whole thing. // bytesRead will count the header size (indicating a message *was* read) return(0); }); // Handle any disconnects that may have occured during the read if (disconnected) { OnDisconnectFromBlender(); } return(bytesRead); }