/// <summary>
    /// Create a directed graph based on the provided connections (between node IDs) and a predefined set of input/output
    /// node IDs defined as being in a contiguous sequence starting at ID zero.
    /// </summary>
    /// <param name="connections">The connections that define the structure and weights of the weighted directed graph.</param>
    /// <param name="inputCount">Input node count.</param>
    /// <param name="outputCount">Output node count.</param>
    /// <returns>A new instance of <see cref="DirectedGraph"/>.</returns>
    /// <remarks>
    /// <paramref name="connections"/> is required to be sorted by sourceId, TargetId.
    /// </remarks>
    public static DirectedGraph Create(
        Span <DirectedConnection> connections,
        int inputCount, int outputCount)
    {
        // Debug assert that the connections are sorted.
        Debug.Assert(SortUtils.IsSortedAscending <DirectedConnection>(connections));

        // Determine the full set of hidden node IDs.
        int inputOutputCount = inputCount + outputCount;
        var hiddenNodeIdArr  = GetHiddenNodeIdArray(connections, inputOutputCount);

        // Compile a mapping from current nodeIDs to new IDs (i.e. removing gaps in the ID space).
        INodeIdMap nodeIdMap = DirectedGraphBuilderUtils.CompileNodeIdMap(
            inputOutputCount, hiddenNodeIdArr);

        // Extract/copy the neat genome connectivity graph into an array of DirectedConnection.
        // Notes.
        // The array contents will be manipulated, so copying this avoids modification of the genome's
        // connection gene list.
        // The IDs are substituted for node indexes here.
        ConnectionIds connIds = CopyAndMapIds(connections, nodeIdMap);

        // Construct and return a new DirectedGraph.
        int totalNodeCount = inputOutputCount + hiddenNodeIdArr.Length;

        return(new DirectedGraph(inputCount, outputCount, totalNodeCount, connIds));
    }
        private void Events_ConnectionClosed(RemotingDbConnection connection)
        {
            var id = GetOrThrowConnectionId(connection);

            ConnectionIds.TryRemove(connection, out _);
            CloseConnection(id);
        }
示例#3
0
        /// <summary>
        /// Sends a packet over the network
        /// </summary>
        /// <param name="packet">The <see cref="Packet"/> instance that is to be sent</param>
        /// <param name="reliable">Whether the transmission is reliable (slow) or unreliable (fast)</param>
        /// <returns>Whether message can be sent or not</returns>
        public bool Send(Packet packet, bool reliable = false)
        {
            if (m_Network == null || ConnectionIds == null || ConnectionIds.Count == 0)
            {
                return(false);
            }

            List <ConnectionId> recipients = new List <ConnectionId>();

            if (packet.Recipients.Length != 0)
            {
                recipients = ConnectionIds.Select(x => x)
                             .Where(x => packet.Recipients.Contains(x.id))
                             .ToList();
            }
            else
            {
                recipients = ConnectionIds.ToList();
            }

            var bytes = packet.Serialize();

            foreach (var cid in recipients)
            {
                m_Network.SendData(cid, bytes, 0, bytes.Length, reliable);
            }

            return(true);
        }
示例#4
0
        public async Task AddRoom(ConnectionIds room)
        {
            var connectionIdsContext = new MonopolyDbContext();

            connectionIdsContext.ConnectionIds.Add(room);
            connectionIdsContext.SaveChanges();
        }
示例#5
0
        public int GetRoomId(string connection)
        {
            var           connectionIdsContext = new MonopolyDbContext();
            ConnectionIds room = connectionIdsContext.ConnectionIds.Where(x => x.connectionId == connection).FirstOrDefault();

            return(room.RoomId);
        }
示例#6
0
    /// <summary>
    /// Constructs a AcyclicNeuralNet with the provided neural net definition parameters.
    /// </summary>
    /// <param name="digraph">Network structure definition.</param>
    /// <param name="weightArr">Connection weights array.</param>
    /// <param name="activationFn">Node activation function.</param>
    public NeuralNetAcyclicSafe(
        DirectedGraphAcyclic digraph,
        double[] weightArr,
        VecFn <double> activationFn)
    {
        Debug.Assert(digraph.ConnectionIds.GetSourceIdSpan().Length == weightArr.Length);

        // Store refs to network structure data.
        _connIds      = digraph.ConnectionIds;
        _weightArr    = weightArr;
        _layerInfoArr = digraph.LayerArray;

        // Store network activation function.
        _activationFn = activationFn;

        // Store input/output node counts.
        _inputCount  = digraph.InputCount;
        _outputCount = digraph.OutputCount;

        // Create working array for node activation signals.
        _activationArr = new double[digraph.TotalNodeCount];

        // Map the inputs vector to the corresponding segment of node activation values.
        this.Inputs = new Memory <double>(_activationArr, 0, _inputCount);

        // Get an array to act a as a contiguous run of output signals.
        _outputArr   = new double[_outputCount];
        this.Outputs = _outputArr;

        // Store the indexes into _activationArr that give the output signals.
        _outputNodeIdxArr = digraph.OutputNodeIdxArr;
    }
