@rbxts/replion 1.0.16 → 1.0.18
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 -5
- package/src/BaseReplion.lua +56 -0
- package/src/Client.lua +22 -36
- package/src/Server.lua +23 -41
- package/src/index.d.ts +4 -4
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/replion",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/init.lua",
|
|
6
6
|
"scripts": {},
|
|
@@ -20,9 +20,5 @@
|
|
|
20
20
|
"@rbxts/types": "^1.0.896",
|
|
21
21
|
"roblox-ts": "^3.0.0",
|
|
22
22
|
"typescript": "^5.9.3"
|
|
23
|
-
},
|
|
24
|
-
"dependencies": {
|
|
25
|
-
"@rbxts/sift": "^0.0.11",
|
|
26
|
-
"@rbxts/sleitnick-signal": "^1.0.8"
|
|
27
23
|
}
|
|
28
24
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
--!strict
|
|
2
|
+
export type GenericDataTable = { [string]: any };
|
|
3
|
+
|
|
4
|
+
local isTypeScriptEnv = script.Parent.Name == 'src';
|
|
5
|
+
local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
|
|
6
|
+
local Shared = require(script.Parent.Shared);
|
|
7
|
+
local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
|
|
8
|
+
|
|
9
|
+
local BaseReplion = {};
|
|
10
|
+
BaseReplion.__index = BaseReplion;
|
|
11
|
+
|
|
12
|
+
function BaseReplion.new(data: GenericDataTable)
|
|
13
|
+
local self = setmetatable({}, BaseReplion);
|
|
14
|
+
|
|
15
|
+
self.data = data;
|
|
16
|
+
|
|
17
|
+
self._signals = {} :: { [string]: any };
|
|
18
|
+
self._allSignal = Signal.new();
|
|
19
|
+
|
|
20
|
+
return self;
|
|
21
|
+
end;
|
|
22
|
+
|
|
23
|
+
function BaseReplion:get(path: { string }?)
|
|
24
|
+
if not path then return self.data; end;
|
|
25
|
+
return Shared.getNestedValue(self.data, path);
|
|
26
|
+
end;
|
|
27
|
+
|
|
28
|
+
function BaseReplion:observe(path: { string }?, callback: (any, any) -> ())
|
|
29
|
+
if not path then
|
|
30
|
+
task.spawn(callback, self.data, nil);
|
|
31
|
+
return self._allSignal:Connect(callback);
|
|
32
|
+
end;
|
|
33
|
+
|
|
34
|
+
local signalKey = Shared.getSignalKey(path);
|
|
35
|
+
if not self._signals[signalKey] then
|
|
36
|
+
self._signals[signalKey] = Signal.new();
|
|
37
|
+
end;
|
|
38
|
+
|
|
39
|
+
local initialValue = Shared.getNestedValue(self.data, path);
|
|
40
|
+
|
|
41
|
+
task.spawn(callback, initialValue, nil);
|
|
42
|
+
local connection = self._signals[signalKey]:Connect(callback);
|
|
43
|
+
return function()
|
|
44
|
+
connection:Disconnect();
|
|
45
|
+
end;
|
|
46
|
+
end;
|
|
47
|
+
|
|
48
|
+
function BaseReplion:destroy()
|
|
49
|
+
self._allSignal:Destroy();
|
|
50
|
+
for _, signal in self._signals do
|
|
51
|
+
signal:Destroy();
|
|
52
|
+
end;
|
|
53
|
+
self._signals = {};
|
|
54
|
+
end;
|
|
55
|
+
|
|
56
|
+
return BaseReplion;
|
package/src/Client.lua
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
--!strict
|
|
2
2
|
local isTypeScriptEnv = script.Parent.Name == 'src';
|
|
3
3
|
local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
|
|
4
|
+
local BaseReplion = require(script.Parent.BaseReplion)
|
|
4
5
|
local Shared = require(script.Parent.Shared);
|
|
5
6
|
local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
|
|
6
7
|
|
|
@@ -52,33 +53,18 @@ function Client.new(channel: string, data: Shared.GenericDataTable)
|
|
|
52
53
|
local self = setmetatable({}, Client);
|
|
53
54
|
|
|
54
55
|
self.channel = channel;
|
|
55
|
-
|
|
56
|
-
self.
|
|
57
|
-
self._allSignal = Signal.new();
|
|
56
|
+
|
|
57
|
+
self._base = BaseReplion.new(data);
|
|
58
58
|
|
|
59
59
|
return self;
|
|
60
60
|
end;
|
|
61
61
|
|
|
62
62
|
function Client:get(path: { string }?)
|
|
63
|
-
|
|
64
|
-
return Shared.getNestedValue(self.data, path);
|
|
63
|
+
return self._base:get(path);
|
|
65
64
|
end;
|
|
66
65
|
|
|
67
66
|
function Client:observe(path: { string }?, callback: Observer)
|
|
68
|
-
|
|
69
|
-
task.spawn(callback, self.data, nil);
|
|
70
|
-
return self._allSignal:Connect(callback);
|
|
71
|
-
end;
|
|
72
|
-
|
|
73
|
-
local signalKey = Shared.getSignalKey(path);
|
|
74
|
-
if not self._signals[signalKey] then
|
|
75
|
-
self._signals[signalKey] = Signal.new();
|
|
76
|
-
end;
|
|
77
|
-
|
|
78
|
-
local initialValue = Shared.getNestedValue(self.data, path);
|
|
79
|
-
|
|
80
|
-
task.spawn(callback, initialValue, nil);
|
|
81
|
-
return self._signals[signalKey]:Connect(callback);
|
|
67
|
+
return self._base:observe(path, callback);
|
|
82
68
|
end;
|
|
83
69
|
|
|
84
70
|
function Client:_collectDeletions(target: any, updates: any, path: { string }, out: { Deletion })
|
|
@@ -98,12 +84,14 @@ function Client:_collectDeletions(target: any, updates: any, path: { string }, o
|
|
|
98
84
|
end;
|
|
99
85
|
|
|
100
86
|
function Client:_notifyDeepDeletion(path: { string }, oldTable: any)
|
|
87
|
+
local signals = self._base._signals;
|
|
88
|
+
|
|
101
89
|
for k, v in oldTable do
|
|
102
90
|
local nextPath = table.clone(path);
|
|
103
91
|
table.insert(nextPath, k);
|
|
104
92
|
|
|
105
93
|
local signalKey = Shared.getSignalKey(nextPath);
|
|
106
|
-
local signal =
|
|
94
|
+
local signal = signals[signalKey];
|
|
107
95
|
if signal then
|
|
108
96
|
signal:Fire(nil, v);
|
|
109
97
|
end;
|
|
@@ -115,15 +103,17 @@ function Client:_notifyDeepDeletion(path: { string }, oldTable: any)
|
|
|
115
103
|
end;
|
|
116
104
|
|
|
117
105
|
function Client:_notifyRecursiveUpdates(currentPath: { string }, updateTree: any)
|
|
106
|
+
local signals = self._base._signals;
|
|
107
|
+
|
|
118
108
|
for k, v in updateTree do
|
|
119
109
|
local nextPath = table.clone(currentPath);
|
|
120
110
|
table.insert(nextPath, k);
|
|
121
111
|
|
|
122
112
|
local signalKey = Shared.getSignalKey(nextPath);
|
|
123
|
-
local signal =
|
|
113
|
+
local signal = signals[signalKey];
|
|
124
114
|
|
|
125
115
|
if signal then
|
|
126
|
-
local newValue = Shared.getNestedValue(self.data, nextPath);
|
|
116
|
+
local newValue = Shared.getNestedValue(self._base.data, nextPath);
|
|
127
117
|
signal:Fire(newValue, newValue); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
|
|
128
118
|
end;
|
|
129
119
|
|
|
@@ -135,11 +125,11 @@ end;
|
|
|
135
125
|
|
|
136
126
|
function Client:_applyUpdates(updates: Shared.GenericDataTable)
|
|
137
127
|
local deletions: { Deletion } = {};
|
|
138
|
-
self:_collectDeletions(self.data, updates, {}, deletions);
|
|
128
|
+
self:_collectDeletions(self._base.data, updates, {}, deletions);
|
|
139
129
|
|
|
140
|
-
local oldData = table.clone(self.data); -- Note: Shallow copy instead of deep copy.
|
|
130
|
+
local oldData = table.clone(self._base.data); -- Note: Shallow copy instead of deep copy.
|
|
141
131
|
|
|
142
|
-
deepMergeAndApply(self.data, updates);
|
|
132
|
+
deepMergeAndApply(self._base.data, updates);
|
|
143
133
|
|
|
144
134
|
local anyChanged = false;
|
|
145
135
|
|
|
@@ -153,31 +143,27 @@ function Client:_applyUpdates(updates: Shared.GenericDataTable)
|
|
|
153
143
|
-- If it's a table, we assume it changed if it's in the updates list.
|
|
154
144
|
if typeof(newValue) == 'table' then
|
|
155
145
|
anyChanged = true;
|
|
156
|
-
local signal = self._signals[key];
|
|
146
|
+
local signal = self._base._signals[key];
|
|
157
147
|
if signal then
|
|
158
|
-
signal:Fire(self.data[key], self.data[key]); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
|
|
148
|
+
signal:Fire(self._base.data[key], self._base.data[key]); -- Warning: Sends improper 'old' argument due to shallow copy optimization.
|
|
159
149
|
end;
|
|
160
150
|
self:_notifyRecursiveUpdates({key}, newValue);
|
|
161
|
-
elseif self.data[key] ~= oldValue then
|
|
151
|
+
elseif self._base.data[key] ~= oldValue then
|
|
162
152
|
anyChanged = true;
|
|
163
|
-
local signal = self._signals[key];
|
|
153
|
+
local signal = self._base._signals[key];
|
|
164
154
|
if signal then
|
|
165
|
-
signal:Fire(self.data[key], oldValue);
|
|
155
|
+
signal:Fire(self._base.data[key], oldValue);
|
|
166
156
|
end;
|
|
167
157
|
end;
|
|
168
158
|
end;
|
|
169
159
|
|
|
170
160
|
if anyChanged then
|
|
171
|
-
self._allSignal:Fire(self.data, updates);
|
|
161
|
+
self._base._allSignal:Fire(self._base.data, updates);
|
|
172
162
|
end;
|
|
173
163
|
end;
|
|
174
164
|
|
|
175
165
|
function Client:destroy()
|
|
176
|
-
self.
|
|
177
|
-
for _, signal in self._signals do
|
|
178
|
-
signal:Destroy();
|
|
179
|
-
end;
|
|
180
|
-
self._signals = {};
|
|
166
|
+
self._base:destroy();
|
|
181
167
|
end;
|
|
182
168
|
|
|
183
169
|
remote.OnClientEvent:Connect(function(type, channel, payload)
|
package/src/Server.lua
CHANGED
|
@@ -3,9 +3,9 @@ local Players = game:GetService('Players');
|
|
|
3
3
|
|
|
4
4
|
local isTypeScriptEnv = script.Parent.Name == 'src';
|
|
5
5
|
local dependencies = if isTypeScriptEnv then script.Parent.Parent.Parent else script.Parent.Parent;
|
|
6
|
+
local BaseReplion = require(script.Parent.BaseReplion)
|
|
6
7
|
local Shared = require(script.Parent.Shared);
|
|
7
8
|
local Sift = require(isTypeScriptEnv and dependencies.sift.out or dependencies.Sift);
|
|
8
|
-
local Signal = require(isTypeScriptEnv and dependencies['sleitnick-signal'] or dependencies.Signal);
|
|
9
9
|
|
|
10
10
|
type ServerConfig = {
|
|
11
11
|
channel: string;
|
|
@@ -76,10 +76,8 @@ function Server.new(config: ServerConfig)
|
|
|
76
76
|
|
|
77
77
|
self.player = config.replicateTo;
|
|
78
78
|
self.channel = config.channel;
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
self._signals = {} :: { [string]: any };
|
|
82
|
-
self._allSignal = Signal.new();
|
|
79
|
+
|
|
80
|
+
self._base = BaseReplion.new(Sift.Dictionary.copyDeep(config.data));
|
|
83
81
|
|
|
84
82
|
self._queuedUpdates = {} :: Shared.GenericDataTable;
|
|
85
83
|
self._isQueued = false;
|
|
@@ -100,23 +98,16 @@ function Server.new(config: ServerConfig)
|
|
|
100
98
|
end;
|
|
101
99
|
|
|
102
100
|
function Server:observe(path: { string }?, callback: Observer)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return self._allSignal:Connect(callback);
|
|
106
|
-
end;
|
|
107
|
-
|
|
108
|
-
local signalKey = Shared.getSignalKey(path);
|
|
109
|
-
if not self._signals[signalKey] then
|
|
110
|
-
self._signals[signalKey] = Signal.new();
|
|
111
|
-
end;
|
|
112
|
-
|
|
113
|
-
local initialValue = Shared.getNestedValue(self.data, path);
|
|
101
|
+
return self._base:observe(path, callback);
|
|
102
|
+
end;
|
|
114
103
|
|
|
115
|
-
|
|
116
|
-
return self.
|
|
104
|
+
function Server:get(path: { string }?)
|
|
105
|
+
return self._base:get(path);
|
|
117
106
|
end;
|
|
118
107
|
|
|
119
108
|
function Server:_notifyDeep(currentPath: { string }, newValue: any, oldValue: any)
|
|
109
|
+
local signals = self._base._signals;
|
|
110
|
+
|
|
120
111
|
if typeof(newValue) == 'table' then
|
|
121
112
|
for k, v in newValue do
|
|
122
113
|
local nextPath = table.clone(currentPath);
|
|
@@ -125,7 +116,7 @@ function Server:_notifyDeep(currentPath: { string }, newValue: any, oldValue: an
|
|
|
125
116
|
local nextOld = if typeof(oldValue) == 'table' then oldValue[k] else nil;
|
|
126
117
|
|
|
127
118
|
local signalKey = Shared.getSignalKey(nextPath);
|
|
128
|
-
local signal =
|
|
119
|
+
local signal = signals[signalKey];
|
|
129
120
|
if signal then
|
|
130
121
|
signal:Fire(v, nextOld);
|
|
131
122
|
end;
|
|
@@ -142,7 +133,7 @@ function Server:_notifyDeep(currentPath: { string }, newValue: any, oldValue: an
|
|
|
142
133
|
table.insert(nextPath, k);
|
|
143
134
|
|
|
144
135
|
local signalKey = Shared.getSignalKey(nextPath);
|
|
145
|
-
local signal =
|
|
136
|
+
local signal = signals[signalKey];
|
|
146
137
|
if signal then
|
|
147
138
|
signal:Fire(nil, v);
|
|
148
139
|
end;
|
|
@@ -157,25 +148,26 @@ function Server:set(path: { string }, value: any)
|
|
|
157
148
|
|
|
158
149
|
local topKey = path[1];
|
|
159
150
|
|
|
160
|
-
local oldValue = Shared.getNestedValue(self.data, path);
|
|
151
|
+
local oldValue = Shared.getNestedValue(self._base.data, path);
|
|
161
152
|
local newValue = if typeof(value) == 'function' then value(oldValue) else value;
|
|
162
153
|
if oldValue == newValue then return; end;
|
|
163
154
|
|
|
164
|
-
local oldTop = self.data[topKey];
|
|
155
|
+
local oldTop = self._base.data[topKey];
|
|
165
156
|
|
|
166
|
-
setNestedValue(self.data, path, newValue);
|
|
157
|
+
setNestedValue(self._base.data, path, newValue);
|
|
167
158
|
queueNestedUpdate(self._queuedUpdates, path, newValue);
|
|
168
159
|
self:_scheduleUpdatesFlush();
|
|
169
160
|
|
|
170
161
|
do
|
|
171
|
-
local
|
|
162
|
+
local signals = self._base._signals;
|
|
163
|
+
local topSignal = signals[topKey];
|
|
172
164
|
if topSignal then
|
|
173
|
-
topSignal:Fire(self.data[topKey], oldTop);
|
|
165
|
+
topSignal:Fire(self._base.data[topKey], oldTop);
|
|
174
166
|
end;
|
|
175
167
|
|
|
176
168
|
local signalKey = Shared.getSignalKey(path);
|
|
177
169
|
if signalKey ~= topKey then
|
|
178
|
-
local pathSignal =
|
|
170
|
+
local pathSignal = signals[signalKey];
|
|
179
171
|
if pathSignal then
|
|
180
172
|
pathSignal:Fire(newValue, oldValue)
|
|
181
173
|
end;
|
|
@@ -185,26 +177,16 @@ function Server:set(path: { string }, value: any)
|
|
|
185
177
|
|
|
186
178
|
local thisUpdate = {};
|
|
187
179
|
setNestedValue(thisUpdate, path, newValue);
|
|
188
|
-
self._allSignal:Fire(self.data, thisUpdate);
|
|
180
|
+
self._base._allSignal:Fire(self._base.data, thisUpdate);
|
|
189
181
|
end;
|
|
190
182
|
end;
|
|
191
183
|
|
|
192
|
-
function Server:get(path: { string }?)
|
|
193
|
-
if not path then return self.data; end;
|
|
194
|
-
return Shared.getNestedValue(self.data, path);
|
|
195
|
-
end;
|
|
196
|
-
|
|
197
184
|
function Server:destroy()
|
|
198
185
|
self._destroyed = true;
|
|
199
|
-
self.data = {};
|
|
200
186
|
self._queuedUpdates = {};
|
|
201
|
-
|
|
202
|
-
self.
|
|
203
|
-
|
|
204
|
-
signal:Destroy();
|
|
205
|
-
end;
|
|
206
|
-
self._signals = {};
|
|
207
|
-
|
|
187
|
+
|
|
188
|
+
self._base:destroy();
|
|
189
|
+
|
|
208
190
|
if self.player then
|
|
209
191
|
local playerReplions = self._activeReplions[self.player];
|
|
210
192
|
if not playerReplions then return; end;
|
|
@@ -246,7 +228,7 @@ function Server:_sendInitial(player: Player?)
|
|
|
246
228
|
local receiver = player or self.player;
|
|
247
229
|
if not receiver then return; end;
|
|
248
230
|
|
|
249
|
-
remote:FireClient(receiver, 'I', self.channel, self.data);
|
|
231
|
+
remote:FireClient(receiver, 'I', self.channel, self._base.data);
|
|
250
232
|
end;
|
|
251
233
|
|
|
252
234
|
remote.OnServerEvent:Connect(function(player, action, ...)
|
package/src/index.d.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { Connection } from '@rbxts/sleitnick-signal';
|
|
2
|
-
|
|
3
1
|
declare namespace Replion {
|
|
4
2
|
/**
|
|
5
3
|
* Recursively builds a union of all valid path tuples for object T.
|
|
@@ -20,6 +18,8 @@ declare namespace Replion {
|
|
|
20
18
|
: never
|
|
21
19
|
: never;
|
|
22
20
|
|
|
21
|
+
type Cleanup = () => void;
|
|
22
|
+
|
|
23
23
|
interface ServerConfig<T extends object> {
|
|
24
24
|
channel: string;
|
|
25
25
|
replicateTo?: Player;
|
|
@@ -30,11 +30,11 @@ declare namespace Replion {
|
|
|
30
30
|
get(): T;
|
|
31
31
|
get<P extends Path<T>>(path: P): PathValue<T, P>;
|
|
32
32
|
|
|
33
|
-
observe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void):
|
|
33
|
+
observe(key: undefined, callback: (newValue: T, oldValue: Partial<T>) => void): Cleanup;
|
|
34
34
|
observe<P extends Path<T>>(
|
|
35
35
|
path: P,
|
|
36
36
|
callback: (newValue: PathValue<T, P>, oldValue: PathValue<T, P> | undefined) => void,
|
|
37
|
-
):
|
|
37
|
+
): Cleanup;
|
|
38
38
|
|
|
39
39
|
destroy(): void;
|
|
40
40
|
}
|