@trioforge/crucible 0.0.0-stage → 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/LICENSE +21 -0
- package/client/package.json +4 -0
- package/emit/package.json +4 -0
- package/out/all.d.ts +7 -0
- package/out/all.luau +2 -0
- package/out/attribute.d.ts +7 -0
- package/out/attribute.luau +7 -0
- package/out/client/index.d.ts +10 -0
- package/out/client/init.luau +55 -0
- package/out/configs.d.ts +37 -0
- package/out/configs.luau +46 -0
- package/out/crucible-behaviour.d.ts +16 -0
- package/out/crucible-behaviour.luau +78 -0
- package/out/crucible-object.d.ts +10 -0
- package/out/crucible-object.luau +69 -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 +45 -0
- package/out/emit/init.luau +101 -0
- 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 +20 -0
- package/out/init.luau +34 -0
- package/out/inject.d.ts +1 -0
- package/out/inject.luau +7 -0
- package/out/internal/binding.d.ts +1 -0
- package/out/internal/binding.luau +615 -0
- package/out/internal/boot.d.ts +1 -0
- package/out/internal/boot.luau +562 -0
- 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/construction-claim.d.ts +1 -0
- package/out/internal/construction-claim.luau +26 -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/dense-list.d.ts +1 -0
- package/out/internal/dense-list.luau +155 -0
- package/out/internal/diagnostics.d.ts +1 -0
- package/out/internal/diagnostics.luau +29 -0
- package/out/internal/hooks.d.ts +1 -0
- package/out/internal/hooks.luau +53 -0
- package/out/internal/implementers.d.ts +1 -0
- package/out/internal/implementers.luau +119 -0
- package/out/internal/lifecycle.d.ts +1 -0
- package/out/internal/lifecycle.luau +912 -0
- package/out/internal/log-output.d.ts +1 -0
- package/out/internal/log-output.luau +106 -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/port.d.ts +1 -0
- package/out/internal/port.luau +12 -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.d.ts +1 -0
- package/out/internal/registry.luau +455 -0
- package/out/internal/roblox-port.d.ts +1 -0
- package/out/internal/roblox-port.luau +117 -0
- package/out/internal/runner.d.ts +1 -0
- package/out/internal/runner.luau +86 -0
- package/out/internal/scheduler.d.ts +1 -0
- package/out/internal/scheduler.luau +207 -0
- package/out/internal/state.d.ts +1 -0
- package/out/internal/state.luau +41 -0
- 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/log-level.d.ts +7 -0
- package/out/log-level.luau +21 -0
- package/out/logging.d.ts +34 -0
- package/out/logging.luau +233 -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 +18 -0
- package/out/server/init.luau +62 -0
- package/out/server/publish.d.ts +20 -0
- package/out/server/publish.luau +24 -0
- package/out/subsystem.d.ts +4 -0
- package/out/subsystem.luau +30 -0
- package/out/vide/index.d.ts +7 -0
- package/out/vide/init.luau +50 -0
- package/package.json +52 -5
- package/server/package.json +4 -0
- package/transformer/data.cjs +72 -0
- package/transformer/declarations.cjs +153 -0
- package/transformer/diagnostics.cjs +19 -0
- package/transformer/index.js +741 -0
- package/transformer/network.cjs +961 -0
- package/transformer/type-id.cjs +80 -0
- package/vide/package.json +4 -0
- package/README.md +0 -3
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Trio Forge Studio
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/out/all.d.ts
ADDED
package/out/all.luau
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type AttributeValue = string | number | boolean | Vector3 | Color3;
|
|
2
|
+
export declare function attribute<T extends AttributeValue>(): T;
|
|
3
|
+
export declare function attribute(fallback: number): number;
|
|
4
|
+
export declare function attribute(fallback: string): string;
|
|
5
|
+
export declare function attribute(fallback: boolean): boolean;
|
|
6
|
+
export declare function attribute<T extends AttributeValue | undefined>(fallback: T): T;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BootOptions } from "../index";
|
|
2
|
+
import type { AskResult, AskServer, PayloadArgs, ToServer } from "../network";
|
|
3
|
+
export declare const BOOTED_ATTRIBUTE = "CrucibleBooted";
|
|
4
|
+
export declare function boot(options: BootOptions): void;
|
|
5
|
+
export declare namespace Network {
|
|
6
|
+
/** True when accepted locally: fired now, or queued until this client is ready. Never delivery. */
|
|
7
|
+
function send<P>(message: ToServer<P>, ...payload: PayloadArgs<NoInfer<P>>): boolean;
|
|
8
|
+
/** Yields until the reply, the declared timeout or the session ending. */
|
|
9
|
+
function ask<Request, Response>(message: AskServer<Request, Response>, ...request: PayloadArgs<NoInfer<Request>>): AskResult<Response>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local bootSide = TS.import(script, script.Parent, "internal", "boot").boot
|
|
4
|
+
local ClientSession = TS.import(script, script.Parent, "internal", "network", "client-session").ClientSession
|
|
5
|
+
local networkPort = TS.import(script, script.Parent, "internal", "network", "port").networkPort
|
|
6
|
+
local installClientSync = TS.import(script, script.Parent, "internal", "sync", "client-sync").installClientSync
|
|
7
|
+
local SyncFaultMessage = TS.import(script, script.Parent, "internal", "sync", "frame").SyncFaultMessage
|
|
8
|
+
if not game:GetService("RunService"):IsClient() then
|
|
9
|
+
error("@trioforge/crucible/client loaded on the server")
|
|
10
|
+
end
|
|
11
|
+
local BOOTED_ATTRIBUTE = "CrucibleBooted"
|
|
12
|
+
local session
|
|
13
|
+
local function active()
|
|
14
|
+
return session or error("Crucible networking is not ready; send from a constructor, a hook, or later", 0)
|
|
15
|
+
end
|
|
16
|
+
local function boot(options)
|
|
17
|
+
local network = ClientSession.new(networkPort())
|
|
18
|
+
session = network
|
|
19
|
+
installClientSync(function(revision)
|
|
20
|
+
return active():send(SyncFaultMessage, revision)
|
|
21
|
+
end)
|
|
22
|
+
bootSide({
|
|
23
|
+
name = "client",
|
|
24
|
+
renders = true,
|
|
25
|
+
booted = function()
|
|
26
|
+
local player = game:GetService("Players").LocalPlayer
|
|
27
|
+
player:SetAttribute(BOOTED_ATTRIBUTE, true)
|
|
28
|
+
return function()
|
|
29
|
+
return player:SetAttribute(BOOTED_ATTRIBUTE, nil)
|
|
30
|
+
end
|
|
31
|
+
end,
|
|
32
|
+
network = network,
|
|
33
|
+
}, options)
|
|
34
|
+
end
|
|
35
|
+
local Network = {}
|
|
36
|
+
do
|
|
37
|
+
local _container = Network
|
|
38
|
+
--* True when accepted locally: fired now, or queued until this client is ready. Never delivery.
|
|
39
|
+
local function send(message, ...)
|
|
40
|
+
local payload = { ... }
|
|
41
|
+
return active():send(message, payload[1])
|
|
42
|
+
end
|
|
43
|
+
_container.send = send
|
|
44
|
+
--* Yields until the reply, the declared timeout or the session ending.
|
|
45
|
+
local function ask(message, ...)
|
|
46
|
+
local request = { ... }
|
|
47
|
+
return active():ask(message, request[1])
|
|
48
|
+
end
|
|
49
|
+
_container.ask = ask
|
|
50
|
+
end
|
|
51
|
+
return {
|
|
52
|
+
boot = boot,
|
|
53
|
+
BOOTED_ATTRIBUTE = BOOTED_ATTRIBUTE,
|
|
54
|
+
Network = Network,
|
|
55
|
+
}
|
package/out/configs.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
declare const configKind: unique symbol;
|
|
2
|
+
/** An experience-wide config. Calling it reads the global snapshot's value; it never yields or throws. */
|
|
3
|
+
export interface Config<T> {
|
|
4
|
+
(): T;
|
|
5
|
+
readonly [configKind]: {
|
|
6
|
+
readonly scope: "global";
|
|
7
|
+
readonly value: (value: T) => T;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** A targeted config. Calling it reads that player's value; it never yields or throws. */
|
|
11
|
+
export interface PlayerConfig<T> {
|
|
12
|
+
(player: Player): T;
|
|
13
|
+
readonly [configKind]: {
|
|
14
|
+
readonly scope: "player";
|
|
15
|
+
readonly value: (value: T) => T;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Declares an experience config by its dashboard key, read from the global snapshot. It must initialize an exported
|
|
20
|
+
* top-level const in a server module, with `T` explicit. The fallback is the value before the first fetch and for a
|
|
21
|
+
* missing key.
|
|
22
|
+
*/
|
|
23
|
+
export declare function config<T = never>(_name: string, _fallback: NoInfer<T>): Config<T>;
|
|
24
|
+
/** Declares a targeted config by its dashboard key, read from each player's snapshot. Same form as `config`. */
|
|
25
|
+
export declare function playerConfig<T = never>(_name: string, _fallback: NoInfer<T>): PlayerConfig<T>;
|
|
26
|
+
/**
|
|
27
|
+
* With no argument, true once the global snapshot has been applied. With a player, true once that player's snapshot
|
|
28
|
+
* has been applied, and false before and after they leave.
|
|
29
|
+
*/
|
|
30
|
+
export declare function configReady(player?: Player): boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Refreshes every snapshot with newer values, fetches every snapshot that has not loaded, and applies them all in
|
|
33
|
+
* one pass. Yields, at most 10 seconds. True when every snapshot is current; false keeps the last good values.
|
|
34
|
+
* Concurrent calls share one refresh. Crucible never calls it.
|
|
35
|
+
*/
|
|
36
|
+
export declare function refreshConfigs(): boolean;
|
|
37
|
+
export {};
|
package/out/configs.luau
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local _runtime = TS.import(script, script.Parent, "internal", "configs", "runtime")
|
|
4
|
+
local ready = _runtime.ready
|
|
5
|
+
local refresh = _runtime.refresh
|
|
6
|
+
--* An experience-wide config. Calling it reads the global snapshot's value; it never yields or throws.
|
|
7
|
+
--* A targeted config. Calling it reads that player's value; it never yields or throws.
|
|
8
|
+
--[[
|
|
9
|
+
*
|
|
10
|
+
* Declares an experience config by its dashboard key, read from the global snapshot. It must initialize an exported
|
|
11
|
+
* top-level const in a server module, with `T` explicit. The fallback is the value before the first fetch and for a
|
|
12
|
+
* missing key.
|
|
13
|
+
|
|
14
|
+
]]
|
|
15
|
+
local function config(_name, _fallback)
|
|
16
|
+
error("Crucible configs need the @trioforge/crucible/transformer plugin")
|
|
17
|
+
end
|
|
18
|
+
--* Declares a targeted config by its dashboard key, read from each player's snapshot. Same form as `config`.
|
|
19
|
+
local function playerConfig(_name, _fallback)
|
|
20
|
+
error("Crucible configs need the @trioforge/crucible/transformer plugin")
|
|
21
|
+
end
|
|
22
|
+
--[[
|
|
23
|
+
*
|
|
24
|
+
* With no argument, true once the global snapshot has been applied. With a player, true once that player's snapshot
|
|
25
|
+
* has been applied, and false before and after they leave.
|
|
26
|
+
|
|
27
|
+
]]
|
|
28
|
+
local function configReady(player)
|
|
29
|
+
return ready(player)
|
|
30
|
+
end
|
|
31
|
+
--[[
|
|
32
|
+
*
|
|
33
|
+
* Refreshes every snapshot with newer values, fetches every snapshot that has not loaded, and applies them all in
|
|
34
|
+
* one pass. Yields, at most 10 seconds. True when every snapshot is current; false keeps the last good values.
|
|
35
|
+
* Concurrent calls share one refresh. Crucible never calls it.
|
|
36
|
+
|
|
37
|
+
]]
|
|
38
|
+
local function refreshConfigs()
|
|
39
|
+
return refresh()
|
|
40
|
+
end
|
|
41
|
+
return {
|
|
42
|
+
config = config,
|
|
43
|
+
playerConfig = playerConfig,
|
|
44
|
+
configReady = configReady,
|
|
45
|
+
refreshConfigs = refreshConfigs,
|
|
46
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { CrucibleObject } from "./crucible-object";
|
|
2
|
+
export type BehaviourClass<T extends CrucibleBehaviour<string>> = new (...args: never[]) => T;
|
|
3
|
+
export declare abstract class CrucibleBehaviour<Tag extends string, I extends Instance = Instance> extends CrucibleObject {
|
|
4
|
+
static accepts(instance: Instance): boolean;
|
|
5
|
+
static refresh(instance: Instance): void;
|
|
6
|
+
static get<T extends CrucibleBehaviour<string>>(this: BehaviourClass<T>, instance: Instance): T | undefined;
|
|
7
|
+
readonly instance: I;
|
|
8
|
+
readonly tag: Tag;
|
|
9
|
+
constructor();
|
|
10
|
+
isEnabled(): boolean;
|
|
11
|
+
setEnabled(enabled: boolean): void;
|
|
12
|
+
remove(): void;
|
|
13
|
+
protected onEnable(): void;
|
|
14
|
+
protected onDisable(): void;
|
|
15
|
+
protected onDestroy(): void;
|
|
16
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local CrucibleObject = TS.import(script, script.Parent, "crucible-object").CrucibleObject
|
|
4
|
+
local _binding = TS.import(script, script.Parent, "internal", "binding")
|
|
5
|
+
local boundTo = _binding.boundTo
|
|
6
|
+
local defaultAccepts = _binding.defaultAccepts
|
|
7
|
+
local refresh = _binding.refresh
|
|
8
|
+
local remove = _binding.remove
|
|
9
|
+
local readClaim = TS.import(script, script.Parent, "internal", "construction-claim").readClaim
|
|
10
|
+
local installBehaviourHooks = TS.import(script, script.Parent, "internal", "hooks").installBehaviourHooks
|
|
11
|
+
local _lifecycle = TS.import(script, script.Parent, "internal", "lifecycle")
|
|
12
|
+
local isLive = _lifecycle.isLive
|
|
13
|
+
local request = _lifecycle.request
|
|
14
|
+
local stateOf = _lifecycle.stateOf
|
|
15
|
+
local CrucibleBehaviour
|
|
16
|
+
do
|
|
17
|
+
local super = CrucibleObject
|
|
18
|
+
CrucibleBehaviour = setmetatable({}, {
|
|
19
|
+
__tostring = function()
|
|
20
|
+
return "CrucibleBehaviour"
|
|
21
|
+
end,
|
|
22
|
+
__index = super,
|
|
23
|
+
})
|
|
24
|
+
CrucibleBehaviour.__index = CrucibleBehaviour
|
|
25
|
+
function CrucibleBehaviour:constructor()
|
|
26
|
+
super.constructor(self)
|
|
27
|
+
local state = readClaim() or error("CrucibleBehaviour constructed outside Crucible")
|
|
28
|
+
self.instance = state.instance
|
|
29
|
+
end
|
|
30
|
+
function CrucibleBehaviour:accepts(instance)
|
|
31
|
+
return defaultAccepts(instance)
|
|
32
|
+
end
|
|
33
|
+
function CrucibleBehaviour:refresh(instance)
|
|
34
|
+
refresh(instance)
|
|
35
|
+
end
|
|
36
|
+
function CrucibleBehaviour:get(instance)
|
|
37
|
+
local state = boundTo(self, instance)
|
|
38
|
+
return if state ~= nil and isLive(state) then (state.object) else nil
|
|
39
|
+
end
|
|
40
|
+
function CrucibleBehaviour:isEnabled()
|
|
41
|
+
local state = stateOf(self)
|
|
42
|
+
return state ~= nil and state.phase == 3 and isLive(state)
|
|
43
|
+
end
|
|
44
|
+
function CrucibleBehaviour:setEnabled(enabled)
|
|
45
|
+
local state = stateOf(self)
|
|
46
|
+
if state ~= nil then
|
|
47
|
+
request(state, if enabled then 0 else 1)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
function CrucibleBehaviour:remove()
|
|
51
|
+
local state = stateOf(self)
|
|
52
|
+
if state ~= nil then
|
|
53
|
+
remove(state)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
function CrucibleBehaviour:onEnable()
|
|
57
|
+
end
|
|
58
|
+
function CrucibleBehaviour:onDisable()
|
|
59
|
+
end
|
|
60
|
+
function CrucibleBehaviour:onDestroy()
|
|
61
|
+
end
|
|
62
|
+
do
|
|
63
|
+
installBehaviourHooks({
|
|
64
|
+
onEnable = function(b)
|
|
65
|
+
return b:onEnable()
|
|
66
|
+
end,
|
|
67
|
+
onDisable = function(b)
|
|
68
|
+
return b:onDisable()
|
|
69
|
+
end,
|
|
70
|
+
onDestroy = function(b)
|
|
71
|
+
return b:onDestroy()
|
|
72
|
+
end,
|
|
73
|
+
})
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
return {
|
|
77
|
+
CrucibleBehaviour = CrucibleBehaviour,
|
|
78
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare abstract class CrucibleObject {
|
|
2
|
+
static readonly renderPriority?: number;
|
|
3
|
+
constructor();
|
|
4
|
+
protected awake(): void;
|
|
5
|
+
protected start(): void;
|
|
6
|
+
protected fixedUpdate(_dt: number): void;
|
|
7
|
+
protected update(_dt: number): void;
|
|
8
|
+
protected lateUpdate(_dt: number): void;
|
|
9
|
+
protected preRender(_dt: number): void;
|
|
10
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
-- Compiled with roblox-ts v3.0.0
|
|
2
|
+
local TS = _G[script]
|
|
3
|
+
local readClaim = TS.import(script, script.Parent, "internal", "construction-claim").readClaim
|
|
4
|
+
local _hooks = TS.import(script, script.Parent, "internal", "hooks")
|
|
5
|
+
local FRAME_HOOK_NAMES = _hooks.FRAME_HOOK_NAMES
|
|
6
|
+
local installObjectHooks = _hooks.installObjectHooks
|
|
7
|
+
local CrucibleObject
|
|
8
|
+
do
|
|
9
|
+
CrucibleObject = {}
|
|
10
|
+
function CrucibleObject:constructor()
|
|
11
|
+
local state = readClaim() or error("CrucibleObject constructed outside Crucible")
|
|
12
|
+
state.object = self
|
|
13
|
+
end
|
|
14
|
+
function CrucibleObject:awake()
|
|
15
|
+
end
|
|
16
|
+
function CrucibleObject:start()
|
|
17
|
+
end
|
|
18
|
+
function CrucibleObject:fixedUpdate(_dt)
|
|
19
|
+
end
|
|
20
|
+
function CrucibleObject:update(_dt)
|
|
21
|
+
end
|
|
22
|
+
function CrucibleObject:lateUpdate(_dt)
|
|
23
|
+
end
|
|
24
|
+
function CrucibleObject:preRender(_dt)
|
|
25
|
+
end
|
|
26
|
+
do
|
|
27
|
+
installObjectHooks({
|
|
28
|
+
awake = function(o)
|
|
29
|
+
return o:awake()
|
|
30
|
+
end,
|
|
31
|
+
start = function(o)
|
|
32
|
+
return o:start()
|
|
33
|
+
end,
|
|
34
|
+
frame = {
|
|
35
|
+
[0] = function(o, dt)
|
|
36
|
+
return o:preRender(dt)
|
|
37
|
+
end,
|
|
38
|
+
[1] = function(o, dt)
|
|
39
|
+
return o:fixedUpdate(dt)
|
|
40
|
+
end,
|
|
41
|
+
[2] = function(o, dt)
|
|
42
|
+
return o:update(dt)
|
|
43
|
+
end,
|
|
44
|
+
[3] = function(o, dt)
|
|
45
|
+
return o:lateUpdate(dt)
|
|
46
|
+
end,
|
|
47
|
+
},
|
|
48
|
+
overrides = function(klass)
|
|
49
|
+
local methods = klass
|
|
50
|
+
local defaults = CrucibleObject
|
|
51
|
+
local mask = 0
|
|
52
|
+
-- ▼ ReadonlyArray.forEach ▼
|
|
53
|
+
local _callback = function(name, hook)
|
|
54
|
+
if methods[name] ~= defaults[name] then
|
|
55
|
+
mask = bit32.bor(mask, bit32.lshift(1, hook))
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
for _k, _v in FRAME_HOOK_NAMES do
|
|
59
|
+
_callback(_v, _k - 1, FRAME_HOOK_NAMES)
|
|
60
|
+
end
|
|
61
|
+
-- ▲ ReadonlyArray.forEach ▲
|
|
62
|
+
return mask
|
|
63
|
+
end,
|
|
64
|
+
})
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
return {
|
|
68
|
+
CrucibleObject = CrucibleObject,
|
|
69
|
+
}
|
package/out/data.d.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { Readable } from "./reactive";
|
|
2
|
+
declare const operationKind: unique symbol;
|
|
3
|
+
declare const storeKind: unique symbol;
|
|
4
|
+
declare const messageKind: unique symbol;
|
|
5
|
+
declare const claimKind: unique symbol;
|
|
6
|
+
declare const serverOnlyKind: unique symbol;
|
|
7
|
+
/** A value a save can hold: JSON, with absent keys for values that admit `undefined`. */
|
|
8
|
+
export type Json = boolean | number | string | readonly Json[] | JsonRecord;
|
|
9
|
+
/** A saved record: string keys to JSON. */
|
|
10
|
+
export interface JsonRecord {
|
|
11
|
+
readonly [key: string]: Json | undefined;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* One schema step. It gets a private copy of the stored record, unknown keys included, as plain JSON, and returns a
|
|
15
|
+
* patch: an object merges into the stored object, any other value replaces the stored one, and an omitted key is kept.
|
|
16
|
+
* A step cannot delete a key. Steps are append-only and must be pure.
|
|
17
|
+
*/
|
|
18
|
+
export type Migration = (old: JsonRecord) => JsonRecord;
|
|
19
|
+
/**
|
|
20
|
+
* The identity of one logical operation, kept across its retries. Only `operationId()` makes one; a plain string does
|
|
21
|
+
* not type-check. It carries its creation time, which bounds how long it deduplicates.
|
|
22
|
+
*/
|
|
23
|
+
export type OperationId = string & {
|
|
24
|
+
readonly [operationKind]: true;
|
|
25
|
+
};
|
|
26
|
+
/** Why a store call did not complete. */
|
|
27
|
+
export type StoreFailure = "unavailable" | "timeout" | "uncertain" | "overloaded" | "full" | "expired" | "reused" | "closing";
|
|
28
|
+
/** A store call's result: narrow `ok` before reading `value`. */
|
|
29
|
+
export type StoreResult<T> = {
|
|
30
|
+
readonly ok: true;
|
|
31
|
+
readonly value: T;
|
|
32
|
+
} | {
|
|
33
|
+
readonly ok: false;
|
|
34
|
+
readonly reason: StoreFailure;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* A template field the owner's client never receives. The brand is phantom; `serverOnly(value)` also marks the value at
|
|
38
|
+
* runtime, and the declaration checks that the two agree.
|
|
39
|
+
*/
|
|
40
|
+
export interface ServerOnly<V> {
|
|
41
|
+
readonly [serverOnlyKind]: V;
|
|
42
|
+
}
|
|
43
|
+
type Scalar = string | number | boolean;
|
|
44
|
+
/** What the server reads and writes: the template's type with every `ServerOnly` unwrapped, deeply readonly. */
|
|
45
|
+
export type ProfileData<T> = T extends ServerOnly<infer V> ? ProfileData<V> : T extends Scalar | undefined ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<K, ProfileData<V>> : {
|
|
46
|
+
readonly [K in keyof T]: ProfileData<T[K]>;
|
|
47
|
+
};
|
|
48
|
+
type IsUnion<V, C = V> = V extends unknown ? ([C] extends [V] ? false : true) : never;
|
|
49
|
+
/** A `ServerOnly` field, or a plain object field whose every field is server-only in turn. */
|
|
50
|
+
type ServerOnlyField<V> = [V] extends [ServerOnly<unknown>] ? true : [V] extends [Scalar | undefined] ? false : [V] extends [ReadonlyMap<unknown, unknown> | readonly unknown[]] ? false : true extends IsUnion<V> ? false : string extends keyof V ? false : [keyof V] extends [never] ? false : false extends {
|
|
51
|
+
[K in keyof V]-?: ServerOnlyField<NonNullable<V[K]>>;
|
|
52
|
+
}[keyof V] ? false : true;
|
|
53
|
+
/**
|
|
54
|
+
* What the owner's client reads: every `ServerOnly` field removed, and every plain object left with no field, at any
|
|
55
|
+
* depth. Deeply readonly.
|
|
56
|
+
*/
|
|
57
|
+
export type ClientData<T> = T extends Scalar | undefined ? T : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<K, ClientData<V>> : T extends readonly unknown[] ? {
|
|
58
|
+
readonly [K in keyof T]: ClientData<T[K]>;
|
|
59
|
+
} : {
|
|
60
|
+
readonly [K in keyof T as ServerOnlyField<NonNullable<T[K]>> extends true ? never : K]: ClientData<T[K]>;
|
|
61
|
+
};
|
|
62
|
+
/** A store's server data type, for helper signatures: `export type PlayerData = Profile<typeof PlayerData>`. */
|
|
63
|
+
export type Profile<S> = S extends ProfileStore<infer T> ? ProfileData<T> : never;
|
|
64
|
+
/** A store's client data type. */
|
|
65
|
+
export type ClientProfile<S> = S extends ProfileStore<infer T> ? ClientData<T> : never;
|
|
66
|
+
/**
|
|
67
|
+
* A server handle's own members. `h()` reads, tracked and frozen; while the profile is not writable it returns the
|
|
68
|
+
* template's value at the path. `set` and `update` are one write each through the store's gate and return its result.
|
|
69
|
+
*/
|
|
70
|
+
export interface WriteHandle<V, A> extends Readable<V | A> {
|
|
71
|
+
set(value: V): boolean;
|
|
72
|
+
update(change: (current: V) => V): boolean;
|
|
73
|
+
}
|
|
74
|
+
/** An array handle's writes. Indices are TypeScript's, from 0. */
|
|
75
|
+
export interface ArrayWrites<E> {
|
|
76
|
+
/** Inserts at `index`, from 0 to the length, or appends without one. */
|
|
77
|
+
insert(value: E, index?: number): boolean;
|
|
78
|
+
/** Removes the element at `index`, from 0 to length - 1. */
|
|
79
|
+
remove(index: number): boolean;
|
|
80
|
+
}
|
|
81
|
+
type DataChildren<V, A> = [V] extends [Scalar] ? unknown : [V] extends [readonly (infer E)[]] ? ArrayWrites<E> & {
|
|
82
|
+
readonly [index: number]: DataHandle<E, undefined>;
|
|
83
|
+
} : true extends IsUnion<V> ? unknown : [V] extends [ReadonlyMap<string, infer E>] ? {
|
|
84
|
+
readonly [key: string]: DataHandle<E | undefined, A>;
|
|
85
|
+
} : string extends keyof V ? {
|
|
86
|
+
readonly [key: string]: DataHandle<V[string & keyof V] | undefined, A>;
|
|
87
|
+
} : {
|
|
88
|
+
readonly [K in keyof V]-?: DataHandle<V[K], A>;
|
|
89
|
+
};
|
|
90
|
+
/** A server handle at a position of type `V`: reads `V | A`, writes `V`, and reaches its children by key. */
|
|
91
|
+
export type DataHandle<V, A = never> = WriteHandle<V, A> & DataChildren<NonNullable<V>, A | Extract<V, undefined>>;
|
|
92
|
+
type ReadChildren<V, A> = [V] extends [Scalar] ? unknown : [V] extends [readonly (infer E)[]] ? {
|
|
93
|
+
readonly [index: number]: ReadHandle<E, undefined>;
|
|
94
|
+
} : true extends IsUnion<V> ? unknown : [V] extends [ReadonlyMap<string, infer E>] ? {
|
|
95
|
+
readonly [key: string]: ReadHandle<E | undefined, A>;
|
|
96
|
+
} : string extends keyof V ? {
|
|
97
|
+
readonly [key: string]: ReadHandle<V[string & keyof V] | undefined, A>;
|
|
98
|
+
} : {
|
|
99
|
+
readonly [K in keyof V]-?: ReadHandle<V[K], A>;
|
|
100
|
+
};
|
|
101
|
+
/** A client handle: the same paths and reads as the server's, and no writes. */
|
|
102
|
+
export type ReadHandle<V, A = never> = Readable<V | A> & ReadChildren<NonNullable<V>, A | Extract<V, undefined>>;
|
|
103
|
+
/** Why a profile loaded. */
|
|
104
|
+
export type LoadReason = "join" | "resume";
|
|
105
|
+
/** Why a profile is releasing. */
|
|
106
|
+
export type ReleaseReason = "leave" | "handoff" | "shutdown";
|
|
107
|
+
/** A developer product purchase, read from Roblox's receipt. */
|
|
108
|
+
export interface Purchase {
|
|
109
|
+
readonly id: string;
|
|
110
|
+
readonly productId: number;
|
|
111
|
+
readonly currencySpent: number;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* A grant callback's fourth argument. It stages an effect that runs once, on a fresh thread, after the grant is durably
|
|
115
|
+
* saved and before the purchase is acknowledged; never after a refused grant.
|
|
116
|
+
*/
|
|
117
|
+
export type AfterGrant = (effect: () => void) => void;
|
|
118
|
+
/** A field holding it is the claim. Not callable. */
|
|
119
|
+
export interface ProfileClaim {
|
|
120
|
+
readonly [claimKind]: true;
|
|
121
|
+
}
|
|
122
|
+
/** A durable message to one player's profile, declared in a server module. Invariant in `P`. */
|
|
123
|
+
export interface ProfileMessage<P> {
|
|
124
|
+
readonly [messageKind]: {
|
|
125
|
+
readonly payload: (value: P) => P;
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/** One quarantined message, for recovery tooling. The payload is the stored JSON, unknown keys included. */
|
|
129
|
+
export interface QuarantinedMessage {
|
|
130
|
+
readonly id: OperationId;
|
|
131
|
+
readonly name: string;
|
|
132
|
+
readonly reason: "payload" | "attempts";
|
|
133
|
+
readonly attempts: number;
|
|
134
|
+
/** The id's creation time, in wall-clock seconds. */
|
|
135
|
+
readonly sentAt: number;
|
|
136
|
+
readonly payload: Json;
|
|
137
|
+
}
|
|
138
|
+
/** Why a save or handoff did not confirm. */
|
|
139
|
+
export type SaveFailure = "unavailable" | "timeout" | "uncertain";
|
|
140
|
+
/** A save or handoff result: narrow `ok` first. */
|
|
141
|
+
export type SaveResult = {
|
|
142
|
+
readonly ok: true;
|
|
143
|
+
} | {
|
|
144
|
+
readonly ok: false;
|
|
145
|
+
readonly reason: SaveFailure;
|
|
146
|
+
};
|
|
147
|
+
/**
|
|
148
|
+
* A profile store: one shared declaration, callable on both sides. The server calls it with a player for that player's
|
|
149
|
+
* writable handle; the client calls it with nothing for the local player's read-only handle.
|
|
150
|
+
*/
|
|
151
|
+
export interface ProfileStore<T> {
|
|
152
|
+
readonly [storeKind]: {
|
|
153
|
+
readonly kind: "profiles";
|
|
154
|
+
readonly value: (value: T) => T;
|
|
155
|
+
};
|
|
156
|
+
/** Server: the player's root handle, the same one while they are present. */
|
|
157
|
+
(player: Player): DataHandle<ProfileData<T>>;
|
|
158
|
+
/** Client: the local player's read-only root handle. */
|
|
159
|
+
(): ReadHandle<ClientData<T>>;
|
|
160
|
+
/** Server. Tracked: whether a write to the player's profile would be accepted now. */
|
|
161
|
+
loaded(player: Player): boolean;
|
|
162
|
+
/** Client. Tracked: whether the server has published this player's profile and the client holds all of it. */
|
|
163
|
+
loaded(): boolean;
|
|
164
|
+
/** Server. Tracked: the players whose profiles are writable; it changes only when one starts or stops being. */
|
|
165
|
+
players(): readonly Player[];
|
|
166
|
+
/** Server. Yields until the profile is writable (true), or its load fails, the player leaves or the server closes. */
|
|
167
|
+
await(player: Player): boolean;
|
|
168
|
+
/** Server. Yields until the current revision is durably saved, for a rare critical point. */
|
|
169
|
+
save(player: Player, timeoutSeconds?: number): SaveResult;
|
|
170
|
+
/** Server. Seals and durably releases the profile while the player stays, before a teleport. */
|
|
171
|
+
handoff(player: Player): SaveResult;
|
|
172
|
+
/** Server. After a failed teleport: waits for the handoff's release to settle, then loads again. */
|
|
173
|
+
resume(player: Player): boolean;
|
|
174
|
+
/** Server. Reads any user's saved data, with no lock and no write. */
|
|
175
|
+
inspect(userId: number): StoreResult<ProfileData<T> | undefined>;
|
|
176
|
+
/** Server. Appends a message to a user's inbox, online or not, and waits until it is durable. */
|
|
177
|
+
send<P>(userId: number, message: ProfileMessage<P>, payload: NoInfer<P>, id?: OperationId): StoreResult<undefined>;
|
|
178
|
+
/** Server. A user's quarantined messages, read with no lock. */
|
|
179
|
+
quarantined(userId: number): StoreResult<readonly QuarantinedMessage[]>;
|
|
180
|
+
/** Server. Moves listed quarantined messages back to the inbox ("retry") or drops them for good ("discard"). */
|
|
181
|
+
clearQuarantined(userId: number, ids: readonly OperationId[], action: "retry" | "discard"): StoreResult<number>;
|
|
182
|
+
/** Server claim, a Subsystem field initializer: changes a profile as it loads, before anything reads it. */
|
|
183
|
+
onLoad(transform: (player: Player, data: ProfileData<T>, reason: LoadReason) => ProfileData<T>): ProfileClaim;
|
|
184
|
+
/** Server claim: changes a profile as it releases. A claim that throws is skipped. */
|
|
185
|
+
onRelease(transform: (player: Player, data: ProfileData<T>, reason: ReleaseReason) => ProfileData<T>): ProfileClaim;
|
|
186
|
+
/** Server claim: applies a message to its target's profile, once per message id. */
|
|
187
|
+
receive<P>(message: ProfileMessage<P>, apply: (player: Player, data: ProfileData<T>, payload: P) => ProfileData<T>): ProfileClaim;
|
|
188
|
+
/** Server claim: grants a developer product. The purchase is acknowledged only after the change is saved. */
|
|
189
|
+
grant(productId: number, apply: (player: Player, data: ProfileData<T>, purchase: Purchase, afterGrant: AfterGrant) => ProfileData<T>): ProfileClaim;
|
|
190
|
+
}
|
|
191
|
+
/** A profile store's options. */
|
|
192
|
+
export interface ProfileOptions<T> {
|
|
193
|
+
/**
|
|
194
|
+
* The store's type, by inference, and a new profile's value. A missing required key on an object path from the root
|
|
195
|
+
* takes its value from here. Admitted and frozen once, with every `serverOnly` mark unwrapped.
|
|
196
|
+
*/
|
|
197
|
+
readonly template: T;
|
|
198
|
+
/** Append-only. Removing or reordering a step changes what every older record becomes. */
|
|
199
|
+
readonly migrations?: readonly Migration[];
|
|
200
|
+
}
|
|
201
|
+
/** The handle member names. A template key with one of these names, at any depth, does not compile. */
|
|
202
|
+
export type ReservedName = "set" | "update" | "insert" | "remove";
|
|
203
|
+
type ReservedKeys<T> = T extends ServerOnly<infer V> ? ReservedKeys<V> : T extends Scalar | undefined ? never : T extends ReadonlyMap<unknown, infer V> ? ReservedKeys<V> : T extends readonly (infer E)[] ? ReservedKeys<E> : string extends keyof T ? ReservedKeys<T[string & keyof T]> : (keyof T & ReservedName) | {
|
|
204
|
+
[K in keyof T]-?: ReservedKeys<T[K]>;
|
|
205
|
+
}[keyof T];
|
|
206
|
+
type ReservedKeyCheck<T> = [ReservedKeys<T>] extends [never] ? unknown : {
|
|
207
|
+
readonly template: {
|
|
208
|
+
readonly [K in ReservedKeys<T> as `"${K & string}" is a handle method; rename it`]: never;
|
|
209
|
+
};
|
|
210
|
+
};
|
|
211
|
+
/**
|
|
212
|
+
* Declares a profile store by its DataStore name. It must initialize an exported top-level const in a shared module.
|
|
213
|
+
* The type comes from `template`; annotate empty collections and optional fields with `as`.
|
|
214
|
+
*/
|
|
215
|
+
export declare function profileStore<T extends object>(_name: string, _options: ProfileOptions<T> & ReservedKeyCheck<T>): ProfileStore<T>;
|
|
216
|
+
/** Marks a template field server-only: the owner's client never receives it. */
|
|
217
|
+
export declare function serverOnly<V>(value: V): ServerOnly<V>;
|
|
218
|
+
/**
|
|
219
|
+
* The exact field paths the owner's client receives, each an array of segments: an object field's key, "[]" for an
|
|
220
|
+
* array element and "*" for a map value. A key that is "[]" or "*", or starts with a backslash, gets one more backslash.
|
|
221
|
+
* Sorted segment by segment, by bytes.
|
|
222
|
+
*/
|
|
223
|
+
export declare function replicatedPaths<T>(store: ProfileStore<T>): readonly (readonly string[])[];
|
|
224
|
+
export {};
|