autodeps 0.0.1 → 0.0.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5a82a1eed613fef969c31d65598dfa94f222f79c
4
- data.tar.gz: aba6eadbf53b938b03bfc5c1f8bc34f4fc886479
3
+ metadata.gz: 78d3bef272635791a3db6c22f57a91207ce38736
4
+ data.tar.gz: a749c7af7789588b1842ae39fccfe1f3de603569
5
5
  SHA512:
6
- metadata.gz: f488ed931e63f8c34def7194d816f385f050e8405ea618d09ae67706218aa8fbfe7f5daea7bd02527cd7d57a4450b3552e60768c4b4152870bac1fafcdc09980
7
- data.tar.gz: 86806e9a550bf0a6dbabc132d24eb84089cb219e9de0bba02d8a1b991ed8736f546f207af83f449bdd459dc33b72fcf81d485aff1f607d4d12188ba0eccc3bfa
6
+ metadata.gz: fb3fa0654c138919354941a09988bb8c2392f9da56e6fcca577fdb7334cc6a18d61998d18aeb68bc562be0d4bd2e23e7f377657194142226fea22c59231228d8
7
+ data.tar.gz: 511df559b1d3fefd0667bddcf7983c4859d3f3df55e42d8b21032e4b56e537cdbe7fd21ac60d5ddf8596961dc210103d1fcdc50cac7c30a0cbfd6853129aba12
data/Gemfile CHANGED
@@ -2,3 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in autodeps.gemspec
4
4
  gemspec
5
+
data/README.md CHANGED
@@ -19,6 +19,90 @@ Or install it yourself as:
19
19
  ## Usage
20
20
 
21
21
  TODO: Write usage instructions here
22
+ two parts
23
+ 1.ReactivePersistency
24
+ 2.ReactiveData
25
+
26
+ ReactivePersistency is used in data storage layer, like you have an activerecord/mongomapper persistency layer,
27
+ when some properties you want to copy to other table(denormalization), and want to be automatically synced when
28
+ main table's property changes,
29
+ then just include Autodeps::Persistency and call depend_on method:
30
+ like you have User table, and Laud table, where you copy user.name to Laud.user_name and Laud.dest_user_name
31
+ ```
32
+ class Laud
33
+
34
+ include MongoMapper::Document
35
+ include Autodeps::Persistency #这里添加对Autodeps::Persistency 的依赖
36
+
37
+ key :user_id, Integer, :required => true
38
+ key :user_name, String
39
+ depend_on "User", :value_mapping => {:name => :user_name} #默认可以省略 :key_mapping=> {:id=>:user_id}
40
+
41
+ key :dest_user_id, Integer, :required => true
42
+ key :dest_user_name, String
43
+ depend_on "User", :key_mapping => {:id => :dest_user_id}, :value_mapping => {:name => :dest_user_name}
44
+
45
+ timestamps!
46
+
47
+ ensure_index([[:user_id,1],[:dest_user_id,1]])
48
+ end
49
+ ```
50
+ then when you change user's name
51
+ ```
52
+ user.name = "some value"
53
+ user.save
54
+ ```
55
+ the name change will automatically propagated to Laud table/collection.
56
+
57
+ because in development environment, when User loads, the Laud class may not been loaded, so you may want to
58
+ do this:
59
+ #user.rb
60
+ class User
61
+ #some attributes
62
+ end
63
+ Laud #just adds a reference here to cause rails to load Laud
64
+
65
+ 2.ReactiveData
66
+ what ReactiveData means, if you want to say a=b+c
67
+ if b or c is ReactiveData, then when b or c's value changes,
68
+ a's value automatically changes,it's the concept copyed from meteor:)
69
+
70
+ see autodeps_test.rb
71
+ where a,b is ReactiveData
72
+
73
+ def test_reactive_integer
74
+ a = Autodeps::ReactiveData.new(3)
75
+ b = nil
76
+ computation = Autodeps.autorun do |computation| #the block will be rerun if a changes
77
+ b = a.value
78
+ end
79
+ assert_equal b,3
80
+
81
+ a.change_to 5
82
+
83
+ assert_equal b,5
84
+ end
85
+
86
+ def test_reactive_integer_add
87
+ a = Autodeps::ReactiveData.new(3)
88
+ b = Autodeps::ReactiveData.new(5)
89
+ c = nil
90
+ computation = Autodeps.autorun do |computation| #the block will be rerun if a or b changes
91
+ c = a.value + b.value
92
+ end
93
+ assert_equal c,8
94
+
95
+ a.change_to 5
96
+
97
+ assert_equal c,10
98
+
99
+ b.change_to 15
100
+
101
+ assert_equal c,20
102
+ end
103
+
104
+
105
+
22
106
 