示例#7
0
        public override void OnServerReceive(TransportEventData eventData)
        {
            CentralServerPeerInfo tempPeerInfo;

            switch (eventData.type)
            {
            case ENetworkEvent.ConnectEvent:
                Logging.Log(LogTag, "OnPeerConnected peer.ConnectionId: " + eventData.connectionId);
                ConnectionIds.Add(eventData.connectionId);
                break;

            case ENetworkEvent.DataEvent:
                ReadPacket(eventData.connectionId, eventData.reader);
                break;

            case ENetworkEvent.DisconnectEvent:
                Logging.Log(LogTag, "OnPeerDisconnected peer.ConnectionId: " + eventData.connectionId + " disconnectInfo.Reason: " + eventData.disconnectInfo.Reason);
                ConnectionIds.Remove(eventData.connectionId);
                // Remove disconnect map spawn server
                MapSpawnServerPeers.Remove(eventData.connectionId);
                // Remove disconnect map server
                if (MapServerPeers.TryGetValue(eventData.connectionId, out tempPeerInfo))
                {
                    MapServerPeersByMapId.Remove(tempPeerInfo.extra);
                    MapServerPeersByInstanceId.Remove(tempPeerInfo.extra);
                    MapServerPeers.Remove(eventData.connectionId);
                    RemoveMapUsers(eventData.connectionId);
                }
                break;

            case ENetworkEvent.ErrorEvent:
                Logging.LogError(LogTag, "OnPeerNetworkError endPoint: " + eventData.endPoint + " socketErrorCode " + eventData.socketError + " errorMessage " + eventData.errorMessage);
                break;
            }
        }
示例#8
0
    /// <summary>
    /// Constructs a cyclic neural network.
    /// </summary>
    /// <param name="digraph">The directed graph that defines the neural network structure.</param>
    /// <param name="weightArr">An array of neural network connection weights.</param>
    /// <param name="activationFn">The neuron activation function to use at all neurons in the network.</param>
    /// <param name="cyclesPerActivation">The number of activation cycles to perform per overall activation of the cyclic network.</param>
    public NeuralNetCyclicSafe(
        DirectedGraph digraph,
        double[] weightArr,
        VecFn2 <double> activationFn,
        int cyclesPerActivation)
    {
        Debug.Assert(digraph.ConnectionIds.GetSourceIdSpan().Length == weightArr.Length);

        // Store refs to network structure data.
        _connIds   = digraph.ConnectionIds;
        _weightArr = weightArr;

        // Store network activation function and parameters.
        _activationFn        = activationFn;
        _cyclesPerActivation = cyclesPerActivation;

        // Store input/output node counts.
        _inputCount  = digraph.InputCount;
        _outputCount = digraph.OutputCount;

        // Create node pre- and post-activation signal arrays.
        int nodeCount = digraph.TotalNodeCount;

        _preActivationArr  = new double[nodeCount];
        _postActivationArr = new double[nodeCount];

        // Map the input and output vectors to the corresponding segments of _postActivationArr.
        this.Inputs = new Memory <double>(_postActivationArr, 0, _inputCount);

        // Note. Output neurons follow input neurons in the arrays.
        this.Outputs = new Memory <double>(_postActivationArr, _inputCount, _outputCount);
    }
示例#9
0
 void OnServerInitSuccess(NetworkEvent netEvent)
 {
     ConnectionIds.Add(new ConnectionId(0));
     NodeState = State.Server;
     m_StartServerCallback.TryInvoke(true);
     m_StartServerCallback = null;
 }
