@rbxts/replion 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/BaseReplion.lua +76 -11
- package/src/Client.lua +52 -102
- package/src/Server.lua +34 -50
- package/src/Shared.lua +0 -5
package/README.md
CHANGED
package/package.json
CHANGED
package/src/BaseReplion.lua
CHANGED
|
@@ -15,7 +15,7 @@ function BaseReplion.new(data: GenericDataTable)
|
|
|
15
15
|
|
|
16
16
|
self.data = data;
|
|
17
17
|
|
|
18
|
-
self.
|
|
18
|
+
self._signalRoot = {};
|
|
19
19
|
self._allSignal = Signal.new();
|
|
20
20
|
|
|
21
21
|
return self;
|
|
@@ -26,23 +26,78 @@ function BaseReplion:get(path: { string }?)
|
|
|
26
26
|
return Shared.getNestedValue(self.data, path);
|
|
27
27
|
end;
|
|
28
28
|
|
|
29
|
+
function BaseReplion:_pruneSignalTree(path: {string})
|
|
30
|
+
local stack = {self._signalRoot};
|
|
31
|
+
local current = self._signalRoot;
|
|
32
|
+
|
|
33
|
+
for _, key in path do
|
|
34
|
+
current = current[key];
|
|
35
|
+
if not current then return; end;
|
|
36
|
+
table.insert(stack, current);
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
for i = #path, 1, -1 do
|
|
40
|
+
local key = path[i];
|
|
41
|
+
local node = stack[i + 1];
|
|
42
|
+
local parent = stack[i];
|
|
43
|
+
|
|
44
|
+
local hasListeners = (node._refCount or 0) > 0;
|
|
45
|
+
local hasChildren = false;
|
|
46
|
+
|
|
47
|
+
if not hasListeners then
|
|
48
|
+
for k in node do
|
|
49
|
+
if k == '_signal' then continue; end;
|
|
50
|
+
if k == '_refCount' then continue; end;
|
|
51
|
+
hasChildren = true;
|
|
52
|
+
break;
|
|
53
|
+
end;
|
|
54
|
+
end;
|
|
55
|
+
|
|
56
|
+
if not hasListeners and not hasChildren then
|
|
57
|
+
parent[key] = nil;
|
|
58
|
+
else
|
|
59
|
+
break;
|
|
60
|
+
end;
|
|
61
|
+
end;
|
|
62
|
+
end;
|
|
63
|
+
|
|
29
64
|
function BaseReplion:subscribe(path: { string }?, callback: Observer)
|
|
30
|
-
if not path then
|
|
65
|
+
if (not path) or (#path == 0) then
|
|
31
66
|
local connection = self._allSignal:Connect(callback);
|
|
32
67
|
return function()
|
|
33
68
|
connection:Disconnect()
|
|
34
69
|
end;
|
|
35
70
|
end;
|
|
36
71
|
|
|
37
|
-
local
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
local current = self._signalRoot;
|
|
73
|
+
for _, key in path do
|
|
74
|
+
if not current[key] then current[key] = {}; end;
|
|
75
|
+
current = current[key];
|
|
40
76
|
end;
|
|
41
77
|
|
|
42
|
-
|
|
43
|
-
|
|
78
|
+
if not current._signal then
|
|
79
|
+
current._signal = Signal.new();
|
|
80
|
+
current._refCount = 0;
|
|
81
|
+
end;
|
|
82
|
+
|
|
83
|
+
local connection = current._signal:Connect(callback);
|
|
84
|
+
current._refCount = (current._refCount or 0) + 1;
|
|
85
|
+
|
|
44
86
|
return function()
|
|
45
87
|
connection:Disconnect();
|
|
88
|
+
|
|
89
|
+
if current._refCount then
|
|
90
|
+
current._refCount -= 1;
|
|
91
|
+
end;
|
|
92
|
+
|
|
93
|
+
if current._refCount <= 0 then
|
|
94
|
+
if current._signal then
|
|
95
|
+
current._signal:Destroy();
|
|
96
|
+
current._signal = nil;
|
|
97
|
+
end;
|
|
98
|
+
current._refCount = nil;
|
|
99
|
+
self:_pruneSignalTree(path);
|
|
100
|
+
end
|
|
46
101
|
end;
|
|
47
102
|
end;
|
|
48
103
|
|
|
@@ -59,10 +114,20 @@ end;
|
|
|
59
114
|
|
|
60
115
|
function BaseReplion:destroy()
|
|
61
116
|
self._allSignal:Destroy();
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
117
|
+
|
|
118
|
+
local function destroyTree(node)
|
|
119
|
+
if node._signal then
|
|
120
|
+
node._signal:Destroy();
|
|
121
|
+
end;
|
|
122
|
+
for k, v in node do
|
|
123
|
+
if k == '_signal' then continue; end;
|
|
124
|
+
if k == '_refCount' then continue; end;
|
|
125
|
+
if typeof(v) ~= 'table' then continue; end;
|
|
126
|
+
destroyTree(v);
|
|
127
|
+
end;
|
|
128
|
+
end;
|
|
129
|
+
destroyTree(self._signalRoot)
|
|
130
|
+
table.clear(self._signalRoot);
|
|
66
131
|
end;
|
|
67
132
|
|
|
68
133
|
return BaseReplion;
|
package/src/Client.lua
CHANGED
|
@@ -5,22 +5,8 @@ local BaseReplion = require(script.Parent.BaseReplion)
|
|
|
5
5
|
local Shared = require(script.Parent.Shared);
|
|
6
6
|
local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
|
|
7
7
|
|
|
8
|
-
type Deletion = { path: { string }, oldTable: any };
|
|
9
|
-
|
|
10
8
|
local remote = script.Parent:WaitForChild(Shared.REMOTE_NAME) :: RemoteEvent;
|
|
11
9
|
|
|
12
|
-
local function deepMergeAndApply(target: any, updates: any)
|
|
13
|
-
for key, value in updates do
|
|
14
|
-
if value == Shared.NIL_SENTINEL then
|
|
15
|
-
target[key] = nil;
|
|
16
|
-
elseif typeof(value) == 'table' and typeof(target[key]) == 'table' then
|
|
17
|
-
deepMergeAndApply(target[key], value);
|
|
18
|
-
else
|
|
19
|
-
target[key] = value;
|
|
20
|
-
end;
|
|
21
|
-
end;
|
|
22
|
-
end;
|
|
23
|
-
|
|
24
10
|
local Client = {};
|
|
25
11
|
Client.__index = Client;
|
|
26
12
|
|
|
@@ -70,99 +56,63 @@ function Client:observe(path: { string }?, callback: BaseReplion.Observer)
|
|
|
70
56
|
return self._base:observe(path, callback);
|
|
71
57
|
end;
|
|
72
58
|
|
|
73
|
-
function Client:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
59
|
+
function Client:_fireRecursive(signalNode: any, newValue: any, oldValue: any)
|
|
60
|
+
if not signalNode then return; end;
|
|
61
|
+
|
|
62
|
+
if signalNode._signal then
|
|
63
|
+
signalNode._signal:Fire(newValue, oldValue);
|
|
64
|
+
end;
|
|
65
|
+
|
|
66
|
+
for key, childNode in signalNode do
|
|
67
|
+
if key == '_signal' then continue; end;
|
|
68
|
+
if key == '_refCount' then continue; end;
|
|
69
|
+
|
|
70
|
+
local newChild = if typeof(newValue) == 'table' then newValue[key] else nil;
|
|
71
|
+
local oldChild = if typeof(oldValue) == 'table' then oldValue[key] else nil;
|
|
72
|
+
|
|
73
|
+
if newChild ~= nil or oldChild ~= nil then
|
|
74
|
+
self:_fireRecursive(childNode, newChild, oldChild);
|
|
75
|
+
end;
|
|
76
|
+
end;
|
|
87
77
|
end;
|
|
88
78
|
|
|
89
|
-
function Client:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
end;
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
signal:Fire(newValue, newValue); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
|
|
121
|
-
end;
|
|
122
|
-
|
|
123
|
-
if typeof(v) == 'table' then
|
|
124
|
-
self:_notifyRecursiveUpdates(nextPath, v);
|
|
125
|
-
end;
|
|
126
|
-
end;
|
|
79
|
+
function Client:_applyUpdatesRecursive(target: any, updates: any, signalNode: any)
|
|
80
|
+
local anyChanged = false;
|
|
81
|
+
|
|
82
|
+
for key, updateVal in updates do
|
|
83
|
+
local currentVal = target[key];
|
|
84
|
+
local nextSignalNode = if signalNode then signalNode[key] else nil;
|
|
85
|
+
|
|
86
|
+
local finalVal = updateVal;
|
|
87
|
+
if finalVal == Shared.NIL_SENTINEL then finalVal = nil; end;
|
|
88
|
+
|
|
89
|
+
if typeof(finalVal) == 'table' and typeof(currentVal) == 'table' then
|
|
90
|
+
if self:_applyUpdatesRecursive(currentVal, finalVal, nextSignalNode) then
|
|
91
|
+
anyChanged = true;
|
|
92
|
+
|
|
93
|
+
if nextSignalNode and nextSignalNode._signal then
|
|
94
|
+
nextSignalNode._signal:Fire(currentVal, currentVal);
|
|
95
|
+
end;
|
|
96
|
+
end;
|
|
97
|
+
else
|
|
98
|
+
if currentVal ~= finalVal then
|
|
99
|
+
target[key] = finalVal;
|
|
100
|
+
anyChanged = true;
|
|
101
|
+
|
|
102
|
+
if nextSignalNode then
|
|
103
|
+
self:_fireRecursive(nextSignalNode, finalVal, currentVal);
|
|
104
|
+
end;
|
|
105
|
+
end;
|
|
106
|
+
end;
|
|
107
|
+
end;
|
|
108
|
+
|
|
109
|
+
return anyChanged;
|
|
127
110
|
end;
|
|
128
111
|
|
|
129
112
|
function Client:_applyUpdates(updates: BaseReplion.GenericDataTable)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
local oldData = table.clone(self._base.data); -- Note: Shallow copy instead of deep copy.
|
|
134
|
-
|
|
135
|
-
deepMergeAndApply(self._base.data, updates);
|
|
136
|
-
|
|
137
|
-
local anyChanged = false;
|
|
138
|
-
|
|
139
|
-
for _, item in deletions do
|
|
140
|
-
self:_notifyDeepDeletion(item.path, item.oldTable);
|
|
141
|
-
end;
|
|
142
|
-
|
|
143
|
-
for key, newValue in updates do
|
|
144
|
-
local oldValue = oldData[key];
|
|
145
|
-
|
|
146
|
-
-- If it's a table, we assume it changed if it's in the updates list.
|
|
147
|
-
if typeof(newValue) == 'table' then
|
|
148
|
-
anyChanged = true;
|
|
149
|
-
local signal = self._base._signals[key];
|
|
150
|
-
if signal then
|
|
151
|
-
signal:Fire(self._base.data[key], self._base.data[key]); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
|
|
152
|
-
end;
|
|
153
|
-
self:_notifyRecursiveUpdates({key}, newValue);
|
|
154
|
-
elseif self._base.data[key] ~= oldValue then
|
|
155
|
-
anyChanged = true;
|
|
156
|
-
local signal = self._base._signals[key];
|
|
157
|
-
if signal then
|
|
158
|
-
signal:Fire(self._base.data[key], oldValue);
|
|
159
|
-
end;
|
|
160
|
-
end;
|
|
161
|
-
end;
|
|
162
|
-
|
|
163
|
-
if anyChanged then
|
|
164
|
-
self._base._allSignal:Fire(self._base.data, updates);
|
|
165
|
-
end;
|
|
113
|
+
local changed = self:_applyUpdatesRecursive(self._base.data, updates, self._base._signalRoot);
|
|
114
|
+
if not changed then return; end;
|
|
115
|
+
self._base._allSignal:Fire(self._base.data, updates);
|
|
166
116
|
end;
|
|
167
117
|
|
|
168
118
|
function Client:destroy()
|
package/src/Server.lua
CHANGED
|
@@ -61,7 +61,6 @@ Server._globalReplions = {} :: { [string]: Replion };
|
|
|
61
61
|
function Server._getPlayerReplion(player: Player, channel: string)
|
|
62
62
|
local playerReplions = Server._activeReplions[player];
|
|
63
63
|
if not playerReplions then return nil; end;
|
|
64
|
-
|
|
65
64
|
return playerReplions[channel];
|
|
66
65
|
end;
|
|
67
66
|
|
|
@@ -108,80 +107,65 @@ function Server:get(path: { string }?)
|
|
|
108
107
|
return self._base:get(path);
|
|
109
108
|
end;
|
|
110
109
|
|
|
111
|
-
function Server:_notifyDeep(
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
if typeof(newValue) == 'table' then
|
|
115
|
-
for k, v in newValue do
|
|
116
|
-
local nextPath = table.clone(currentPath);
|
|
117
|
-
table.insert(nextPath, k);
|
|
118
|
-
|
|
119
|
-
local nextOld = if typeof(oldValue) == 'table' then oldValue[k] else nil;
|
|
120
|
-
|
|
121
|
-
local signalKey = Shared.getSignalKey(nextPath);
|
|
122
|
-
local signal = signals[signalKey];
|
|
123
|
-
if signal then
|
|
124
|
-
signal:Fire(v, nextOld);
|
|
125
|
-
end;
|
|
126
|
-
|
|
127
|
-
self:_notifyDeep(nextPath, v, nextOld);
|
|
128
|
-
end;
|
|
129
|
-
end;
|
|
110
|
+
function Server:_notifyDeep(signalNode: any, newValue: any, oldValue: any)
|
|
111
|
+
if not signalNode then return; end;
|
|
130
112
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
113
|
+
if signalNode._signal then
|
|
114
|
+
signalNode._signal:Fire(newValue, oldValue);
|
|
115
|
+
end;
|
|
134
116
|
|
|
135
|
-
|
|
136
|
-
|
|
117
|
+
for key, childNode in signalNode do
|
|
118
|
+
if key == '_signal' then continue; end;
|
|
119
|
+
if key == '_refCount' then continue; end;
|
|
137
120
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if signal then
|
|
141
|
-
signal:Fire(nil, v);
|
|
142
|
-
end;
|
|
121
|
+
local nextNew = if typeof(newValue) == 'table' then newValue[key] else nil;
|
|
122
|
+
local nextOld = if typeof(oldValue) == 'table' then oldValue[key] else nil;
|
|
143
123
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
124
|
+
if nextNew ~= nil or nextOld ~= nil then
|
|
125
|
+
self:_notifyDeep(childNode, nextNew, nextOld);
|
|
126
|
+
end;
|
|
127
|
+
end;
|
|
147
128
|
end;
|
|
148
129
|
|
|
149
130
|
function Server:set(path: { string }, value: any)
|
|
150
131
|
if self._destroyed then return; end;
|
|
151
132
|
|
|
152
|
-
local topKey = path[1];
|
|
153
|
-
|
|
154
133
|
local oldValue = Shared.getNestedValue(self._base.data, path);
|
|
155
134
|
local newValue = if typeof(value) == 'function' then value(oldValue) else value;
|
|
156
135
|
if oldValue == newValue then return; end;
|
|
157
136
|
|
|
158
|
-
local oldTop = self._base.data[topKey];
|
|
159
|
-
|
|
160
137
|
setNestedValue(self._base.data, path, newValue);
|
|
161
138
|
queueNestedUpdate(self._queuedUpdates, path, newValue);
|
|
162
139
|
self:_scheduleUpdatesFlush();
|
|
163
140
|
|
|
164
141
|
do
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if topSignal then
|
|
168
|
-
topSignal:Fire(self._base.data[topKey], oldTop);
|
|
169
|
-
end;
|
|
142
|
+
local rootKey = path[1];
|
|
143
|
+
local currentSignalNode = self._base._signalRoot;
|
|
170
144
|
|
|
171
|
-
|
|
172
|
-
if signalKey ~= topKey then
|
|
173
|
-
local pathSignal = signals[signalKey];
|
|
174
|
-
if pathSignal then
|
|
175
|
-
pathSignal:Fire(newValue, oldValue)
|
|
176
|
-
end;
|
|
177
|
-
end;
|
|
145
|
+
local rootSignalNode = if currentSignalNode then currentSignalNode[rootKey] else nil;
|
|
178
146
|
|
|
179
|
-
|
|
147
|
+
for _, key in path do
|
|
148
|
+
if not currentSignalNode then break; end;
|
|
149
|
+
currentSignalNode = currentSignalNode[key];
|
|
150
|
+
end;
|
|
151
|
+
|
|
152
|
+
if rootSignalNode and rootSignalNode ~= currentSignalNode and rootSignalNode._signal then
|
|
153
|
+
local rootValue = self._base.data[rootKey];
|
|
154
|
+
rootSignalNode._signal:Fire(rootValue, rootValue);
|
|
155
|
+
end;
|
|
156
|
+
|
|
157
|
+
if currentSignalNode then
|
|
158
|
+
self:_notifyDeep(currentSignalNode, newValue, oldValue);
|
|
159
|
+
end;
|
|
180
160
|
|
|
181
161
|
local thisUpdate = {};
|
|
182
162
|
setNestedValue(thisUpdate, path, newValue);
|
|
183
163
|
self._base._allSignal:Fire(self._base.data, thisUpdate);
|
|
184
164
|
end;
|
|
165
|
+
|
|
166
|
+
if newValue == nil then
|
|
167
|
+
self._base:_pruneSignalTree(path);
|
|
168
|
+
end;
|
|
185
169
|
end;
|
|
186
170
|
|
|
187
171
|
function Server:destroy()
|
package/src/Shared.lua
CHANGED
|
@@ -13,9 +13,4 @@ function Shared.getNestedValue(root: any, path: { string })
|
|
|
13
13
|
return current;
|
|
14
14
|
end;
|
|
15
15
|
|
|
16
|
-
function Shared.getSignalKey(key: string | { string })
|
|
17
|
-
if typeof(key) == 'string' then return key; end;
|
|
18
|
-
return table.concat(key, '\0');
|
|
19
|
-
end;
|
|
20
|
-
|
|
21
16
|
return Shared;
|