@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.
Files changed (3) hide show
  1. package/README.md +55 -34
  2. package/package.json +2 -2
  3. 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 based on [fine-grained reactivity](https://dev.to/ryansolid/a-hands-on-introduction-to-fine-grained-reactivity-3ndf). Connect behavior to data with reactive signals, ensuring games stay up-to-date with their underlying data while eliminating the need for manual updates.
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 state in [signals](#signalinitialvalue-equals): state containers that hold a value
26
- - React to state changes with [effects](#effectcallback): re-run code when a dependency updates
27
- - Derive values from state with [computed signals](#computedgetter): memoized functions with dependency tracking
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 signals?**
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://angular.dev/guide/signals
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, mapper)`](#mappedgetter-mapper)
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, mapper)`
475
+ ### `mapped(getter, transform)`
476
476
 
477
- The `mapped` function iterates over every key in a table and uses the mapper 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, the mapper is called for that key and its current value.
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 mapper is used as the new value:
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 mapper returns two values, the second value is used as the new key:
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 and other development environments where the Luau optimization level is `O1` or lower.
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 joins on the server, call `server.addSignalsToClient` with the keyed signals that the client should receive updates for. Once they leave, call `server.removeClient` to unsubscribe them from all updates.
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
- local function onPlayerAdded(player: Player)
649
- -- Add signal getters, computed signals, atoms, or reactive objects
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 signal across all players. If two players subscribe to the same key, but were given different signals, Charm will output a warning.
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 Roblox remote event limitations regarding array patches. Disable this if your networking library serializes remote arguments (Zap, ByteNet, etc.). |
705
- | validatePatches | `true` | When `true`, and both `fixArrays` and strict mode are enabled, synced values containing unsafe sparse arrays or mixed tables will throw an error. See the [remote argument limitations](https://create.roblox.com/docs/scripting/events/remote#argument-limitations). |
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
- Players.PlayerAdded:Connect(function(player)
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 player:
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
- server.addSignalsToClient(player, {
729
- [`data-{player.UserId}`] = computed(function()
730
- return getPlayerData(player.UserId)
731
- end),
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
- Players.PlayerAdded:Connect(function(player)
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-rc.1",
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-rc.5"
21
+ "@rbxts/charm": "^0.11.0"
22
22
  },
23
23
  "scripts": {
24
24
  "publish:wally": "wally publish"
package/src/init.luau CHANGED
@@ -36,7 +36,7 @@ end
36
36
  ]]
37
37
  local function toRaw<T>(value: T): T
38
38
  if isReactive(value) then
39
- local value = value :: ReactiveProxy<T> & T
39
+ value = value :: ReactiveProxy<T>
40
40
  value.__track()
41
41
  return toRaw(value.__target)
42
42
  end