autodeps 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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