@rbxts/deep-charm 0.1.0-rc.1 → 0.1.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/README.md +55 -34
- package/package.json +2 -2
- package/src/init.luau +1 -1
package/README.md
CHANGED
|
@@ -18,20 +18,20 @@
|
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
Charm is a state management library
|
|
21
|
+
Charm is a reactive state management library designed for storing data in discrete containers called _signals_. Connect behavior to data with signals, ensuring systems stay up-to-date with their underlying data while reactivity eliminates the need for manual updates.
|
|
22
22
|
|
|
23
23
|
**Build game state from simple building blocks:**
|
|
24
24
|
|
|
25
|
-
- Store
|
|
26
|
-
- React to state changes with [effects](#effectcallback): re-run code when
|
|
27
|
-
- Derive values from
|
|
25
|
+
- Store data in [signals](#signalinitialvalue-equals): reactive state containers that hold a value
|
|
26
|
+
- React to state changes with [effects](#effectcallback): re-run code when signals update
|
|
27
|
+
- Derive new values from data with [computed signals](#computedgetter): memoize functions that access signals
|
|
28
28
|
|
|
29
|
-
**Want to learn more about
|
|
29
|
+
**Want to learn more about reactivity?**
|
|
30
30
|
|
|
31
31
|
- https://dev.to/ryansolid/a-hands-on-introduction-to-fine-grained-reactivity-3ndf
|
|
32
32
|
- https://preactjs.com/blog/introducing-signals
|
|
33
33
|
- https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity
|
|
34
|
-
- https://
|
|
34
|
+
- https://github.com/roblox/signals
|
|
35
35
|
|
|
36
36
|
[Migrating from an older version of Charm?](#migration)
|
|
37
37
|
|
|
@@ -51,7 +51,7 @@ Charm is a state management library based on [fine-grained reactivity](https://d
|
|
|
51
51
|
- [`subscribe(getter, callback)`](#subscribegetter-callback)
|
|
52
52
|
- [`untracked(callback)`](#untrackedcallback)
|
|
53
53
|
- [`batch(callback)`](#batchcallback)
|
|
54
|
-
- [`mapped(getter,
|
|
54
|
+
- [`mapped(getter, transform)`](#mappedgetter-transform)
|
|
55
55
|
- [`onCleanup(callback, failSilently?)`](#oncleanupcallback-failsilently)
|
|
56
56
|
- [`atom(initialValue, equals?)`](#atominitialvalue-equals)
|
|
57
57
|
- [`trigger(callback)`](#triggercallback)
|
|
@@ -472,11 +472,11 @@ end)
|
|
|
472
472
|
|
|
473
473
|
---
|
|
474
474
|
|
|
475
|
-
### `mapped(getter,
|
|
475
|
+
### `mapped(getter, transform)`
|
|
476
476
|
|
|
477
|
-
The `mapped` function iterates over every key in a table and uses the
|
|
477
|
+
The `mapped` function iterates over every key in a table and uses the transform function to assign them to a new key and value. The result is returned as a read-only signal containing the new keys and values. When a key's value changes, or a new key is added to the table, `transform` is called for that key and its current value.
|
|
478
478
|
|
|
479
|
-
The first value returned by the
|
|
479
|
+
The first value returned by the transform function is used as the new value:
|
|
480
480
|
|
|
481
481
|
```luau
|
|
482
482
|
local getList, setList = signal({ "a", "b", "c" })
|
|
@@ -488,7 +488,7 @@ end)
|
|
|
488
488
|
print(getUppercase()) -- { "A", "B", "C" }
|
|
489
489
|
```
|
|
490
490
|
|
|
491
|
-
If the
|
|
491
|
+
If the transform function returns two values, the second value is used as the new key:
|
|
492
492
|
|
|
493
493
|
```luau
|
|
494
494
|
local getList, setList = signal({ "a", "b", "c" })
|
|
@@ -602,7 +602,7 @@ Charm exposes the following global flags to customize behavior:
|
|
|
602
602
|
| frozen | `true`\* | Enforces data immutability by deep-freezing tables passed to signals, excluding objects with metatables. |
|
|
603
603
|
| trackInnerEffects | `true` | Whether nested effects should be tracked and cleaned up when the parent effect re-runs. This should only be disabled to debug issues during migration. |
|
|
604
604
|
|
|
605
|
-
The `strict` and `frozen` flags are automatically enabled in Roblox Studio
|
|
605
|
+
The `strict` and `frozen` flags are automatically enabled in Roblox Studio.
|
|
606
606
|
|
|
607
607
|
---
|
|
608
608
|
|
|
@@ -640,25 +640,19 @@ return {
|
|
|
640
640
|
}
|
|
641
641
|
```
|
|
642
642
|
|
|
643
|
-
When a player
|
|
643
|
+
When a player notifies the server that they're ready to start syncing, call `server.addSignalsToClient` with the signals that the client should receive updates for. Once they leave, call `server.removeClient` to unsubscribe them from all updates.
|
|
644
644
|
|
|
645
|
-
Then, use `server.connect` to specify how state updates should be sent to each client. Pass a callback function that fires a remote with the given target player and the state updates they subscribed to
|
|
645
|
+
Then, use `server.connect` to specify how state updates should be sent to each client. Pass a callback function that fires a remote with the given target player and the state updates they subscribed to:
|
|
646
646
|
|
|
647
647
|
```luau
|
|
648
|
-
|
|
649
|
-
--
|
|
648
|
+
playerReadyEvent.OnServerEvent:Connect(function(player)
|
|
649
|
+
-- Sync signal getters, computed signals, atoms, or reactive proxies
|
|
650
650
|
server.addSignalsToClient(player, {
|
|
651
651
|
name = nameStore.getName,
|
|
652
652
|
surname = nameStore.getSurname,
|
|
653
653
|
age = nameStore.ageAtom,
|
|
654
654
|
})
|
|
655
|
-
end
|
|
656
|
-
|
|
657
|
-
for _, player in Players:GetPlayers() do
|
|
658
|
-
onPlayerAdded(player)
|
|
659
|
-
end
|
|
660
|
-
|
|
661
|
-
Players.PlayerAdded:Connect(onPlayerAdded)
|
|
655
|
+
end)
|
|
662
656
|
|
|
663
657
|
Players.PlayerRemoving:Connect(function(player)
|
|
664
658
|
server.removeClient(player)
|
|
@@ -671,7 +665,7 @@ end)
|
|
|
671
665
|
```
|
|
672
666
|
|
|
673
667
|
> [!NOTE]
|
|
674
|
-
> On the server, make sure each key corresponds to the same
|
|
668
|
+
> On the server, make sure each key corresponds to the same data across all players. If two players subscribe to the same key, but were given different signals, Charm will output a warning.
|
|
675
669
|
|
|
676
670
|
To sync the client with the server's state, call `client.addSignals` with a table of writable signals (setter functions or atoms) whose keys match their server counterparts.
|
|
677
671
|
|
|
@@ -701,8 +695,8 @@ A configuration table that customizes the behavior of Charm Sync on the server.
|
|
|
701
695
|
| --------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
702
696
|
| interval | `0` | The frequency at which the server will send patches to the client, in seconds. A value of `0` sends updates on the next frame. Set to a negative value to disable the interval. |
|
|
703
697
|
| preserveHistory | `false` | Whether to preserve a full history of state changes since the last sync, at the cost of performance. This is useful if you need to replicate each individual change that occurs between sync events. |
|
|
704
|
-
| fixArrays | `true` | When `true`, Charm will attempt to work around
|
|
705
|
-
| validatePatches | `true` | When `true`, and both `fixArrays` and strict mode are enabled, synced values containing unsafe sparse arrays or mixed tables will
|
|
698
|
+
| fixArrays | `true` | When `true`, Charm will attempt to work around remote event limitations that cause data loss in array patches. Disable this if your networking library serializes remote arguments (Zap, ByteNet, etc.). |
|
|
699
|
+
| validatePatches | `true` | When `true`, and both `fixArrays` and strict mode are enabled, synced values containing unsafe sparse arrays or mixed tables will emit a warning. See the [remote argument limitations](https://create.roblox.com/docs/scripting/events/remote#argument-limitations). |
|
|
706
700
|
|
|
707
701
|
---
|
|
708
702
|
|
|
@@ -713,7 +707,7 @@ The `addSignalsToClient` function subscribes a client to updates in the given si
|
|
|
713
707
|
You can pass signal getter functions, computed signals, atoms, and [reactive objects](#reactiveinitialvalue) in the `getters` table. This function can also be called multiple times on the same client to subscribe to new signals.
|
|
714
708
|
|
|
715
709
|
```luau
|
|
716
|
-
|
|
710
|
+
playerReadyEvent.OnServerEvent:Connect(function(player)
|
|
717
711
|
server.addSignalsToClient(player, {
|
|
718
712
|
name = nameStore.getName,
|
|
719
713
|
surname = nameStore.getSurname,
|
|
@@ -722,14 +716,22 @@ Players.PlayerAdded:Connect(function(player)
|
|
|
722
716
|
end)
|
|
723
717
|
```
|
|
724
718
|
|
|
725
|
-
You're also allowed to create new signals to sync to specific players, as long as the key is unique to that
|
|
719
|
+
You're also allowed to create new signals to sync to specific players, as long as the key is unique to that data:
|
|
726
720
|
|
|
727
721
|
```luau
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
722
|
+
playerReadyEvent.OnServerEvent:Connect(function(player)
|
|
723
|
+
server.addSignalsToClient(player, {
|
|
724
|
+
-- 🟢 Good: Player-specific data is synced with a unique key
|
|
725
|
+
[`data-{player.UserId}`] = computed(function()
|
|
726
|
+
return getPlayerData(player.UserId)
|
|
727
|
+
end),
|
|
728
|
+
|
|
729
|
+
-- 🔴 Bad: Syncing different data with the same keys does not work
|
|
730
|
+
playerData = computed(function()
|
|
731
|
+
return getPlayerData(player.UserId)
|
|
732
|
+
end),
|
|
733
|
+
})
|
|
734
|
+
end)
|
|
733
735
|
```
|
|
734
736
|
|
|
735
737
|
---
|
|
@@ -754,7 +756,7 @@ server.removeSignalsFromClient(player, "surname")
|
|
|
754
756
|
The `removeClient` function unsubscribes a client from receiving all state updates from the server. You should call this function when a player leaves the game.
|
|
755
757
|
|
|
756
758
|
```luau
|
|
757
|
-
|
|
759
|
+
playerReadyEvent.OnServerEvent:Connect(function(player)
|
|
758
760
|
server.addSignalsToClient(player, signals)
|
|
759
761
|
end)
|
|
760
762
|
|
|
@@ -883,6 +885,9 @@ You can opt-in to deep reactivity with the Deep Charm library:
|
|
|
883
885
|
|
|
884
886
|
### Installation
|
|
885
887
|
|
|
888
|
+
> [!WARNING]
|
|
889
|
+
> Deep Charm is an experimental library that is not fully documented, and the API is subject to breaking changes.
|
|
890
|
+
|
|
886
891
|
```sh
|
|
887
892
|
npm install @rbxts/deep-charm
|
|
888
893
|
yarn add @rbxts/deep-charm
|
|
@@ -938,6 +943,22 @@ local proxy = reactive(raw)
|
|
|
938
943
|
print(toRaw(proxy) == raw) -- true
|
|
939
944
|
```
|
|
940
945
|
|
|
946
|
+
Calling `toRaw()` on a proxy also subscribes to its changes. This allows you to use proxies with `observe()` and other Charm APIs that accept a getter function:
|
|
947
|
+
|
|
948
|
+
```luau
|
|
949
|
+
local proxy, updateProxy = reactive({})
|
|
950
|
+
|
|
951
|
+
observe(function()
|
|
952
|
+
return toRaw(proxy)
|
|
953
|
+
end, function(value, key)
|
|
954
|
+
print(`Value {value} added at key {key}`)
|
|
955
|
+
end)
|
|
956
|
+
|
|
957
|
+
updateProxy(function(state)
|
|
958
|
+
table.insert(state, "foo")
|
|
959
|
+
end) -- Value foo added at key 1
|
|
960
|
+
```
|
|
961
|
+
|
|
941
962
|
---
|
|
942
963
|
|
|
943
964
|
### `isReactive(value)`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rbxts/deep-charm",
|
|
3
|
-
"version": "0.1.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "Use Charm with plain Luau tables",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "src/init.luau",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"url": "git+https://github.com/littensy/charm.git"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@rbxts/charm": "^0.11.0
|
|
21
|
+
"@rbxts/charm": "^0.11.0"
|
|
22
22
|
},
|
|
23
23
|
"scripts": {
|
|
24
24
|
"publish:wally": "wally publish"
|