示例#10
0
    /// <summary>
    /// Constructs a cyclic neural network.
    /// </summary>
    /// <param name="digraph">The directed graph that defines the neural network structure.</param>
    /// <param name="weightArr">An array of neural network connection weights.</param>
    /// <param name="activationFn">The neuron activation function to use at all neurons in the network.</param>
    /// <param name="cyclesPerActivation">The number of activation cycles to perform per overall activation of the cyclic network.</param>
    public NeuralNetCyclic(
        DirectedGraph digraph,
        double[] weightArr,
        VecFn2 <double> activationFn,
        int cyclesPerActivation)
    {
        Debug.Assert(digraph.ConnectionIds.GetSourceIdSpan().Length == weightArr.Length);

        // Store refs to network structure data.
        _connIds   = digraph.ConnectionIds;
        _weightArr = weightArr;

        // Store network activation function and parameters.
        _activationFn        = activationFn;
        _cyclesPerActivation = cyclesPerActivation;

        // Store input/output node counts.
        _inputCount     = digraph.InputCount;
        _outputCount    = digraph.OutputCount;
        _totalNodeCount = digraph.TotalNodeCount;

        // Get a working array for both pre and post node activation signals, and map memory segments to pre
        // and post signal segments.
        // Rent an array that has length of at least _totalNodeCount * 2.
        _activationsArr = ArrayPool <double> .Shared.Rent(_totalNodeCount << 1);

        _preActivationMem  = _activationsArr.AsMemory(0, _totalNodeCount);
        _postActivationMem = _activationsArr.AsMemory(_totalNodeCount, _totalNodeCount);

        // Map the input and output vectors to the corresponding segments of _postActivationArr.
        this.Inputs = _postActivationMem.Slice(0, _inputCount);

        // Note. Output neurons follow input neurons in the arrays.
        this.Outputs = _postActivationMem.Slice(_inputCount, _outputCount);
    }
        private string GetOrThrowConnectionId(RemotingDbConnection connection)
        {
            if (ConnectionIds.TryGetValue(connection, out var id))
            {
                return(id);
            }

            throw new InvalidOperationException("Missing Connection Id");
        }
示例#12
0
 public virtual void RegisterPlayerCharacter(long connectionId, BasePlayerCharacterEntity playerCharacterEntity)
 {
     if (playerCharacterEntity == null || !ConnectionIds.Contains(connectionId) || playerCharacters.ContainsKey(connectionId))
     {
         return;
     }
     playerCharacters[connectionId] = playerCharacterEntity;
     playerCharactersById[playerCharacterEntity.Id] = playerCharacterEntity;
     connectionIdsByCharacterName[playerCharacterEntity.CharacterName] = connectionId;
 }
        public void removeConnection(string currentPlayer)
        {
            int           roomId     = 0;
            var           roomDb     = new MonopolyDbContext();
            ConnectionIds connection = roomDb.ConnectionIds.Where(x => x.PlayerName == currentPlayer).FirstOrDefault();

            if (connection != null)
            {
                roomId = roomDb.ConnectionIds.Where(x => x.PlayerName == currentPlayer).FirstOrDefault().RoomId;
                roomDb.Remove(connection);
                roomDb.SaveChanges();
            }
            checkLastPlayer(roomId);
        }
示例#14
0
 public void AddConnectionId(string newConnectionId)
 {
     if (ConnectionIds.Count >= MaxConnectionsPerUser)
     {
         //SendErrorMessage("Too many connections to the website. Please make sure you are not using same tool in multiply tabs.");
         //return;
         ConnectionIds.RemoveFirst();
     }
     if (!ConnectionIds.Contains(newConnectionId)) // only for low number of connection
     {
         ConnectionIds.AddLast(newConnectionId);
     }
     //_hub = GlobalHost.ConnectionManager.GetHubContext<SessionHub>();
     SetGuiState(_worker != null ? 1 : 0, newConnectionId);
     UpdateLastActivityTime();
 }
示例#15
0
    private static void CopyAndMapIds(
        DirectedConnection[] connArr,
        INodeIdMap nodeIdMap,
        out ConnectionIds connIds)
    {
        int count = connArr.Length;

        connIds = new ConnectionIds(count);
        var srcIds = connIds.GetSourceIdSpan();
        var tgtIds = connIds.GetTargetIdSpan();

        for (int i = 0; i < count; i++)
        {
            srcIds[i] = nodeIdMap.Map(connArr[i].SourceId);
            tgtIds[i] = nodeIdMap.Map(connArr[i].TargetId);
        }
    }
    private static ConnectionIds CopyAndMapIds(
        Span <DirectedConnection> connSpan,
        INodeIdMap nodeIdMap)
    {
        int count   = connSpan.Length;
        var connIds = new ConnectionIds(count);
        var srcIds  = connIds.GetSourceIdSpan();
        var tgtIds  = connIds.GetTargetIdSpan();

        for (int i = 0; i < count; i++)
        {
            srcIds[i] = nodeIdMap.Map(connSpan[i].SourceId);
            tgtIds[i] = nodeIdMap.Map(connSpan[i].TargetId);
        }

        return(connIds);
    }
