Example #1
0
        /// <summary>
        /// Copy an <see cref="MLoopTri"/> array into <see cref="Triangles"/>
        ///
        /// <para>
        ///     You <b>must</b> ensure <see cref="cachedLoops"/> is up to date via
        ///     <see cref="CopyFromMVerts(MVert[])"/> before performing this
        ///     operation to ensure that vertices can be mapped to.
        /// </para>
        /// </summary>
        /// <param name="loopTris"></param>
        internal void CopyFromMLoopTris(MLoopTri[] loopTris)
        {
            var count = loopTris.Length * 3;

            if (triangles == null)
            {
                triangles = new uint[count];
            }

            Array.Resize(ref triangles, count);

            // Triangle indices are re-mapped from MLoop vertex index
            // to the source vertex index using cachedLoops.
            for (int i = 0; i < loopTris.Length; i++)
            {
                var loop = loopTris[i];
                var j    = i * 3;

                // TODO: Can this be any faster? This indirect lookup sucks,
                // but might be the best we can do with the way Blender
                // organizes data.
                triangles[j]     = cachedLoops[loop.tri[0]].v;
                triangles[j + 1] = cachedLoops[loop.tri[1]].v;
                triangles[j + 2] = cachedLoops[loop.tri[2]].v;

                // Console.WriteLine($" - {triangles[j]}, {triangles[j + 1]},  {triangles[j + 2]}");
            }

            InteropLogger.Debug($"Copied {triangles.Length} triangle indices");
        }
Example #2
0
        /// <summary>
        /// Process queued messages and write if possible
        /// </summary>
        public void ProcessOutboundQueue()
        {
            if (outboundQueue.Count() < 1)
            {
                return;
            }

            if (outboundQueue.Count() > 10)
            {
                InteropLogger.Warning($"Outbound queue is at {outboundQueue.Count()} messages");
            }

            // Pump the queue until we fill the outbound buffer
            int bytesWritten;

            do
            {
                // Only dequeue a message once we have an available node for writing
                bytesWritten = messageProducer.Write((ptr) =>
                {
                    var next = outboundQueue.Dequeue();
                    InteropLogger.Debug($"    W-> {next.target}:{next.header.type:F}");
                    return(WriteMessage(next, ptr));
                }, 0);
            } while (bytesWritten > 0 && outboundQueue.Count() > 0);
        }
Example #3
0
        internal void Update()
        {
            try
            {
                if (messages != null)
                {
                    messages.ProcessOutboundQueue();
                    ConsumeMessage();
                    CheckForTimeout();
                }
            }
            catch (AccessViolationException)
            {
                // We run into an access violation when Unity disposes the
                // shared memory buffer but Blender is still trying to do IO with it.
                // So we explicitly catch this case and gracefully disconnect.
                // See: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.exceptionservices.handleprocesscorruptedstateexceptionsattribute#remarks

                // TODO: IDEALLY - this should happen closer to the IO with the shared memory buffer,
                // as doing it here might catch access violations elsewhere in the library that weren't
                // intended since this method essentially wraps EVERYTHING.
                // Possibly @ RpcMessenger.Read/Write? Or deeper in CircularBuffer.Read/Write.
                // Actual exception for Read was thrown within CircularBuffer.ReturnNode
                InteropLogger.Error($"Access Violation doing IO to the shared memory - disconnecting");
                Disconnect();
            }
        }
Example #4
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);
                }
            });
        }
Example #5
0
        int RebuildVertices()
        {
            // We resize to the number of vertices from Blender
            // PLUS the number of split vertices we already calculated
            // in a prior pass (thus already have the data filled out)
            vertices.Resize(verts.Length + splitVertices.Count);

            InteropLogger.Debug($"RebuildVertices name={Name}, Length={vertices.Length}");

            for (int i = 0; i < verts.Length; i++)
            {
                var vert = verts[i];

                // y/z are swizzled here to convert to Unity's coordinate space
                vertices[i] = new InteropVector3(
                    vert.co_x,
                    vert.co_z,
                    vert.co_y
                    );
            }

            // Also update all existing split vertices to match
            // the original vertex that may have been updated
            foreach (var kv in splitVertices)
            {
                var vertIndex = (int)loops[kv.Key].v;
                vertices[kv.Value] = vertices[vertIndex];
            }

            // This will never split
            return(0);
        }