23
107
  ## Contributing
24
108
 
@@ -20,4 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.5"
22
22
  spec.add_development_dependency "rake"
23
+
24
+ spec.add_runtime_dependency "thread_safe"
23
25
  end
@@ -4,6 +4,7 @@ module Autodeps
4
4
  # Your code goes here...
5
5
  end
6
6
 
7
+ require 'thread_safe'
7
8
  require "autodeps/autodeps"
8
9
  require "autodeps/computation"
9
10
  require "autodeps/dependency"
@@ -1,8 +1,13 @@
1
+ require 'logger'
1
2
  module Autodeps
2
- @pending_computations = []
3
- @after_flush_callbacks = []
3
+ class << self
4
+ attr_accessor :logger
5
+ end
6
+ self.logger = Logger.new(STDOUT)
7
+ @pending_computations = ThreadSafe::Array.new
8
+ @after_flush_callbacks = ThreadSafe::Array.new
4
9
  @constructingComputation = false
5
- @active = false
10
+
6
11
 
7
12
  class << self
8
13
  attr_accessor :active
@@ -16,9 +21,26 @@ module Autodeps
16
21
  @constructingComputation = true
17
22
  c = Computation.new(block, Autodeps.current_computation);
18
23
 
24
+
25
+ if (Autodeps.active)
26
+ Autodeps.on_invalidate do
27
+ c.stop();
28
+ end
29
+ end
30
+
19
31
  return c
20
32
  end
21
33
 
34
+ def nonreactive(&f)
35
+ previous = self.current_computation;
36
+ self.current_computation = nil;
37
+ begin
38
+ f.call()
39
+ ensure
40
+ self.current_computation = previous;
41
+ end
42
+ end
43
+
22
44
  def flush
23
45
 
24
46
  #if (inFlush)
@@ -59,10 +81,125 @@ module Autodeps
59
81
 
60
82
  end
61
83
 
84
+ def on_invalidate(&f)
85
+ if (! Autodeps.active)
86
+ raise "AutoDeps.on_invalidate requires a currentComputation"
87
+ end
88
+
89
+ Autodeps.current_computation.on_invalidate(f);
90
+ end
91
+
92
+ def afterFlush(&f)
93
+ @after_flush_callbacks.push(f);
94
+ end
95
+
96
+ def isolateValue(equals=nil, &f)
97
+ raise "must define a block in isolateValue" unless f
98
+ if (!Autodeps.active)
99
+ return f.call();
100
+ end
101
+
102
+ result_dep = Autodeps::Dependency.new;
103
+ orig_result = nil
104
+ Autodeps.autorun do |c|
105
+ result = f.call();
106
+ if (c.first_run)
107
+ orig_result = result;
108
+ elsif (!(equals ? equals(result, orig_result) :
109
+ result == orig_result))
110
+ result_dep.changed();
111
+ end
112
+ end
113
+ result_dep.depend();
114
+
115
+ return orig_result;
116
+ end
117
+
118
+ def embox(equals=nil, &func)
119
+ raise "must define a block in embox" unless func
120
+
121
+
122
+ curResult = nil;
123
+ #There's one shared Dependency and Computation for all callers of
124
+
125
+ # our box function. It gets kicked off if necessary, and when
126
+ # there are no more dependents, it gets stopped to avoid leaking
127
+ # memory.
128
+ resultDep = nil;
129
+ computation = nil;
130
+
131
+ return proc do
132
+ if (!computation)
133
+ if (!Autodeps.active)
134
+ # Not in a reactive context. Just call func, and don't start a
135
+ # computation if there isn't one running already.
136
+ break func.call();
137
+ end
138
+
139
+ # No running computation, so kick one off. Since this computation
140
+ # will be shared, avoid any association with the current computation
141
+ # by using `Deps.nonreactive`.
142
+ resultDep = Autodeps::Dependency.new;
143
+
144
+ computation = Autodeps.nonreactive do
145
+ break Autodeps.autorun do |c|
146
+ oldResult = curResult;
147
+ curResult = func.call();
148
+ if (!c.first_run)
149
+ if (!(equals ? equals.call(curResult, oldResult) :
150
+ curResult == oldResult))
151
+ resultDep.changed();
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ if (Autodeps.active)
159
+ is_new = resultDep.depend();
160
+ if (is_new)
161
+ # For each new dependent, schedule a task for after that dependents
162
+ # invalidation time and the subsequent flush. The task checks
163
+ # whether the computation should be torn down.
164
+ Autodeps.on_invalidate do
165
+ if (resultDep && !resultDep.hasDependents())
166
+ Autodeps.afterFlush do
167
+ # use a second afterFlush to bump ourselves to the END of the
168
+ # flush, after computation re-runs have had a chance to
169
+ # re-establish their connections to our computation.
170
+ Autodeps.afterFlush do
171
+ if (resultDep && !resultDep.hasDependents())
172
+ computation.stop();
173
+ computation = nil;
174
+ resultDep = nil;
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
182
+
183
+ curResult
184
+ end
185
+ end
186
+
187
+ def embox_value(value, equals=nil)
188
+ raise "embox_value on a direct value not implemented"
189
+ end
190
+
191
+ def active
192
+ Thread.current["Autodeps::active"]
193
+ end
194
+
195
+ def active= (active)
196
+ Thread.current["Autodeps::active"] = active
197
+ end
198
+
62
199
  def current_computation