示例#17
0
        void OnDisconnected(NetworkEvent netEvent)
        {
            if (NodeState == State.Client)
            {
                NodeState = State.Uninitialized;
                Deinit();
            }
            else if (NodeState == State.Server)
            {
                OnLeave.TryInvoke(netEvent.ConnectionId);
                var dId = netEvent.ConnectionId;
                ConnectionIds.Remove(netEvent.ConnectionId);

                var payload = new UniStreamWriter().WriteShort(dId.id).Bytes;

                // Clients are not aware of each other as this is a star network
                // Send a reliable reserved message to everyone to announce the disconnection
                Send(Packet.From(CId).With(ReservedTags.ClientLeft, payload), true);
            }
        }
示例#18
0
    /// <summary>
    /// Split each IWeightedDirectedConnection in a list into an array of DirectedConnections(s), and an array of weights.
    /// Map the node IDs to indexes as we go.
    /// </summary>
    private static void CopyAndMapIds(
        Span <WeightedDirectedConnection <T> > connSpan,
        INodeIdMap nodeIdMap,
        out ConnectionIds connIds,
        out T[] weightArr)
    {
        int count = connSpan.Length;

        connIds = new ConnectionIds(count);
        var srcIds = connIds.GetSourceIdSpan();
        var tgtIds = connIds.GetTargetIdSpan();

        weightArr = new T[count];

        for (int i = 0; i < count; i++)
        {
            srcIds[i]    = nodeIdMap.Map(connSpan[i].SourceId);
            tgtIds[i]    = nodeIdMap.Map(connSpan[i].TargetId);
            weightArr[i] = connSpan[i].Weight;
        }
    }
示例#19
0
        void OnNewConnection(NetworkEvent netEvent)
        {
            ConnectionId newCId = netEvent.ConnectionId;

            ConnectionIds.Add(newCId);

            if (NodeState == State.Uninitialized)
            {
                OnJoin.TryInvoke(newCId);

                // Add server as a connection on the client end
                ConnectionIds.Add(new ConnectionId(0));
                NodeState = State.Client;
            }
            else if (NodeState == State.Server)
            {
                OnJoin.TryInvoke(newCId);
                foreach (var id in ConnectionIds)
                {
                    if (id.id == 0 || id.id == newCId.id)
                    {
                        continue;
                    }

                    byte[] payload;

                    // Announce the new connection to the old ones and vice-versa
                    payload = new UniStreamWriter().WriteShort(newCId.id).Bytes;
                    Send(Packet.From(this).To(id).With(ReservedTags.ClientJoined, payload), true);

                    payload = new UniStreamWriter().WriteShort(id.id).Bytes;
                    Send(Packet.From(this).To(newCId).With(ReservedTags.ClientJoined, payload), true);
                }
            }

            m_ConnectCallback.TryInvoke(newCId);
            m_ConnectCallback = null;
        }
        public void PlayerLeavesGame(string player)
        {
            int           roomId     = 0;
            var           roomDb     = new MonopolyDbContext();
            ConnectionIds connection = roomDb.ConnectionIds.Where(x => x.PlayerName == player).FirstOrDefault();

            if (connection != null)
            {
                roomId = roomDb.ConnectionIds.Where(x => x.PlayerName == player).FirstOrDefault().RoomId;
                roomDb.Remove(connection);
                roomDb.SaveChanges();
            }

            Room room = roomDb.Rooms.Where(x => x.Player1 == player || x.Player2 == player || x.Player3 == player || x.Player4 == player).FirstOrDefault();

            if (room != null)
            {
                if (room.Player1 == player)
                {
                    room.Player1 = null;
                }
                else if (room.Player2 == player)
                {
                    room.Player2 = null;
                }
                else if (room.Player3 == player)
                {
                    room.Player3 = null;
                }
                else if (room.Player4 == player)
                {
                    room.Player4 = null;
                }
                roomDb.SaveChanges();
            }
        }