Example #6
0
        int RebuildNormals()
        {
            // We assume it always happens AFTER RebuildVertices
            normals.Resize(vertices.Length);

            InteropLogger.Debug($"RebuildNormals name={Name}, Length={normals.Length}");

            // Normals need to be cast from short -> float from Blender
            var normalScale = 1f / 32767f;

            for (int i = 0; i < verts.Length; i++)
            {
                var vert = verts[i];

                // Like vertex coordinates - we swizzle y/z
                normals[i] = new InteropVector3(
                    vert.no_x * normalScale,
                    vert.no_z * normalScale,
                    vert.no_y * normalScale
                    );
            }

            // Also update all existing split vertices to match
            // the original normal that may have been updated
            foreach (var kv in splitVertices)
            {
                var vertIndex = (int)loops[kv.Key].v;
                normals[kv.Value] = normals[vertIndex];
            }

            // For now we have no splits. Eventually this may
            // change if we add support for split normals
            // coming from a CustomData layer
            return(0);
        }
Example #7
0
        /// <summary>
        /// Read pixel data from <paramref name="intPtr"/> into our local buffer
        /// and return the total number of bytes read.
        /// </summary>
        /// <param name="header"></param>
        /// <param name="intPtr"></param>
        /// <returns></returns>
        internal int ReadPixelData(InteropRenderHeader header, IntPtr intPtr)
        {
            var pixelArraySize = header.width * header.height * 3;

            lock (renderTextureLock)
            {
                if (Pixels == IntPtr.Zero)
                {
                    InteropLogger.Debug($"Allocating {header.width} x {header.height}");
                    Pixels = Marshal.AllocHGlobal(pixelArraySize);
                }
                else if (header.width != Header.width || header.height != Header.height)
                {
                    // If the inbound data resized - resize our buffer
                    InteropLogger.Debug($"Reallocating {header.width} x {header.height}");
                    Pixels = Marshal.ReAllocHGlobal(Pixels, new IntPtr(pixelArraySize));
                }

                // Could do a Buffer.MemoryCopy here - but I'm locked to
                // .NET 4.5 due to the DllExport library we're using.
                UnsafeNativeMethods.CopyMemory(Pixels, intPtr, (uint)pixelArraySize);

                Header = header;
                Frame++;
            }

            return(pixelArraySize);
        }
Example #8
0
        public static int SetVisibleObjects(
        #pragma warning disable IDE0060 // Remove unused parameter
            int viewportId,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] int[] visibleObjectIds,
            int totalVisibleObjectIds
        #pragma warning restore IDE0060 // Remove unused parameter
            )
        {
            InteropLogger.Debug($"Set Visible Objects viewport={viewportId}");

            foreach (var i in visibleObjectIds)
            {
                InteropLogger.Debug($" - {i}");
            }

            try
            {
                var viewport = Bridge.GetViewport(viewportId);
                viewport.SetVisibleObjects(visibleObjectIds);

                Bridge.SendArray(
                    RpcRequest.UpdateVisibleObjects,
                    viewport.Name,
                    viewport.VisibleObjectIds,
                    false
                    );
                return(1);
            }
            catch (Exception e)
            {
                SetLastError(e);
                return(-1);
            }
        }
