@trioforge/crucible 0.1.0 → 0.2.0
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/out/client/index.d.ts +7 -0
- package/out/client/init.luau +36 -1
- package/out/configs.d.ts +37 -0
- package/out/configs.luau +46 -0
- package/out/data.d.ts +224 -0
- package/out/data.luau +97 -0
- package/out/emit/configs.d.ts +4 -0
- package/out/emit/configs.luau +22 -0
- package/out/emit/data.d.ts +6 -0
- package/out/emit/data.luau +19 -0
- package/out/emit/index.d.ts +5 -1
- package/out/emit/init.luau +23 -7
- package/out/emit/network.d.ts +6 -0
- package/out/emit/network.luau +36 -0
- package/out/emit/replicated.d.ts +4 -0
- package/out/emit/replicated.luau +13 -0
- package/out/index.d.ts +8 -0
- package/out/init.luau +23 -0
- package/out/internal/binding.luau +5 -0
- package/out/internal/boot.luau +107 -8
- package/out/internal/configs/limits.d.ts +10 -0
- package/out/internal/configs/limits.luau +25 -0
- package/out/internal/configs/port.d.ts +29 -0
- package/out/internal/configs/port.luau +99 -0
- package/out/internal/configs/runtime.d.ts +21 -0
- package/out/internal/configs/runtime.luau +981 -0
- package/out/internal/data/admission.d.ts +1 -0
- package/out/internal/data/admission.luau +673 -0
- package/out/internal/data/declarations.d.ts +1 -0
- package/out/internal/data/declarations.luau +384 -0
- package/out/internal/data/envelope.d.ts +1 -0
- package/out/internal/data/envelope.luau +318 -0
- package/out/internal/data/handles.d.ts +1 -0
- package/out/internal/data/handles.luau +255 -0
- package/out/internal/data/inbox-transforms.d.ts +1 -0
- package/out/internal/data/inbox-transforms.luau +458 -0
- package/out/internal/data/inbox.d.ts +1 -0
- package/out/internal/data/inbox.luau +437 -0
- package/out/internal/data/lease.d.ts +1 -0
- package/out/internal/data/lease.luau +304 -0
- package/out/internal/data/limits.d.ts +1 -0
- package/out/internal/data/limits.luau +234 -0
- package/out/internal/data/messages.d.ts +1 -0
- package/out/internal/data/messages.luau +69 -0
- package/out/internal/data/operation-id.d.ts +1 -0
- package/out/internal/data/operation-id.luau +61 -0
- package/out/internal/data/paths.d.ts +2 -0
- package/out/internal/data/paths.luau +323 -0
- package/out/internal/data/port.d.ts +1 -0
- package/out/internal/data/port.luau +290 -0
- package/out/internal/data/profiles.d.ts +1 -0
- package/out/internal/data/profiles.luau +862 -0
- package/out/internal/data/receipts.d.ts +1 -0
- package/out/internal/data/receipts.luau +190 -0
- package/out/internal/data/record-bindings.d.ts +1 -0
- package/out/internal/data/record-bindings.luau +92 -0
- package/out/internal/data/record-declarations.d.ts +1 -0
- package/out/internal/data/record-declarations.luau +140 -0
- package/out/internal/data/records.d.ts +1 -0
- package/out/internal/data/records.luau +400 -0
- package/out/internal/data/runtime.d.ts +1 -0
- package/out/internal/data/runtime.luau +918 -0
- package/out/internal/data/scheduler.d.ts +1 -0
- package/out/internal/data/scheduler.luau +507 -0
- package/out/internal/data/session.d.ts +1 -0
- package/out/internal/data/session.luau +234 -0
- package/out/internal/data/shutdown.d.ts +1 -0
- package/out/internal/data/shutdown.luau +91 -0
- package/out/internal/data/waiters.d.ts +1 -0
- package/out/internal/data/waiters.luau +65 -0
- package/out/internal/lifecycle.luau +45 -0
- package/out/internal/network/catalog.d.ts +26 -0
- package/out/internal/network/catalog.luau +182 -0
- package/out/internal/network/claims.d.ts +1 -0
- package/out/internal/network/claims.luau +154 -0
- package/out/internal/network/client-session.d.ts +1 -0
- package/out/internal/network/client-session.luau +403 -0
- package/out/internal/network/codec.d.ts +63 -0
- package/out/internal/network/codec.luau +1183 -0
- package/out/internal/network/envelope.d.ts +1 -0
- package/out/internal/network/envelope.luau +247 -0
- package/out/internal/network/limits.d.ts +26 -0
- package/out/internal/network/limits.luau +55 -0
- package/out/internal/network/port.d.ts +1 -0
- package/out/internal/network/port.luau +19 -0
- package/out/internal/network/roblox-network-port.d.ts +1 -0
- package/out/internal/network/roblox-network-port.luau +105 -0
- package/out/internal/network/schema.d.ts +72 -0
- package/out/internal/network/schema.luau +386 -0
- package/out/internal/network/server-session.d.ts +1 -0
- package/out/internal/network/server-session.luau +393 -0
- package/out/internal/network/session.d.ts +1 -0
- package/out/internal/network/session.luau +266 -0
- package/out/internal/reactive/context.d.ts +1 -0
- package/out/internal/reactive/context.luau +149 -0
- package/out/internal/reactive/flush.d.ts +1 -0
- package/out/internal/reactive/flush.luau +355 -0
- package/out/internal/reactive/graph.d.ts +1 -0
- package/out/internal/reactive/graph.luau +626 -0
- package/out/internal/reactive/limits.d.ts +1 -0
- package/out/internal/reactive/limits.luau +54 -0
- package/out/internal/reactive/ownership.d.ts +1 -0
- package/out/internal/reactive/ownership.luau +626 -0
- package/out/internal/registry.luau +10 -1
- package/out/internal/sync/client-sync.d.ts +1 -0
- package/out/internal/sync/client-sync.luau +157 -0
- package/out/internal/sync/declarations.d.ts +1 -0
- package/out/internal/sync/declarations.luau +276 -0
- package/out/internal/sync/frame.d.ts +1 -0
- package/out/internal/sync/frame.luau +91 -0
- package/out/internal/sync/server-sync.d.ts +1 -0
- package/out/internal/sync/server-sync.luau +502 -0
- package/out/internal/sync/values.d.ts +1 -0
- package/out/internal/sync/values.luau +930 -0
- package/out/network.d.ts +66 -0
- package/out/network.luau +24 -0
- package/out/reactive.d.ts +76 -0
- package/out/reactive.luau +136 -0
- package/out/replicated.d.ts +38 -0
- package/out/replicated.luau +45 -0
- package/out/server/data.d.ts +54 -0
- package/out/server/data.luau +44 -0
- package/out/server/index.d.ts +16 -0
- package/out/server/init.luau +49 -3
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +13 -2
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/index.js +37 -11
- package/transformer/network.cjs +961 -0
- package/vide/package.json +4 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local ClientReady = TS.import(script, script.Parent.Parent.Parent, "network").ClientReady
|
|
4
|
+
local readClaim = TS.import(script, script.Parent.Parent, "construction-claim").readClaim
|
|
5
|
+
local definitionOf = TS.import(script, script.Parent, "catalog").definitionOf
|
|
6
|
+
--* @internal
|
|
7
|
+
--* @internal
|
|
8
|
+
local session
|
|
9
|
+
local frozen = false
|
|
10
|
+
local receivers = {}
|
|
11
|
+
local ready = {}
|
|
12
|
+
local live = {}
|
|
13
|
+
local function retire(claim)
|
|
14
|
+
local _claim = claim
|
|
15
|
+
-- ▼ Set.delete ▼
|
|
16
|
+
local _valueExisted = live[_claim] ~= nil
|
|
17
|
+
live[_claim] = nil
|
|
18
|
+
-- ▲ Set.delete ▲
|
|
19
|
+
if not _valueExisted then
|
|
20
|
+
return nil
|
|
21
|
+
end
|
|
22
|
+
if claim.target == ClientReady then
|
|
23
|
+
local _claim_1 = claim
|
|
24
|
+
local _arg0 = (table.find(ready, _claim_1) or 0) - 1
|
|
25
|
+
table.remove(ready, _arg0 + 1)
|
|
26
|
+
else
|
|
27
|
+
local _target = claim.target
|
|
28
|
+
receivers[_target] = nil
|
|
29
|
+
end
|
|
30
|
+
local _result = session
|
|
31
|
+
if _result ~= nil then
|
|
32
|
+
_result.retired(claim)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
--[[
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
* Re-checks what the transformer checked, because untransformed or cast code skips it. A violation throws from the
|
|
39
|
+
* constructor and fails boot.
|
|
40
|
+
|
|
41
|
+
]]
|
|
42
|
+
local function claim(owner, field, target, callback)
|
|
43
|
+
local state = readClaim()
|
|
44
|
+
if state == nil or state.object ~= owner then
|
|
45
|
+
error("receive() must initialize a readonly field of the Subsystem being constructed")
|
|
46
|
+
end
|
|
47
|
+
local record = state.record
|
|
48
|
+
local label = `{record.id}.{field}`
|
|
49
|
+
if record.kind ~= 0 then
|
|
50
|
+
error(`{label}: receive() must initialize a readonly field of a Subsystem; behaviours do not receive`)
|
|
51
|
+
end
|
|
52
|
+
if frozen then
|
|
53
|
+
error(`{label}: receive() after the network commit`)
|
|
54
|
+
end
|
|
55
|
+
local _result = session
|
|
56
|
+
if _result ~= nil then
|
|
57
|
+
_result = _result.side
|
|
58
|
+
end
|
|
59
|
+
local _condition = _result
|
|
60
|
+
if _condition == nil then
|
|
61
|
+
_condition = error(`{label}: receive() before the network session opened`)
|
|
62
|
+
end
|
|
63
|
+
local side = _condition
|
|
64
|
+
local _callback = callback
|
|
65
|
+
if not (type(_callback) == "function") then
|
|
66
|
+
error(`{label}: receive() needs a callback`)
|
|
67
|
+
end
|
|
68
|
+
if target == ClientReady then
|
|
69
|
+
if side ~= "server" then
|
|
70
|
+
error(`{label}: ClientReady is received in a server Subsystem.`)
|
|
71
|
+
end
|
|
72
|
+
for _, other in ready do
|
|
73
|
+
if other.owner == state then
|
|
74
|
+
error(`{label}: {other.label} already receives ClientReady; a Subsystem holds one`)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
local definition = definitionOf(target) or error(`{label}: receive() needs a message declared with toServer, toClient or askServer`)
|
|
79
|
+
local serverSide = definition.kind ~= "to-client"
|
|
80
|
+
if serverSide ~= (side == "server") then
|
|
81
|
+
error(if serverSide then `{label}: {definition.name} is client-to-server; receive it in a server Subsystem.` else `{label}: {definition.name} is server-to-client; receive it in a client Subsystem.`)
|
|
82
|
+
end
|
|
83
|
+
local other = receivers[definition]
|
|
84
|
+
if other ~= nil then
|
|
85
|
+
error(`{definition.name} has two receivers: {other.label} and {label}`)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
local entry = {
|
|
89
|
+
owner = state,
|
|
90
|
+
label = label,
|
|
91
|
+
target = target,
|
|
92
|
+
callback = callback,
|
|
93
|
+
}
|
|
94
|
+
if target == ClientReady then
|
|
95
|
+
table.insert(ready, entry)
|
|
96
|
+
else
|
|
97
|
+
local _target = target
|
|
98
|
+
receivers[_target] = entry
|
|
99
|
+
end
|
|
100
|
+
live[entry] = true
|
|
101
|
+
if state.subscriptions == nil then
|
|
102
|
+
state.subscriptions = {}
|
|
103
|
+
end
|
|
104
|
+
local _exp = state.subscriptions
|
|
105
|
+
table.insert(_exp, function()
|
|
106
|
+
return retire(entry)
|
|
107
|
+
end)
|
|
108
|
+
return table.freeze({})
|
|
109
|
+
end
|
|
110
|
+
--* @internal
|
|
111
|
+
local function isLive(claim)
|
|
112
|
+
local _claim = claim
|
|
113
|
+
return live[_claim] ~= nil
|
|
114
|
+
end
|
|
115
|
+
--* @internal
|
|
116
|
+
local function receiverOf(definition)
|
|
117
|
+
local _definition = definition
|
|
118
|
+
return receivers[_definition]
|
|
119
|
+
end
|
|
120
|
+
--* @internal
|
|
121
|
+
local function readyClaims()
|
|
122
|
+
local _array = {}
|
|
123
|
+
local _length = #_array
|
|
124
|
+
table.move(ready, 1, #ready, _length + 1, _array)
|
|
125
|
+
return _array
|
|
126
|
+
end
|
|
127
|
+
--* @internal
|
|
128
|
+
local function openClaims(value)
|
|
129
|
+
session = value
|
|
130
|
+
end
|
|
131
|
+
--* @internal
|
|
132
|
+
local function freezeClaims()
|
|
133
|
+
frozen = true
|
|
134
|
+
end
|
|
135
|
+
--* @internal
|
|
136
|
+
local function closeClaims(discard)
|
|
137
|
+
session = nil
|
|
138
|
+
if not discard then
|
|
139
|
+
return nil
|
|
140
|
+
end
|
|
141
|
+
frozen = false
|
|
142
|
+
table.clear(receivers)
|
|
143
|
+
table.clear(ready)
|
|
144
|
+
table.clear(live)
|
|
145
|
+
end
|
|
146
|
+
return {
|
|
147
|
+
claim = claim,
|
|
148
|
+
isLive = isLive,
|
|
149
|
+
receiverOf = receiverOf,
|
|
150
|
+
readyClaims = readyClaims,
|
|
151
|
+
openClaims = openClaims,
|
|
152
|
+
freezeClaims = freezeClaims,
|
|
153
|
+
closeClaims = closeClaims,
|
|
154
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local runtimeDiagnostic = TS.import(script, script.Parent.Parent, "diagnostics").runtimeDiagnostic
|
|
4
|
+
local _catalog = TS.import(script, script.Parent, "catalog")
|
|
5
|
+
local adopt = _catalog.adopt
|
|
6
|
+
local catalogSize = _catalog.catalogSize
|
|
7
|
+
local definitionAt = _catalog.definitionAt
|
|
8
|
+
local definitionOf = _catalog.definitionOf
|
|
9
|
+
local discardIndices = _catalog.discardIndices
|
|
10
|
+
local unpublished = _catalog.unpublished
|
|
11
|
+
local _claims = TS.import(script, script.Parent, "claims")
|
|
12
|
+
local closeClaims = _claims.closeClaims
|
|
13
|
+
local freezeClaims = _claims.freezeClaims
|
|
14
|
+
local openClaims = _claims.openClaims
|
|
15
|
+
local receiverOf = _claims.receiverOf
|
|
16
|
+
local _envelope = TS.import(script, script.Parent, "envelope")
|
|
17
|
+
local argumentCount = _envelope.argumentCount
|
|
18
|
+
local controlPacket = _envelope.controlPacket
|
|
19
|
+
local decodeBody = _envelope.decodeBody
|
|
20
|
+
local encodeBody = _envelope.encodeBody
|
|
21
|
+
local failureReason = _envelope.failureReason
|
|
22
|
+
local frame = _envelope.frame
|
|
23
|
+
local missingInstance = _envelope.missingInstance
|
|
24
|
+
local openBody = _envelope.openBody
|
|
25
|
+
local readHeader = _envelope.readHeader
|
|
26
|
+
local L = TS.import(script, script.Parent, "limits")
|
|
27
|
+
local _session = TS.import(script, script.Parent, "session")
|
|
28
|
+
local newPeer = _session.newPeer
|
|
29
|
+
local Session = _session.Session
|
|
30
|
+
local FAIL_NAMES = { "overflow", "overloaded", "closing", "malformed" }
|
|
31
|
+
local ERROR_REASONS = { "unavailable", "handler-error", "overloaded", "invalid" }
|
|
32
|
+
local function failed(reason)
|
|
33
|
+
return table.freeze({
|
|
34
|
+
ok = false,
|
|
35
|
+
reason = reason,
|
|
36
|
+
})
|
|
37
|
+
end
|
|
38
|
+
--* @internal
|
|
39
|
+
local toServer
|
|
40
|
+
local ClientSession
|
|
41
|
+
do
|
|
42
|
+
local super = Session
|
|
43
|
+
ClientSession = setmetatable({}, {
|
|
44
|
+
__tostring = function()
|
|
45
|
+
return "ClientSession"
|
|
46
|
+
end,
|
|
47
|
+
__index = super,
|
|
48
|
+
})
|
|
49
|
+
ClientSession.__index = ClientSession
|
|
50
|
+
function ClientSession.new(...)
|
|
51
|
+
local self = setmetatable({}, ClientSession)
|
|
52
|
+
return self:constructor(...) or self
|
|
53
|
+
end
|
|
54
|
+
function ClientSession:constructor(...)
|
|
55
|
+
super.constructor(self, ...)
|
|
56
|
+
self.peer = newPeer()
|
|
57
|
+
self.nextNonce = 1
|
|
58
|
+
self.pending = {}
|
|
59
|
+
self.fired = false
|
|
60
|
+
self.unpublishedReported = {}
|
|
61
|
+
self.undeclared = {}
|
|
62
|
+
self.unmatched = {}
|
|
63
|
+
self.undecoded = {}
|
|
64
|
+
self.missingPlayer = {}
|
|
65
|
+
end
|
|
66
|
+
function ClientSession:open()
|
|
67
|
+
self.stage = 1
|
|
68
|
+
openClaims({
|
|
69
|
+
side = "client",
|
|
70
|
+
retired = function() end,
|
|
71
|
+
})
|
|
72
|
+
local where = `ReplicatedStorage.{L.REMOTE_NAME}`
|
|
73
|
+
local found = self.port.discover(L.CLIENT_DISCOVERY_TIMEOUT_SECONDS, function(...)
|
|
74
|
+
local args = { ... }
|
|
75
|
+
return self:receive(argumentCount(args), args[1], args[2])
|
|
76
|
+
end)
|
|
77
|
+
if found == nil then
|
|
78
|
+
error(`Crucible found no {where} within {L.CLIENT_DISCOVERY_TIMEOUT_SECONDS} seconds`, 0)
|
|
79
|
+
end
|
|
80
|
+
if found.className ~= "RemoteEvent" then
|
|
81
|
+
error(`Crucible found {where} as a {found.className}`, 0)
|
|
82
|
+
end
|
|
83
|
+
if found.protocol ~= L.PROTOCOL_VERSION then
|
|
84
|
+
error(`Crucible {where} has Protocol {tostring(found.protocol)}; this client needs {L.PROTOCOL_VERSION}`, 0)
|
|
85
|
+
end
|
|
86
|
+
local adopted, err = pcall(adopt, found.catalog)
|
|
87
|
+
if not adopted then
|
|
88
|
+
error(`Crucible {where} has an invalid Catalog: {tostring(err)}`, 0)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
function ClientSession:loaded()
|
|
92
|
+
self.stage = 2
|
|
93
|
+
end
|
|
94
|
+
function ClientSession:commit()
|
|
95
|
+
freezeClaims()
|
|
96
|
+
for _, definition in unpublished() do
|
|
97
|
+
if receiverOf(definition) ~= nil then
|
|
98
|
+
self:unpublished(definition)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
if self.peer.state == 0 then
|
|
102
|
+
self:fire(self.peer, controlPacket(4, {}))
|
|
103
|
+
self.fired = true
|
|
104
|
+
self.peer.state = 1
|
|
105
|
+
self:flush(self.peer)
|
|
106
|
+
end
|
|
107
|
+
self.stage = 3
|
|
108
|
+
end
|
|
109
|
+
function ClientSession:close()
|
|
110
|
+
if self.stage == 4 then
|
|
111
|
+
return nil
|
|
112
|
+
end
|
|
113
|
+
local committed = self.stage == 3
|
|
114
|
+
self.stage = 4
|
|
115
|
+
if self.fired and self.peer.state ~= 2 then
|
|
116
|
+
pcall(function()
|
|
117
|
+
return self:fire(self.peer, controlPacket(5, {}, 2))
|
|
118
|
+
end)
|
|
119
|
+
end
|
|
120
|
+
pcall(function()
|
|
121
|
+
return self["end"](self, self.peer)
|
|
122
|
+
end)
|
|
123
|
+
pcall(function()
|
|
124
|
+
return self.port.disconnect()
|
|
125
|
+
end)
|
|
126
|
+
closeClaims(not committed)
|
|
127
|
+
if not committed then
|
|
128
|
+
discardIndices()
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
function ClientSession:send(token, value)
|
|
132
|
+
local sendable = self:sendable()
|
|
133
|
+
local definition = toServer(token, "to-server", "Network.send needs a message declared with toServer")
|
|
134
|
+
local body = encodeBody(definition.name, definition.input, "payload", value)
|
|
135
|
+
if not sendable or self.peer.state == 2 then
|
|
136
|
+
return false
|
|
137
|
+
end
|
|
138
|
+
local index = definition.index
|
|
139
|
+
if index == nil then
|
|
140
|
+
self:unpublished(definition)
|
|
141
|
+
return false
|
|
142
|
+
end
|
|
143
|
+
return self:deliver(self.peer, frame(body, 0, { index }))
|
|
144
|
+
end
|
|
145
|
+
function ClientSession:ask(token, value)
|
|
146
|
+
local sendable = self:sendable()
|
|
147
|
+
local definition = toServer(token, "ask-server", "Network.ask needs a message declared with askServer")
|
|
148
|
+
local body = encodeBody(definition.name, definition.input, "request", value)
|
|
149
|
+
if not sendable or self.peer.state == 2 then
|
|
150
|
+
return failed("disconnected")
|
|
151
|
+
end
|
|
152
|
+
local index = definition.index
|
|
153
|
+
if index == nil then
|
|
154
|
+
self:unpublished(definition)
|
|
155
|
+
return failed("unavailable")
|
|
156
|
+
end
|
|
157
|
+
-- ▼ ReadonlyMap.size ▼
|
|
158
|
+
local _size = 0
|
|
159
|
+
for _ in self.pending do
|
|
160
|
+
_size += 1
|
|
161
|
+
end
|
|
162
|
+
-- ▲ ReadonlyMap.size ▲
|
|
163
|
+
if _size >= L.MAX_PENDING_ASKS then
|
|
164
|
+
return failed("overloaded")
|
|
165
|
+
end
|
|
166
|
+
local nonce = self.nextNonce
|
|
167
|
+
local packet = frame(body, 1, { index, nonce })
|
|
168
|
+
self.nextNonce = nonce + 1
|
|
169
|
+
local entry = {
|
|
170
|
+
definition = definition,
|
|
171
|
+
thread = coroutine.running(),
|
|
172
|
+
packet = packet,
|
|
173
|
+
}
|
|
174
|
+
self.pending[nonce] = entry
|
|
175
|
+
entry.deadline = self.port.delay(definition.timeout, function()
|
|
176
|
+
return self:expire(nonce)
|
|
177
|
+
end)
|
|
178
|
+
self:deliver(self.peer, packet)
|
|
179
|
+
if entry.result ~= nil then
|
|
180
|
+
return entry.result
|
|
181
|
+
end
|
|
182
|
+
return (coroutine.yield())
|
|
183
|
+
end
|
|
184
|
+
function ClientSession:fire(_peer, packet)
|
|
185
|
+
self.port.fireServer(packet.bytes, packet.sidecar)
|
|
186
|
+
end
|
|
187
|
+
ClientSession["end"] = function(self, peer)
|
|
188
|
+
if not super["end"](self, peer) then
|
|
189
|
+
return false
|
|
190
|
+
end
|
|
191
|
+
local nonces = {}
|
|
192
|
+
for nonce in self.pending do
|
|
193
|
+
table.insert(nonces, nonce)
|
|
194
|
+
end
|
|
195
|
+
table.sort(nonces)
|
|
196
|
+
for _, nonce in nonces do
|
|
197
|
+
self:settle(nonce, failed("disconnected"))
|
|
198
|
+
end
|
|
199
|
+
return true
|
|
200
|
+
end
|
|
201
|
+
function ClientSession:fail(peer, reason, detail)
|
|
202
|
+
if peer.state == 2 then
|
|
203
|
+
return nil
|
|
204
|
+
end
|
|
205
|
+
runtimeDiagnostic("error", `Crucible network session failed ({FAIL_NAMES[reason + 1]}){if detail == nil then "" else `: {detail}`}`)
|
|
206
|
+
pcall(function()
|
|
207
|
+
return self:fire(peer, controlPacket(5, {}, reason))
|
|
208
|
+
end)
|
|
209
|
+
self["end"](self, peer)
|
|
210
|
+
end
|
|
211
|
+
function ClientSession:unpublished(definition)
|
|
212
|
+
local _unpublishedReported = self.unpublishedReported
|
|
213
|
+
local _definition = definition
|
|
214
|
+
if _unpublishedReported[_definition] ~= nil then
|
|
215
|
+
return nil
|
|
216
|
+
end
|
|
217
|
+
local _unpublishedReported_1 = self.unpublishedReported
|
|
218
|
+
local _definition_1 = definition
|
|
219
|
+
_unpublishedReported_1[_definition_1] = true
|
|
220
|
+
runtimeDiagnostic("warning", `Crucible {definition.name} is not in the server's network catalog`)
|
|
221
|
+
end
|
|
222
|
+
function ClientSession:once(seen, definition, message)
|
|
223
|
+
local _seen = seen
|
|
224
|
+
local _definition = definition
|
|
225
|
+
if _seen[_definition] ~= nil then
|
|
226
|
+
return nil
|
|
227
|
+
end
|
|
228
|
+
local _seen_1 = seen
|
|
229
|
+
local _definition_1 = definition
|
|
230
|
+
_seen_1[_definition_1] = true
|
|
231
|
+
runtimeDiagnostic("warning", message)
|
|
232
|
+
end
|
|
233
|
+
function ClientSession:expire(nonce)
|
|
234
|
+
local _pending = self.pending
|
|
235
|
+
local _nonce = nonce
|
|
236
|
+
local entry = _pending[_nonce]
|
|
237
|
+
if entry == nil then
|
|
238
|
+
return nil
|
|
239
|
+
end
|
|
240
|
+
local _queue = self.peer.queue
|
|
241
|
+
local _packet = entry.packet
|
|
242
|
+
local index = (table.find(_queue, _packet) or 0) - 1
|
|
243
|
+
if index ~= -1 then
|
|
244
|
+
table.remove(self.peer.queue, index + 1)
|
|
245
|
+
self.peer.queuedBytes -= entry.packet.charge
|
|
246
|
+
end
|
|
247
|
+
self:settle(nonce, failed("timeout"))
|
|
248
|
+
end
|
|
249
|
+
function ClientSession:settle(nonce, result)
|
|
250
|
+
local _pending = self.pending
|
|
251
|
+
local _nonce = nonce
|
|
252
|
+
local entry = _pending[_nonce]
|
|
253
|
+
if entry == nil then
|
|
254
|
+
return nil
|
|
255
|
+
end
|
|
256
|
+
local _pending_1 = self.pending
|
|
257
|
+
local _nonce_1 = nonce
|
|
258
|
+
_pending_1[_nonce_1] = nil
|
|
259
|
+
local _result = entry.deadline
|
|
260
|
+
if _result ~= nil then
|
|
261
|
+
_result()
|
|
262
|
+
end
|
|
263
|
+
entry.result = result
|
|
264
|
+
if self.later ~= nil then
|
|
265
|
+
local _exp = self.later
|
|
266
|
+
table.insert(_exp, entry)
|
|
267
|
+
else
|
|
268
|
+
self:resume(entry)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
function ClientSession:resume(entry)
|
|
272
|
+
if coroutine.status(entry.thread) == "suspended" then
|
|
273
|
+
self.port.resume(entry.thread, entry.result)
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
function ClientSession:receive(count, packet, sidecar)
|
|
277
|
+
local later = {}
|
|
278
|
+
self.later = later
|
|
279
|
+
local ok, err = pcall(function()
|
|
280
|
+
return self:dispatch(count, packet, sidecar)
|
|
281
|
+
end)
|
|
282
|
+
self.later = nil
|
|
283
|
+
if not ok then
|
|
284
|
+
self:fail(self.peer, 3, `receive failed: {tostring(err)}`)
|
|
285
|
+
end
|
|
286
|
+
for _, entry in later do
|
|
287
|
+
self:resume(entry)
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
function ClientSession:violation(detail)
|
|
291
|
+
self:fail(self.peer, 3, detail)
|
|
292
|
+
end
|
|
293
|
+
function ClientSession:dispatch(count, packet, sidecar)
|
|
294
|
+
if self.peer.state == 2 then
|
|
295
|
+
return nil
|
|
296
|
+
end
|
|
297
|
+
local header = readHeader(count, packet, sidecar)
|
|
298
|
+
if header == nil then
|
|
299
|
+
return self:violation("a malformed packet")
|
|
300
|
+
end
|
|
301
|
+
if header.opcode == 5 then
|
|
302
|
+
local _condition = FAIL_NAMES[header.reason + 1]
|
|
303
|
+
if _condition == nil then
|
|
304
|
+
_condition = `unknown reason {header.reason}`
|
|
305
|
+
end
|
|
306
|
+
local reason = _condition
|
|
307
|
+
runtimeDiagnostic("error", `Crucible network session ended: the server sent FAIL ({reason})`)
|
|
308
|
+
self["end"](self, self.peer)
|
|
309
|
+
return nil
|
|
310
|
+
end
|
|
311
|
+
if not self.fired then
|
|
312
|
+
return self:violation("a game packet before READY")
|
|
313
|
+
end
|
|
314
|
+
if header.opcode == 0 then
|
|
315
|
+
return self:event(header, packet, sidecar)
|
|
316
|
+
end
|
|
317
|
+
if header.opcode == 2 or header.opcode == 3 then
|
|
318
|
+
return self:answer(header, packet, sidecar)
|
|
319
|
+
end
|
|
320
|
+
self:violation("a client-to-server packet")
|
|
321
|
+
end
|
|
322
|
+
function ClientSession:event(header, packet, sidecar)
|
|
323
|
+
if header.index >= catalogSize() then
|
|
324
|
+
return self:violation("an unknown message index")
|
|
325
|
+
end
|
|
326
|
+
local definition = definitionAt(header.index)
|
|
327
|
+
if definition == nil then
|
|
328
|
+
local _undeclared = self.undeclared
|
|
329
|
+
local _index = header.index
|
|
330
|
+
if _undeclared[_index] ~= nil then
|
|
331
|
+
return nil
|
|
332
|
+
end
|
|
333
|
+
local _undeclared_1 = self.undeclared
|
|
334
|
+
local _index_1 = header.index
|
|
335
|
+
_undeclared_1[_index_1] = true
|
|
336
|
+
runtimeDiagnostic("warning", `Crucible dropped catalog message {header.index}: this client never declared it`)
|
|
337
|
+
return nil
|
|
338
|
+
end
|
|
339
|
+
if definition.kind ~= "to-client" then
|
|
340
|
+
return self:violation(`{definition.name} sent as an event`)
|
|
341
|
+
end
|
|
342
|
+
local decoded, value = pcall(function()
|
|
343
|
+
return decodeBody(definition.input, "payload", openBody(packet, sidecar, header))
|
|
344
|
+
end)
|
|
345
|
+
if not decoded then
|
|
346
|
+
if missingInstance(value) then
|
|
347
|
+
return self:once(self.missingPlayer, definition, `Crucible dropped {definition.name}: a Player left`)
|
|
348
|
+
end
|
|
349
|
+
return self:violation(`{definition.name} {failureReason(value)}`)
|
|
350
|
+
end
|
|
351
|
+
local receiver = receiverOf(definition)
|
|
352
|
+
if receiver == nil then
|
|
353
|
+
return self:unreceived(definition)
|
|
354
|
+
end
|
|
355
|
+
if self.peer.counted >= L.MAX_ACTIVE_HANDLERS then
|
|
356
|
+
return self:fail(self.peer, 1)
|
|
357
|
+
end
|
|
358
|
+
self:start(self.peer, receiver, { value })
|
|
359
|
+
end
|
|
360
|
+
function ClientSession:answer(header, packet, sidecar)
|
|
361
|
+
local definition = definitionAt(header.index)
|
|
362
|
+
if definition == nil or definition.kind ~= "ask-server" then
|
|
363
|
+
return self:violation("a reply naming a message that is not an ask")
|
|
364
|
+
end
|
|
365
|
+
local _pending = self.pending
|
|
366
|
+
local _nonce = header.nonce
|
|
367
|
+
local entry = _pending[_nonce]
|
|
368
|
+
if entry == nil then
|
|
369
|
+
return self:once(self.unmatched, definition, `Crucible ignored a late or unknown reply to {definition.name}`)
|
|
370
|
+
end
|
|
371
|
+
if entry.definition ~= definition then
|
|
372
|
+
return self:violation(`a reply to {entry.definition.name} naming {definition.name}`)
|
|
373
|
+
end
|
|
374
|
+
if header.opcode == 3 then
|
|
375
|
+
local reason = ERROR_REASONS[header.reason + 1]
|
|
376
|
+
if reason == nil then
|
|
377
|
+
return self:violation(`an unknown ERROR reason {header.reason}`)
|
|
378
|
+
end
|
|
379
|
+
return self:settle(header.nonce, failed(reason))
|
|
380
|
+
end
|
|
381
|
+
local decoded, value = pcall(function()
|
|
382
|
+
return decodeBody(definition.output, "response", openBody(packet, sidecar, header))
|
|
383
|
+
end)
|
|
384
|
+
if not decoded then
|
|
385
|
+
self:once(self.undecoded, definition, `Crucible could not decode a reply to {definition.name}: {failureReason(value)}`)
|
|
386
|
+
return self:settle(header.nonce, failed("invalid"))
|
|
387
|
+
end
|
|
388
|
+
self:settle(header.nonce, table.freeze({
|
|
389
|
+
ok = true,
|
|
390
|
+
value = value,
|
|
391
|
+
}))
|
|
392
|
+
end
|
|
393
|
+
end
|
|
394
|
+
function toServer(token, kind, message)
|
|
395
|
+
local definition = definitionOf(token)
|
|
396
|
+
if definition == nil or definition.kind ~= kind then
|
|
397
|
+
error(message, 0)
|
|
398
|
+
end
|
|
399
|
+
return definition
|
|
400
|
+
end
|
|
401
|
+
return {
|
|
402
|
+
ClientSession = ClientSession,
|
|
403
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type Branch, type Node } from "./schema";
|
|
2
|
+
export interface CodecFailure {
|
|
3
|
+
readonly codecFailure: true;
|
|
4
|
+
readonly path: string;
|
|
5
|
+
readonly reason: string;
|
|
6
|
+
readonly missing?: true;
|
|
7
|
+
}
|
|
8
|
+
export declare class Budget {
|
|
9
|
+
visits: number;
|
|
10
|
+
entries: number;
|
|
11
|
+
depth: number;
|
|
12
|
+
chargeEntries(count: number, path: string): void;
|
|
13
|
+
visit(path: string): void;
|
|
14
|
+
enter(path: string): void;
|
|
15
|
+
leave(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare class Writer {
|
|
18
|
+
readonly budget: Budget;
|
|
19
|
+
readonly slots: Instance[];
|
|
20
|
+
private data;
|
|
21
|
+
private cursor;
|
|
22
|
+
private reserve;
|
|
23
|
+
u8(value: number): void;
|
|
24
|
+
u16(value: number): void;
|
|
25
|
+
u32(value: number): void;
|
|
26
|
+
varuint(value: number): void;
|
|
27
|
+
f32(value: number): void;
|
|
28
|
+
f64(value: number): void;
|
|
29
|
+
bytes(text: string): void;
|
|
30
|
+
slot(instance: Instance): void;
|
|
31
|
+
finish(header: buffer): LuaTuple<[buffer, Readonly<Record<string, Instance>>, number]>;
|
|
32
|
+
}
|
|
33
|
+
export declare class Reader {
|
|
34
|
+
private readonly packet;
|
|
35
|
+
private cursor;
|
|
36
|
+
private readonly sidecar;
|
|
37
|
+
private readonly slotCount;
|
|
38
|
+
readonly budget: Budget;
|
|
39
|
+
private nextSlot;
|
|
40
|
+
constructor(packet: buffer, cursor: number, sidecar: Readonly<Record<string, Instance>>, slotCount: number);
|
|
41
|
+
private take;
|
|
42
|
+
u8(): number;
|
|
43
|
+
u16(): number;
|
|
44
|
+
u32(): number;
|
|
45
|
+
varuint(): number;
|
|
46
|
+
f32(): number;
|
|
47
|
+
f64(): number;
|
|
48
|
+
string(): string;
|
|
49
|
+
count(minElementBytes: number, path: string): number;
|
|
50
|
+
slot(path: string): Instance | undefined;
|
|
51
|
+
finished(): boolean;
|
|
52
|
+
}
|
|
53
|
+
export interface Codec {
|
|
54
|
+
readonly node: Branch;
|
|
55
|
+
readonly minBytes: number;
|
|
56
|
+
readonly encode: (writer: Writer, value: unknown, path: string) => void;
|
|
57
|
+
readonly decode: (reader: Reader, path: string) => unknown;
|
|
58
|
+
}
|
|
59
|
+
export declare function compile(node: Node, tally?: {
|
|
60
|
+
nodes: number;
|
|
61
|
+
}): Codec;
|
|
62
|
+
export declare function encodeDynamic(w: Writer, value: unknown, path: string, active: Set<object>, topLevel?: boolean, arrayElement?: boolean): void;
|
|
63
|
+
export declare function decodeDynamic(r: Reader, path: string, topLevel: boolean, arrayElement?: boolean): unknown;
|