示例#21
0
    /// <summary>
    /// Constructs a AcyclicNeuralNet with the provided neural net definition parameters.
    /// </summary>
    /// <param name="digraph">Network structure definition.</param>
    /// <param name="weightArr">Connection weights array.</param>
    /// <param name="activationFn">Node activation function.</param>
    public NeuralNetAcyclic(
        DirectedGraphAcyclic digraph,
        double[] weightArr,
        VecFn <double> activationFn)
    {
        Debug.Assert(digraph.ConnectionIds.GetSourceIdSpan().Length == weightArr.Length);

        // Store refs to network structure data.
        _connIds      = digraph.ConnectionIds;
        _weightArr    = weightArr;
        _layerInfoArr = digraph.LayerArray;

        // Store network activation function.
        _activationFn = activationFn;

        // Store input/output node counts.
        _inputCount     = digraph.InputCount;
        _outputCount    = digraph.OutputCount;
        _totalNodeCount = digraph.TotalNodeCount;

        // Get a working array for node activations signals and a separate output signal segment on the end.
        // And map the memory segments onto the array.
        _workingArr = ArrayPool <double> .Shared.Rent(_totalNodeCount + _outputCount);

        _activationMem = _workingArr.AsMemory(0, _totalNodeCount);
        _outputMem     = _workingArr.AsMemory(_totalNodeCount, _outputCount);

        // Map the inputs vector to the corresponding segment of node activation values.
        this.Inputs = _activationMem.Slice(0, _inputCount);

        // Use the already defined outputs memory segment.
        this.Outputs = _outputMem;

        // Store the indexes into _activationArr that give the output signals.
        _outputNodeIdxArr = digraph.OutputNodeIdxArr;
    }
示例#22
0
        void OnMessageReceived(NetworkEvent netEvent, bool reliable)
        {
            var bytes  = netEvent.GetDataAsByteArray();
            var packet = Packet.Deserialize(bytes);

            // If packet is null, it is a "raw" byte array message.
            // Forward it to everyone
            if (packet == null)
            {
                OnGetBytes.TryInvoke(netEvent.ConnectionId, bytes, reliable);
                foreach (var r in ConnectionIds)
                {
                    // Forward to everyone except the original sender and the server
                    if (r == CId || r == netEvent.ConnectionId)
                    {
                        continue;
                    }
                    Send(Packet.From(CId).To(r).With(ReservedTags.PacketForwarding, packet.Serialize()), true);
                }
                return;
            }


            string reservedTag = packet.Tag.StartsWith("reserved") ? packet.Tag : string.Empty;

            // If is not a reserved message
            if (reservedTag == string.Empty)
            {
                OnGetPacket.TryInvoke(netEvent.ConnectionId, packet, reliable);

                if (NodeState != State.Server)
                {
                    return;
                }

                // The server tries to broadcast the packet to everyone else listed as recipients
                foreach (var r in packet.Recipients)
                {
                    // Forward to everyone except the original sender and the server
                    if (r == CId.id || r == netEvent.ConnectionId.id)
                    {
                        continue;
                    }
                    Send(Packet.From(CId).To(r).With(ReservedTags.PacketForwarding, packet.Serialize()), true);
                }
                return;
            }

            // handle reserved messages
            switch (reservedTag)
            {
            case ReservedTags.ServerStopped:
                OnServerStopped.TryInvoke();
                break;

            case ReservedTags.ClientJoined:
                ConnectionIds.Add(netEvent.ConnectionId);
                OnJoin.TryInvoke(netEvent.ConnectionId);
                break;

            case ReservedTags.ClientLeft:
                ConnectionIds.Remove(netEvent.ConnectionId);
                OnLeave.TryInvoke(netEvent.ConnectionId);
                break;

            case ReservedTags.PacketForwarding:
                OnGetPacket.TryInvoke(netEvent.ConnectionId, packet, reliable);
                break;
            }
        }
 private void Events_ConnectionOpened(RemotingDbConnection connection)
 {
     ConnectionIds.TryAdd(connection, RegisterConnection());
 }
示例#24
0
 public void RemoveConnectionId(string connectionId)
 {
     ConnectionIds.Remove(connectionId);
     UpdateLastActivityTime();
 }
