Skip to content

Commit

Permalink
Add untracked method
Browse files Browse the repository at this point in the history
- Add `mutation_detected` check for `computed`
  • Loading branch information
jaredcwhite committed Oct 4, 2023
1 parent 49bdebe commit f67f8fa
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 4 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## [Unreleased]

## [1.2.0] - 2023-10-03

- Add `untracked` method (implements #5)
- Add `mutation_detected` check for `computed`

Gem now roughly analogous to `@preact/signals-core` v1.5

## [1.1.0] - 2023-03-25

- Provide better signal/computed inspect strings (fixes #1)
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
signalize (1.1.0)
signalize (1.2.0)
concurrent-ruby (~> 1.2)

GEM
Expand Down
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,27 @@ counter = signal(0)
counter.value += 1
```

### `untracked { }`

In case when you're receiving a callback that can read some signals, but you don't want to subscribe to them, you can use `untracked` to prevent any subscriptions from happening.

```ruby
require "signalize"
include Signalize::API

counter = signal(0)
effect_count = signal(0)
fn = proc { effect_count.value + 1 }

effect do
# Logs the value
puts counter.value

# Whenever this effect is triggered, run `fn` that gives new value
effect_count.value = untracked(&fn)
end
```

### `computed { }`

You derive computed state by accessing a signal's value within a `computed` block and returning a new value. Every time that signal value is updated, a computed value will likewise be updated. Actually, that's not quite accurate — the computed value only computes when it's read. In this sense, we can call computed values "lazily-evaluated".
Expand Down
29 changes: 27 additions & 2 deletions lib/signalize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def self.cycle_detected
raise Signalize::Error, "Cycle detected"
end

def self.mutation_detected
raise Signalize::Error, "Computed cannot have side-effects"
end

RUNNING = 1 << 0
NOTIFIED = 1 << 1
OUTDATED = 1 << 2
Expand All @@ -34,6 +38,10 @@ def self.cycle_detected
global_map_accessor :eval_context
self.eval_context = nil

# Used by `untracked` method
global_map_accessor :untracked_depth
self.untracked_depth = 0

# Effects collected into a batch.
global_map_accessor :batched_effect
self.batched_effect = nil
Expand Down Expand Up @@ -414,6 +422,8 @@ def value
end

def value=(value)
Signalize.mutation_detected if Signalize.eval_context.is_a?(Signalize::Computed)

if value != @value
Signalize.cycle_detected if Signalize.batch_iteration > 100

Expand Down Expand Up @@ -484,7 +494,7 @@ def _refresh
return true
end

prevContext = Signalize.eval_context
prev_context = Signalize.eval_context
begin
Signalize.prepare_sources(self)
Signalize.eval_context = self
Expand All @@ -499,7 +509,7 @@ def _refresh
@_flags |= HAS_ERROR
@_version += 1
end
Signalize.eval_context = prevContext
Signalize.eval_context = prev_context
Signalize.cleanup_sources(self)
@_flags &= ~RUNNING

Expand Down Expand Up @@ -667,6 +677,21 @@ def batch
Signalize.end_batch
end
end

def untracked
return yield unless Signalize.untracked_depth.zero?

prev_context = Signalize.eval_context
Signalize.eval_context = nil
Signalize.untracked_depth += 1

begin
return yield
ensure
Signalize.untracked_depth -= 1
Signalize.eval_context = prev_context
end
end
end

extend API
Expand Down
2 changes: 1 addition & 1 deletion lib/signalize/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Signalize
VERSION = "1.1.0"
VERSION = "1.2.0"
end
27 changes: 27 additions & 0 deletions test/test_signalize.rb
Original file line number Diff line number Diff line change
Expand Up @@ -146,4 +146,31 @@ def test_subscribe
test_value = 10
counter.value = test_value # logs the new value
end

def test_disallow_setting_signal_in_computed
v = 123
a = signal(v)
c = computed { a.value += 1 }
error = assert_raises(Signalize::Error) do
c.value
end

assert_equal "Computed cannot have side-effects", error.message
assert_equal v, a.value
end

def test_run_untracked_callback_once
calls = 0
a = signal(1);
b = signal(2);
spy = proc do
calls += 1
a.value + b.value
end
effect { untracked(&spy) }
a.value = 10
b.value = 20

assert_equal 1, calls
end
end

0 comments on commit f67f8fa

Please sign in to comment.