Example #9
0
        public static int CopyLoopTriangles(
        #pragma warning disable IDE0060 // Remove unused parameter
            [MarshalAs(UnmanagedType.LPStr)] string name,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] MLoopTri[] loopTris,
            uint loopTrisCount
        #pragma warning restore IDE0060 // Remove unused parameter
            )
        {
            InteropLogger.Debug($"Copy {loopTris.Length} loop triangles for `{name}`");

            try
            {
                var obj = Bridge.GetObject(name);

                obj.CopyFromMLoopTris(loopTris);

                // If the vertex count changes, we'll need to push a
                // change to the object's metadata
                if (obj.data.triangleCount != obj.Triangles.Length)
                {
                    obj.data.triangleCount = obj.Triangles.Length;
                    Bridge.SendEntity(RpcRequest.UpdateSceneObject, obj);
                }

                Bridge.SendArray(RpcRequest.UpdateTriangles, obj.Name, obj.Triangles, true);

                return(1);
            }
            catch (Exception e)
            {
                SetLastError(e);
                return(-1);
            }
        }
Example #10
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>());
        }
Example #11
0
        internal void SendArray <T>(RpcRequest type, string target, IArray <T> buffer) where T : struct
        {
            if (!IsConnectedToSharedMemory || buffer.Length < 1)
            {
                return;
            }

            InteropLogger.Debug(
                $"SendBuffer target={target} type={type} length={buffer.Length}"
                );

            messages.QueueArray(type, target, buffer);
        }
Example #12
0
        void RebuildAll()
        {
            InteropLogger.Debug($"RebuildAll name={Name}");
            splitVertices.Clear();

            RebuildVertices();
            RebuildNormals();
            RebuildBuffer(loopCols, colors);
            RebuildBuffer(loopUVs, uvs);
            // .. and so on

            RebuildTriangles();
        }
Example #13
0
        public override bool Equals(object obj)
        {
            if (obj is InteropString64 interop)
            {
                return(Value == interop.Value);
            }

            if (obj is string str)
            {
                return(Value == str);
            }

            InteropLogger.Debug($"Diff str {Value} != {obj}");
            return(false);
        }
Example #14
0
        /// <summary>
        /// Consume a single message off the read queue.
        /// </summary>
        private void ConsumeMessage()
        {
            messages.Read((target, header, ptr) =>
            {
                lastUpdateFromUnity = DateTime.Now;

                if (!handlers.ContainsKey(header.type))
                {
                    InteropLogger.Warning($"Unhandled request type {header.type} for {target}");
                    return(0);
                }

                return(handlers[header.type](target, ptr));
            });
        }
Example #15
0
 /// <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
 {
     InteropLogger.Debug($"    Q-> {target}:{type:F}");
     outboundQueue.Enqueue(new InteropMessage
     {
         target = target,
         header = new InteropMessageHeader {
             type   = type,
             index  = 0,
             length = 0,
             count  = 0
         },
         payload = FastStructure.ToBytes(ref data)
     });
 }
Example #16
0
        /// <summary>
        /// Copy an <see cref="MLoop"/> array into managed memory for vertex lookups
        /// aligned with other MLoop* structures.
        /// </summary>
        /// <param name="loops"></param>
        internal void CopyFromMLoops(MLoop[] loops)
        {
            if (cachedLoops == null)
            {
                cachedLoops = new MLoop[loops.Length];
            }
            else
            {
                Array.Resize(ref cachedLoops, loops.Length);
            }

            Array.Copy(loops, cachedLoops, loops.Length);

            InteropLogger.Debug($"Copy {loops.Length} loops");
        }
Example #17
0
        public static int RemoveViewport(int viewportId)
        {
            InteropLogger.Debug($"Removing viewport={viewportId}");

            try
            {
                Bridge.RemoveViewport(viewportId);
                return(1);
            }
            catch (Exception e)
            {
                SetLastError(e);
                return(-1);
            }
        }
Example #18
0
        public void SendDirty()
        {
            if (!dirty)
            {
                return;
            }

            var b = Bridge.Instance;

            InteropLogger.Debug($"Pixel buffer is {pixels.Length} elements for {data.width}x{data.height} image");

            b.SendEntity(RpcRequest.UpdateTexture, this);
            b.SendArray(RpcRequest.UpdateTextureData, Name, pixels);

            dirty = false;
        }