示例#25
0
    /// <summary>
    /// Creates a new directed acyclic graph instance from the provided graph structure, accompanying graph
    /// layer information, and node ID mappings.
    /// </summary>
    /// <param name="digraph">A directed graph structure.</param>
    /// <param name="depthInfo">Depth/layer information, describing what layer each node of the graph is within.</param>
    /// <param name="newIdByOldId">Returns a set of node ID mappings. These describe a mapping from the
    /// non-contiguous node ID space of <paramref name="digraph"/>, to the contiguous node ID space of the
    /// returned <see cref="DirectedGraphAcyclic"/>
    /// contiguous space.</param>
    /// <param name="connectionIndexMap">Returns a set of connection index mappings. The connections of
    /// <paramref name="digraph"/> are re-ordered based on the layer/depth of each connection's source node;
    /// this structure conveys the new index of each connection given its original/old index.</param>
    /// <param name="timsortWorkArr">A re-usable working array for use as temporary storage by the timsort algorithm.</param>
    /// <param name="timsortWorkVArr">A secondary re-usable working array for use as temporary storage by the timsort algorithm.</param>
    /// <returns>A new instance of <see cref="DirectedGraphAcyclic"/>.</returns>
    public static DirectedGraphAcyclic CreateDirectedGraphAcyclic(
        DirectedGraph digraph,
        GraphDepthInfo depthInfo,
        out int[] newIdByOldId,
        out int[] connectionIndexMap,
        ref int[]?timsortWorkArr,
        ref int[]?timsortWorkVArr)
    {
        int inputCount  = digraph.InputCount;
        int outputCount = digraph.OutputCount;

        // Assert that all input nodes are at depth zero.
        // Any input node with a non-zero depth must have an input connection, and this is not supported.
        Debug.Assert(SpanUtils.Equal(depthInfo._nodeDepthArr.AsSpan(0, inputCount), 0));

        // Compile a mapping from current node IDs to new IDs (based on node depth in the graph).
        newIdByOldId = CompileNodeIdMap(depthInfo, digraph.TotalNodeCount, inputCount, ref timsortWorkArr, ref timsortWorkVArr);

        // Map the connection node IDs.
        ConnectionIds connIds = digraph.ConnectionIds;

        MapIds(connIds, newIdByOldId);

        // Init connection index map.
        int connCount = connIds.Length;

        connectionIndexMap = new int[connCount];
        for (int i = 0; i < connCount; i++)
        {
            connectionIndexMap[i] = i;
        }

        // Sort the connections based on sourceID, targetId; this will arrange the connections based on the depth
        // of the source nodes.
        // Note. This sort routine will also sort a secondary array, i.e. keep the items in both arrays aligned;
        // here we use this to create connectionIndexMap.
        ConnectionSorter <int> .Sort(connIds, connectionIndexMap);

        // Make a copy of the sub-range of newIdByOldId that represents the output nodes.
        // This is required later to be able to locate the output nodes now that they have been sorted by depth.
        int[] outputNodeIdxArr = new int[outputCount];
        newIdByOldId.AsSpan(inputCount, outputCount).CopyTo(outputNodeIdxArr);

        // Create an array of LayerInfo(s).
        // Each LayerInfo contains the index + 1 of both the last node and last connection in that layer.
        //
        // The array is in order of depth, from layer zero (inputs nodes) to the last layer (usually output nodes,
        // but not necessarily if there is a dead end pathway with a high number of hops).
        //
        // Note. There is guaranteed to be at least one connection with a source at a given depth level, this is
        // because for there to be a layer N there must necessarily be a connection from a node in layer N-1
        // to a node in layer N.
        int graphDepth = depthInfo._graphDepth;

        LayerInfo[] layerInfoArr = new LayerInfo[graphDepth];

        // Note. Scanning over nodes can start at inputCount instead of zero, because all nodes prior to that index
        // are input nodes and are therefore at depth zero. (input nodes are never the target of a connection,
        // therefore are always guaranteed to be at the start of a connectivity graph, and thus at depth zero).
        int nodeCount = digraph.TotalNodeCount;
        int nodeIdx   = inputCount;
        int connIdx   = 0;

        int[] nodeDepthArr        = depthInfo._nodeDepthArr;
        ReadOnlySpan <int> srcIds = connIds.GetSourceIdSpan();

        for (int currDepth = 0; currDepth < graphDepth; currDepth++)
        {
            // Scan for last node at the current depth.
            for (; nodeIdx < nodeCount && nodeDepthArr[nodeIdx] == currDepth; nodeIdx++)
            {
                ;
            }

            // Scan for last connection at the current depth.
            for (; connIdx < srcIds.Length && nodeDepthArr[srcIds[connIdx]] == currDepth; connIdx++)
            {
                ;
            }

            // Store node and connection end indexes for the layer.
            layerInfoArr[currDepth] = new LayerInfo(nodeIdx, connIdx);
        }

        // Construct and return.
        return(new DirectedGraphAcyclic(
                   inputCount, outputCount, nodeCount,
                   connIds,
                   layerInfoArr,
                   outputNodeIdxArr));
    }