63
200
  Thread.current["Autodeps::current_computation"]
64
201
  end
65
- def current_computation=(computation)
202
+ def current_computation= (computation)
66
203
  Thread.current["Autodeps::current_computation"] = computation
67
204
  self.active = !! computation;
68
205
  end
@@ -1,6 +1,6 @@
1
1
  module Autodeps
2
2
  class Computation
3
- attr_accessor :stopped, :invalidated, :first_run, :parent, :block, :recomputing
3
+ attr_accessor :stopped, :invalidated, :first_run, :parent, :block, :recomputing,:_on_invalidate_callbacks
4
4
  def initialize(block, parent=nil)
5
5
  self.stopped = false;
6
6
  self.invalidated = false;
@@ -9,6 +9,7 @@ module Autodeps
9
9
  self.parent = parent;
10
10
  self.block = block;
11
11
  self.recomputing = false;
12
+ self._on_invalidate_callbacks = ThreadSafe::Array.new
12
13
 
13
14
  errored = true;
14
15
  begin
@@ -31,6 +32,9 @@ module Autodeps
31
32
  in_compute = true;
32
33
  begin
33
34
  block.call(self)
35
+ #rescue => e
36
+ # Autodeps.logger.error(e.message) if Autodeps.logger
37
+ # Autodeps.logger.error(e.backtrace.join("\n")) if Autodeps.logger
34
38
  ensure
35
39
  Autodeps.current_computation = previous;
36
40
  in_compute = false;
@@ -41,7 +45,12 @@ module Autodeps
41
45
  self.recomputing = true
42
46
 
43
47
  while (self.invalidated && !self.stopped)
44
- self.compute() rescue nil
48
+ begin
49
+ self.compute()
50
+ rescue => e
51
+ Autodeps.logger.error(e.message) if Autodeps.logger
52
+ Autodeps.logger.error(e.backtrace.join("\n")) if Autodeps.logger
53
+ end
45
54
  end
46
55
 
47
56
  self.recomputing = false;
@@ -58,26 +67,52 @@ module Autodeps
58
67
  Autodeps::flush
59
68
  end
60
69
 
61
-
62
70
  def invalidate
63
- #we request an immediate flush because we don't have timeout
71
+ ##we request an immediate flush because we don't have timeout
64
72
 
65
73
 
66
74
 
67
75
  if (! self.invalidated)
68
- # if we're currently in _recompute(), don't enqueue
69
- # ourselves, since we'll rerun immediately anyway.
70
- if (! self.recomputing && ! self.stopped)
76
+ # if we're currently in _recompute(), don't enqueue
77
+ # ourselves, since we'll rerun immediately anyway.
78
+
71
79
  self.invalidated = true;