Example #19
0
        public static int RemoveObjectFromScene(
            [MarshalAs(UnmanagedType.LPStr)] string name
            )
        {
            InteropLogger.Debug($"Removing object {name} from the scene");

            try
            {
                Bridge.RemoveObject(name);
                return(1);
            }
            catch (Exception e)
            {
                SetLastError(e);
                return(-1);
            }
        }
Example #20
0
        private int OnDisconnect(string target, IntPtr ptr)
        {
            InteropLogger.Debug("Unity disconnected");

            // Disconnect from invalidated shared memory since Unity was the owner
            Disconnect();

            // Based on Unity's side of things - we may never even see this message.
            // If unity sends out a Disconnect and then immediately disposes the
            // shared memory - we won't be able to read the disconnect and instead
            // just get an access violation on the next read of shared memory
            // (which is caught in Update() and calls Disconnect() anyway).
            // If there was some delay though between Unity sending a Disconnect
            // and memory cleanup, then we can safely catch it here and disconnect
            // ourselves while avoiding a potential access violation.

            return(0);
        }
Example #21
0
        /// <summary>
        /// Process queued messages and write if possible
        /// </summary>
        public void ProcessOutboundQueue()
        {
            if (outboundQueue.Count() < 1)
            {
                return;
            }

            if (outboundQueue.Count() > 10)
            {
                InteropLogger.Warning($"Outbound queue is at {outboundQueue.Count()} messages");
            }

            // Only dequeue a message once we have an available node for writing
            int bytesWritten = messageProducer.Write((ptr) =>
            {
                var next = outboundQueue.Dequeue();
                return(WriteMessage(next, ptr));
            }, 5);
        }
Example #22
0
        /// <summary>
        /// Send all buffers to Unity - regardless of dirtied status
        /// </summary>
        internal void SendAll()
        {
            var b = Bridge.Instance;

            InteropLogger.Debug($"SendAll name={Name}");

            b.SendArray(RpcRequest.UpdateVertices, Name, vertices);
            b.SendArray(RpcRequest.UpdateNormals, Name, normals);
            b.SendArray(RpcRequest.UpdateVertexColors, Name, colors);
            b.SendArray(RpcRequest.UpdateUV, Name, uvs);
            // ... and so on

            b.SendArray(RpcRequest.UpdateTriangles, Name, triangles);

            SendApplyChanges();

            // Clean everything - Unity should be synced.
            CleanAllBuffers();
        }
Example #23
0
        /// <summary>
        /// Send an array of <typeparamref name="T"/> to Unity, splitting into multiple
        /// <see cref="RpcRequest"/> if all the objects cannot fit in a single message.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="type"></param>
        /// <param name="target"></param>
        /// <param name="data"></param>
        /// <param name="allowSplitMessages"></param>
        internal void SendArray <T>(RpcRequest type, string target, T[] data, bool allowSplitMessages) where T : struct
        {
            if (!IsConnectedToSharedMemory)
            {
                return;
            }

            // TODO: Zero length array support. Makes sense in some use cases
            // but not others (e.g. don't send RpcRequest.UpdateUVs if there
            // are no UVs to send)
            if (data == null || data.Length < 1)
            {
                return;
            }

            if (messages.ReplaceOrQueueArray(type, target, data, allowSplitMessages))
            {
                InteropLogger.Debug($"Replaced queued {type} for {target}");
            }
        }
Example #24
0
        public static int CopyVertices(
        #pragma warning disable IDE0060 // Remove unused parameter
            [MarshalAs(UnmanagedType.LPStr)] string name,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2)] MLoop[] loops,
            uint loopCount,
            [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 4)] MVert[] vertices,
            uint verticesCount
        #pragma warning restore IDE0060 // Remove unused parameter
            )
        {
            InteropLogger.Debug($"Copy {loops.Length} loops and {vertices.Length} vertices for `{name}`");

            try
            {
                var obj = Bridge.GetObject(name);

                obj.CopyFromMVerts(vertices);
                obj.CopyFromMLoops(loops);

                // If the vertex count changes, we'll need to push a
                // change to the object's metadata
                if (obj.data.vertexCount != obj.Vertices.Length)
                {
                    obj.data.vertexCount = obj.Vertices.Length;
                    Bridge.SendEntity(RpcRequest.UpdateSceneObject, obj);
                }

                // Followed by changes to the vertex coordinate and normals
                Bridge.SendArray(RpcRequest.UpdateVertices, obj.Name, obj.Vertices, true);
                Bridge.SendArray(RpcRequest.UpdateNormals, obj.Name, obj.Normals, true);

                return(1);
            }
            catch (Exception e)
            {
                SetLastError(e);
                return(-1);
            }
        }
