-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathPseudoNetwork.cs
More file actions
208 lines (183 loc) · 6.61 KB
/
PseudoNetwork.cs
File metadata and controls
208 lines (183 loc) · 6.61 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
using System;
using System.Collections.Generic;
using System.Linq;
using Assets.Scripts.GridSystem;
using Assets.Scripts.Objects;
using Assets.Scripts.Objects.Pipes;
using Assets.Scripts.Util;
namespace LibConstruct;
public interface IPseudoNetworkMember<T> : IReferencable where T : IPseudoNetworkMember<T>
{
// connections that should connect network members
public IEnumerable<Connection> Connections { get; }
public PseudoNetwork<T> Network { get; }
// When network members are changed, called once for each new member
public void OnMemberAdded(T member);
// When network members are changed, called once for each member no longer in network
public void OnMemberRemoved(T member);
// When network members are changed, called once after all OnMemberAdded/OnMemberRemoved hooks
public void OnMembersChanged();
}
internal static class PseudoNetworks
{
private static readonly object _networkTypeLock = new();
// this gives us 23 custom network types that hopefully won't already be used
private static NetworkType _nextNetworkType = (NetworkType)0x00100000;
private static readonly List<(Type, ConnectionGetter)> _connectionGetters = [];
private delegate IEnumerable<Connection> ConnectionGetter(Device device);
private static NetworkType NextNetworkType()
{
lock (_networkTypeLock)
{
var res = _nextNetworkType;
if (res > NetworkType.All)
throw new Exception("Exhausted custom network types");
_nextNetworkType = (NetworkType)((long)_nextNetworkType << 1);
return res;
}
}
private static IEnumerable<Connection> GetConnections<T>(Device device) where T : IPseudoNetworkMember<T>
{
if (device is not IPseudoNetworkMember<T> member)
yield break;
foreach (var connection in member.Connections)
yield return connection;
}
public static NetworkType AddNetworkType<T>() where T : IPseudoNetworkMember<T>
{
_connectionGetters.Add((typeof(T), GetConnections<T>));
return NextNetworkType();
}
public static List<Device> RemovePseudoConnected(List<Device> connectedDevices, Device device)
{
for (var i = connectedDevices.Count - 1; i >= 0; i--)
{
var connected = connectedDevices[i];
var keep = true;
foreach (var (memberType, getConnections) in _connectionGetters)
{
if (!memberType.IsAssignableFrom(device.GetType()) || !memberType.IsAssignableFrom(connected.GetType()))
continue;
// if both devices implement the same member interface, check the connections for matches
foreach (var conn in getConnections(device))
{
if (connected.IsConnected(conn))
{
keep = false;
break;
}
}
if (!keep) break;
}
if (!keep)
connectedDevices.RemoveAt(i);
}
return connectedDevices;
}
}
public class PseudoNetworkType<T> where T : IPseudoNetworkMember<T>
{
public NetworkType ConnectionType { get; }
public PseudoNetworkType()
{
// This isn't sent over the network so it doesn't matter if these get assigned different
// numbers in host/client, as long as it is unique for each.
ConnectionType = PseudoNetworks.AddNetworkType<T>();
}
public void PatchConnections(T memberPrefab)
{
foreach (var connection in memberPrefab.Connections)
connection.ConnectionType = ConnectionType;
}
public PseudoNetwork<T> Join() => new(this);
// Call this at the end of OnRegistered
public void RebuildNetworkCreate(T newMember)
{
RebuildNetworkFrom(newMember, false);
}
// Call this at the end of OnDeregistered
public void RebuildNetworkDestroy(T destroyedMember)
{
RebuildNetworkFrom(destroyedMember, true);
}
private void RebuildNetworkFrom(T start, bool excludeStart)
{
var visited = new HashSet<T>();
if (excludeStart) visited.Add(start);
var memberList = start.Network.Members.ToList(); // make a copy
if (memberList.Count == 0)
memberList.Add(start);
// run through each member and rebuild its network, skipping any included in networks we already rebuilt
foreach (var member in memberList)
{
if (visited.Contains(member))
continue;
RebuildNetworkSingle(member, excludeStart ? start : default(T));
visited.AddRange(member.Network.Members);
}
}
private void RebuildNetworkSingle(T start, T exclude)
{
// BFS walk connections from start
var queue = new Queue<T>();
queue.Enqueue(start);
var members = new HashSet<T>() { start };
while (queue.Count > 0)
{
var member = queue.Dequeue();
foreach (var conn in member.Connections)
{
var other = SmallCell.Get<T>(conn.GetLocalGrid());
// exclude the member being destroyed (if there is one), and members we've already enqueued
if (other == null || other.Equals(exclude) || members.Contains(other))
continue;
var connected = false;
foreach (var oconn in other.Connections)
{
// connection must match the network type and be facing the opposite grids
if (oconn.ConnectionType == ConnectionType && oconn.GetLocalGrid() == conn.GetFacingGrid() && oconn.GetFacingGrid() == conn.GetLocalGrid())
{
connected = true;
break;
}
}
if (connected)
{
queue.Enqueue(other);
members.Add(other);
}
}
}
// Send updates to all member lists
foreach (var member in members)
member.Network.ReplaceMembers(member, members);
}
// These helpers only exist to make it more convenient to have one class implement multiple
// network types. The interface members will have to be implemented explicitly, so calling
// these on the definition will implicitly call the right interface methods.
public PseudoNetwork<T> MemberNetwork(T member) => member.Network;
public IEnumerable<Connection> MemberConnections(T member) => member.Connections;
}
// Contains the list of all connected members. Each member has its own PseudoNetwork object
public class PseudoNetwork<T> where T : IPseudoNetworkMember<T>
{
public readonly PseudoNetworkType<T> Type;
public HashSet<T> Members { get; internal set; }
internal PseudoNetwork(PseudoNetworkType<T> type)
{
Type = type;
Members = [];
}
internal void ReplaceMembers(T self, HashSet<T> newMembers)
{
var oldMembers = Members;
Members = newMembers;
foreach (var member in oldMembers)
if (!newMembers.Contains(member))
self.OnMemberRemoved(member);
foreach (var member in newMembers)
if (!oldMembers.Contains(member))
self.OnMemberAdded(member);
self.OnMembersChanged();
}
}