72
- Autodeps.add_pending_computation(self);
73
- require_flush();
74
- end
80
+ self._on_invalidate_callbacks.each do |f|
81
+ f.call(); #// already bound with self as argument
82
+ end
83
+ self._on_invalidate_callbacks = ThreadSafe::Array.new;
75
84
 
85
+ if (! self.recomputing && ! self.stopped)
86
+ self.invalidated = true;
76
87
 
88
+ Autodeps.add_pending_computation(self);
89
+ require_flush();
90
+ end
77
91
 
78
92
  # callbacks can't add callbacks, because
79
93
  #self.invalidated === true.
94
+
95
+
80
96
  end
81
97
  end
98
+
99
+ def on_invalidate(f=nil, &block)
100
+ if block_given?
101
+ f = block
102
+ end
103
+ raise ("on_invalidate requires a block") unless f;
104
+
105
+ g = proc do
106
+ Autodeps.nonreactive do
107
+ f.call(self);
108
+ end
109
+ end
110
+
111
+ if (self.invalidated)
112
+ g();
113
+ else
114
+ self._on_invalidate_callbacks.push(g);
115
+ end
116
+ end
82
117
  end
83
118
  end
@@ -2,7 +2,7 @@ module Autodeps
2
2
  class Dependency
3
3
  attr_accessor :dependents
4
4
  def initialize
5
- @dependents = []
5
+ @dependents = ThreadSafe::Array.new
6
6
  end
7
7
  def depend(computation = Autodeps.current_computation)
8
8
 
@@ -12,9 +12,9 @@ module Autodeps
12
12
  end
13
13
  if !@dependents.include?(computation)
14
14
  @dependents << computation
15
- #computation.onInvalidate(function () {
16
- # delete self._dependentsById[id];
17
- #});
15
+ computation.on_invalidate do
16
+ @dependents.delete(computation)
17
+ end
18
18
  return true
19
19
  else
20
20
  return false;
@@ -27,5 +27,9 @@ module Autodeps
27
27
  computation.invalidate
28
28
  end
29
29
  end
30
+
31
+ def hasDependents
32
+ !@dependents.empty?
33
+ end
30
34
  end
31
35
  end
@@ -1,3 +1,3 @@
1
1
  module Autodeps
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -48,4 +48,196 @@ class AutoDepsTest < Test::Unit::TestCase
48
48
 
49
49
  assert_equal c,20
50
50
  end
