Esempio n. 1
0
        /// <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));
        }
Esempio n. 2
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();
            }
        }
Esempio n. 3
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);
                }
            });
        }
Esempio n. 4
0
        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();
                    }
                }
        }
Esempio n. 5
0
        /// <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);
        }
Esempio n. 6
0
        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>());
        }
Esempio n. 7
0
        /// <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();
        }
Esempio n. 8
0
        /// <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}");
        }
Esempio n. 9
0
        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);
        }
Esempio n. 10
0
        /// <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);
        }
Esempio n. 11
0
        /// <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);
        }
Esempio n. 12
0
        private int OnUpdateUnityState(string target, IntPtr ptr)
        {
            unityState = FastStructure.PtrToStructure <InteropUnityState>(ptr);

            return(FastStructure.SizeOf <InteropUnityState>());
        }
Esempio n. 13
0
        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
                    );
            }
        }