Example #25
0
        /// <summary>
        /// Connect to a shared memory space hosted by Unity and sync scene data
        /// </summary>
        /// <param name="connectionName">Common name for the shared memory space between Blender and Unity</param>
        public bool Connect(string connectionName, string versionInfo)
        {
            blenderState = new InteropBlenderState
            {
                version = versionInfo
            };

            try
            {
                InteropLogger.Debug($"Connecting to `{connectionName + VIEWPORT_IMAGE_BUFFER}`");

                // Buffer for render data coming from Unity (consume-only)
                pixelsConsumer = new CircularBuffer(connectionName + VIEWPORT_IMAGE_BUFFER);

                InteropLogger.Debug($"Connecting to `{connectionName + UNITY_MESSAGES_BUFFER}` and `{connectionName + BLENDER_MESSAGES_BUFFER}`");

                // Two-way channel between Blender and Unity
                messages = new InteropMessenger();
                messages.ConnectAsSlave(
                    connectionName + UNITY_MESSAGES_BUFFER,
                    connectionName + BLENDER_MESSAGES_BUFFER
                    );
            }
            catch (System.IO.FileNotFoundException)
            {
                // Shared memory space is not valid - Unity may not have started it.
                // This is an error that should be gracefully handled by the UI.
                IsConnectedToSharedMemory = false;
                return(false);
            }

            IsConnectedToSharedMemory = true;

            // Send an initial connect message to let Unity know we're in
            messages.Queue(RpcRequest.Connect, versionInfo, ref blenderState);

            return(true);
        }
Example #26
0
        void RebuildTriangles()
        {
            triangles.Resize(loopTris.Length * 3);

            InteropLogger.Debug($"RebuildTriangles name={Name}, Length={triangles.Length}");

            for (int t = 0; t < loopTris.Length; t++)
            {
                var loopTri = loopTris[t];

                // Triangles are flipped due to coordinate space conversions
                // that happen from Blender to Unity
                int tri0 = (int)loopTri.tri_2;
                int tri1 = (int)loopTri.tri_1;
                int tri2 = (int)loopTri.tri_0;

                // If the triangle vert has been mapped to a split vertex,
                // use that instead of the original vertex
                triangles[t * 3 + 0] = splitVertices.GetValueOrDefault(tri0, (int)loops[tri0].v);
                triangles[t * 3 + 1] = splitVertices.GetValueOrDefault(tri1, (int)loops[tri1].v);
                triangles[t * 3 + 2] = splitVertices.GetValueOrDefault(tri2, (int)loops[tri2].v);
            }
        }
Example #27
0
        public static int AddMeshObjectToScene(
            [MarshalAs(UnmanagedType.LPStr)] string name,
            InteropMatrix4x4 transform,
            [MarshalAs(UnmanagedType.LPStr)] string material
            )
        {
            InteropLogger.Debug($"Adding mesh <name={name}, material={material}>");

            try
            {
                var obj = new SceneObject(name, SceneObjectType.Mesh);
                obj.Transform = transform;
                obj.Material  = material;

                Bridge.AddObject(obj);
                return(1);
            }
            catch (Exception e)
            {
                SetLastError(e);
                return(-1);
            }
        }
Example #28
0
        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);
        }
Example #29
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);
        }
Example #30
0
 public static void SetLastError(Exception e)
 {
     InteropLogger.Error(e.ToString());
     LastError = e.ToString();
 }