51
+
52
+ def test_exception
53
+ a = Autodeps::ReactiveData.new(3)
54
+ b = Autodeps::ReactiveData.new(5)
55
+ c = nil
56
+ computation = nil
57
+ begin
58
+ Autodeps.autorun do |_computation|
59
+ computation = _computation
60
+ c = a.value + b.value
61
+ dd
62
+ end
63
+ rescue
64
+ end
65
+ #puts computation
66
+ #assert_equal c,8
67
+ #
68
+ #a.change_to 5
69
+ #
70
+ #assert_equal c,10
71
+ #
72
+ #b.change_to 15
73
+ #
74
+ #assert_equal c,20
75
+ end
76
+
77
+ def test_isolation
78
+ a = Autodeps::ReactiveData.new(3)
79
+ b = Autodeps::ReactiveData.new(5)
80
+ c = nil
81
+ count = 0
82
+ computation = nil
83
+ result = nil
84
+ Autodeps.autorun do ###1
85
+ count += 1
86
+ result = Autodeps.isolateValue do ###2
87
+ a.value >= 3
88
+ end
89
+ end
90
+ assert_equal true, result
91
+ assert_equal 1, count #the ###1 blocks gets run first_time
92
+ a.change_to 5
93
+ assert_equal true, result
94
+ assert_equal 1, count #the ###1 blocks doesn't gets run again because ###2's value doesn't change, the isolateValue call isolates it
95
+ a.change_to 2
96
+ assert_equal false, result
97
+ assert_equal 2, count #the ###1 blocks gets run again, because the isolated block's value changes
98
+
99
+ end
100
+
101
+ def test_embox
102
+ a = Autodeps::ReactiveData.new(3)
103
+ b = Autodeps::ReactiveData.new(5)
104
+ c = nil
105
+ count = 0
106
+
107
+ inner = proc do
108
+ a.value
109
+ count += 1
110
+ end
111
+
112
+ outter = Autodeps.embox do
113
+ inner.call
114
+ end
115
+
116
+ Autodeps.autorun do ###1
117
+ inner.call
118
+ end
119
+ Autodeps.autorun do ###1
120
+ inner.call
121
+ end
122
+ assert_equal 2, count
123
+
124
+
125
+ a.change_to 4
126
+ assert_equal 4, count
127
+ end
128
+
129
+ def test_embox1
130
+ a = Autodeps::ReactiveData.new(3)
131
+ b = Autodeps::ReactiveData.new(5)
132
+ c = nil
133
+ count = 0
134
+
135
+ inner = proc do
136
+ a.value
137
+ count += 1
138
+ end
139
+
140
+ outter = Autodeps.embox do
141
+ inner.call
142
+ end
143
+
144
+ Autodeps.autorun do ###1
145
+ outter.call
146
+ end
147
+ Autodeps.autorun do ###1
148
+ outter.call
149
+ end
150
+ assert_equal 1, count
151
+
152
+
153
+ a.change_to 4
154
+ assert_equal 2, count
155
+ end
156
+
157
+ def test_nested_computation
158
+ a = Autodeps::ReactiveData.new(3)
159
+ b = Autodeps::ReactiveData.new(5)
160
+
161
+ count_1 = 0
162
+ count_2 = 0
163
+ compuation1 = nil
164
+ compuation2 = nil
165
+
166
+ compuation1 = Autodeps.autorun do ###1
167
+ a.value
168
+ count_1 += 1
169
+ compuation2 = Autodeps.autorun do ###1
170
+ b.value
171
+ count_2 += 1
172
+ puts "b is " + b.value.to_s + " , count: " + count_2.to_s
173
+ end
174
+ end
175
+ assert_equal 1, count_1
176
+ assert_equal 1, count_2
177
+ tmp_compuation1 = compuation1
178
+ tmp_compuation2 = compuation2
179
+ b.change_to 2
180
+
181
+ assert_equal 1, count_1
182
+ assert_equal 2, count_2
183
+ assert_equal tmp_compuation1, compuation1
184
+ assert_equal tmp_compuation2, compuation2
185
+
186
+ a.change_to 15
187
+ #todo: clear b's deps's 2 sizes array
188
+ #in which one(the original one) is stopped,
189
+ #so we need to sometime clear in order not to
190
+ #let the deps grow larger and larger.
191
+ assert_equal 2, count_1
192
+ assert_equal 3, count_2
193
+ assert_equal tmp_compuation1, compuation1
194
+ assert_not_equal tmp_compuation2, compuation2
195
+
196
+ puts "before"
197
+ b.change_to 10
198
+ assert_equal 4, count_2
199
+ puts "after"
200
+
201
+ end
202
+
203
+ def test_thread
204
+ a = Autodeps::ReactiveData.new(3)
205
+ b = Autodeps::ReactiveData.new(5)
206
+ c = nil
207
+ computation = Autodeps.autorun do |computation|
208
+ c = a.value + b.value
209
+ end
210
+ assert_equal c,8
211
+
212
+ thread = Thread.new do
213
+ a.change_to 5
214
+ end
215
+ thread.join
216
+ assert_equal c,10
217
+
218
+ thread = Thread.new do
219
+ b.change_to 15
220
+ end
221
+ thread.join
222
+ assert_equal c,20
223
+ end
224
+
225
+ def test_active
226
+ a = Autodeps::ReactiveData.new(3)
227
+ b = Autodeps::ReactiveData.new(5)
228
+ c = nil
229
+ computation = Autodeps.autorun do |computation|
230
+ c = a.value + b.value
231
+ end
232
+ assert_equal c,8
233
+
234
+
235
+ thread = Thread.new do
236
+ assert_equal nil, Autodeps.active
237
+ end
238
+ thread.join
239
+
240
+ end
241
+
242
+
51
243
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: autodeps
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - femto
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-02-12 00:00:00.000000000 Z
11
+ date: 2014-02-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thread_safe
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  description: autodeps
42
56
  email:
43
57
  - femtowin@gmail.com