hyper-state 0.1
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 +7 -0
- data/.gitignore +53 -0
- data/.travis.yml +20 -0
- data/DOCS.md +325 -0
- data/Gemfile +5 -0
- data/README.md +70 -0
- data/Rakefile +11 -0
- data/hyper-state.gemspec +47 -0
- data/lib/hyper-state.rb +17 -0
- data/lib/hyperstack/internal/auto_unmount.rb +55 -0
- data/lib/hyperstack/internal/callbacks.rb +48 -0
- data/lib/hyperstack/internal/receiver.rb +36 -0
- data/lib/hyperstack/internal/state/mapper.rb +274 -0
- data/lib/hyperstack/state/observable.rb +76 -0
- data/lib/hyperstack/state/observer.rb +17 -0
- data/lib/hyperstack/state/version.rb +5 -0
- data/notes.md +104 -0
- metadata +383 -0
data/Rakefile
ADDED
data/hyper-state.gemspec
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'hyperstack/state/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'hyper-state'
|
8
|
+
spec.version = Hyperstack::State::VERSION
|
9
|
+
spec.authors = ['Mitch VanDuyn', 'Adam Creekroad', 'Jan Biedermann']
|
10
|
+
spec.email = ['mitch@catprint.com', 'jan@kursator.com']
|
11
|
+
spec.summary = 'Flux Stores and more for Hyperloop'
|
12
|
+
spec.homepage = 'https://ruby-hyperloop.org'
|
13
|
+
spec.license = 'MIT'
|
14
|
+
# spec.metadata = {
|
15
|
+
# "homepage_uri" => 'http://ruby-hyperloop.org',
|
16
|
+
# "source_code_uri" => 'https://github.com/ruby-hyperloop/hyper-component'
|
17
|
+
# }
|
18
|
+
|
19
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(gemfiles|spec)/}) }
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_dependency 'hyperstack-config', Hyperstack::State::VERSION
|
25
|
+
spec.add_dependency 'opal', '>= 0.11.0', '< 0.12.0'
|
26
|
+
spec.add_development_dependency 'bundler'
|
27
|
+
spec.add_development_dependency 'chromedriver-helper'
|
28
|
+
spec.add_development_dependency 'hyper-component', Hyperstack::State::VERSION
|
29
|
+
spec.add_development_dependency 'hyper-spec', Hyperstack::State::VERSION
|
30
|
+
spec.add_development_dependency 'listen'
|
31
|
+
spec.add_development_dependency 'mini_racer', '~> 0.1.15'
|
32
|
+
spec.add_development_dependency 'opal-browser', '~> 0.2.0'
|
33
|
+
spec.add_development_dependency 'opal-rails', '~> 0.9.4'
|
34
|
+
spec.add_development_dependency 'pry-byebug'
|
35
|
+
spec.add_development_dependency 'pry-rescue'
|
36
|
+
spec.add_development_dependency 'puma'
|
37
|
+
spec.add_development_dependency 'rails', '>= 4.0.0'
|
38
|
+
spec.add_development_dependency 'rake'
|
39
|
+
spec.add_development_dependency 'react-rails', '>= 2.4.0', '< 2.5.0'
|
40
|
+
spec.add_development_dependency 'rspec', '~> 3.7.0'
|
41
|
+
spec.add_development_dependency 'rspec-rails'
|
42
|
+
spec.add_development_dependency 'rspec-steps', '~> 2.1.1'
|
43
|
+
spec.add_development_dependency 'rubocop', '~> 0.51.0'
|
44
|
+
spec.add_development_dependency 'sqlite3'
|
45
|
+
spec.add_development_dependency 'timecop', '~> 0.8.1'
|
46
|
+
|
47
|
+
end
|
data/lib/hyper-state.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'set'
|
2
|
+
require 'hyperstack-config'
|
3
|
+
Hyperstack.import 'hyper-state'
|
4
|
+
require 'hyperstack/internal/callbacks'
|
5
|
+
require 'hyperstack/internal/auto_unmount'
|
6
|
+
|
7
|
+
require 'hyperstack/internal/state/mapper'
|
8
|
+
require 'hyperstack/internal/auto_unmount'
|
9
|
+
require 'hyperstack/internal/receiver'
|
10
|
+
require 'hyperstack/state/observable'
|
11
|
+
require 'hyperstack/state/observer'
|
12
|
+
require 'hyperstack/state/version'
|
13
|
+
|
14
|
+
if RUBY_ENGINE != 'opal'
|
15
|
+
require 'opal'
|
16
|
+
Opal.append_path(File.expand_path('../', __FILE__).untaint)
|
17
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Hyperstack
|
2
|
+
module Internal
|
3
|
+
module AutoUnmount
|
4
|
+
def self.included(base)
|
5
|
+
base.include(Hyperstack::Internal::Callbacks)
|
6
|
+
base.class_eval do
|
7
|
+
define_callback :before_unmount
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def unmounted?
|
12
|
+
@__hyperstack_internal_auto_unmount_unmounted
|
13
|
+
end
|
14
|
+
|
15
|
+
def unmount
|
16
|
+
run_callback(:before_unmount)
|
17
|
+
AutoUnmount.objects_to_unmount[self].each(&:unmount)
|
18
|
+
AutoUnmount.objects_to_unmount.delete(self)
|
19
|
+
instance_variables.each do |var|
|
20
|
+
val = instance_variable_get(var)
|
21
|
+
begin
|
22
|
+
val.unmount if val.respond_to?(:unmount)
|
23
|
+
rescue RUBY_ENGINE == 'opal' ? JS::Error : nil
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
@__hyperstack_internal_auto_unmount_unmounted = true
|
28
|
+
end
|
29
|
+
|
30
|
+
def every(*args, &block)
|
31
|
+
return if unmounted?
|
32
|
+
super.tap do |id|
|
33
|
+
sself = self
|
34
|
+
id.define_singleton_method(:unmount) { abort }
|
35
|
+
AutoUnmount.objects_to_unmount[self] << id
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def after(*args, &block)
|
40
|
+
return if unmounted?
|
41
|
+
super.tap do |id|
|
42
|
+
sself = self
|
43
|
+
id.define_singleton_method(:unmount) { abort }
|
44
|
+
AutoUnmount.objects_to_unmount[self] << id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class << self
|
49
|
+
def objects_to_unmount
|
50
|
+
@objects_to_unmount ||= Hash.new { |h, k| h[k] = Set.new }
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Hyperstack
|
2
|
+
module Internal
|
3
|
+
module Callbacks
|
4
|
+
if RUBY_ENGINE != 'opal'
|
5
|
+
class Hyperstack::Hotloader
|
6
|
+
def self.record(*args); end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
def self.included(base)
|
10
|
+
base.extend(ClassMethods)
|
11
|
+
end
|
12
|
+
|
13
|
+
def run_callback(name, *args)
|
14
|
+
self.class.callbacks_for(name).each do |callback|
|
15
|
+
if callback.is_a?(Proc)
|
16
|
+
instance_exec(*args, &callback)
|
17
|
+
else
|
18
|
+
send(callback, *args)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
def define_callback(callback_name, &after_define_hook)
|
25
|
+
wrapper_name = "_#{callback_name}_callbacks"
|
26
|
+
define_singleton_method(wrapper_name) do
|
27
|
+
Context.set_var(self, "@#{wrapper_name}", force: true) { [] }
|
28
|
+
end
|
29
|
+
define_singleton_method(callback_name) do |*args, &block|
|
30
|
+
send(wrapper_name).concat(args)
|
31
|
+
send(wrapper_name).push(block) if block_given?
|
32
|
+
Hotloader.record(self, "@#{wrapper_name}", 4, *args, block)
|
33
|
+
after_define_hook.call(*args, &block) if after_define_hook
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def callbacks_for(callback_name)
|
38
|
+
wrapper_name = "_#{callback_name}_callbacks"
|
39
|
+
if superclass.respond_to? :callbacks_for
|
40
|
+
superclass.callbacks_for(callback_name)
|
41
|
+
else
|
42
|
+
[]
|
43
|
+
end + send(wrapper_name)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Hyperstack
|
2
|
+
module Internal
|
3
|
+
module Receiver
|
4
|
+
class << self
|
5
|
+
def mount(receiver, *args, &block)
|
6
|
+
return if receiver.respond_to?(:unmounted?) && receiver.unmounted?
|
7
|
+
# Format the callback to be Proc or Nil
|
8
|
+
callback = format_callback(receiver, args)
|
9
|
+
|
10
|
+
# Loop through receivers and call callback and/or block on dispatch
|
11
|
+
args.each do |operation|
|
12
|
+
id = operation.on_dispatch do |params|
|
13
|
+
callback.call(params) if callback
|
14
|
+
yield params if block
|
15
|
+
end
|
16
|
+
# TODO: broadcaster classes need to define unmount as well
|
17
|
+
AutoUnmount.objects_to_unmount[receiver] << id if receiver.respond_to? :unmount
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def format_callback(receiver, args)
|
22
|
+
call_back =
|
23
|
+
if args.last.is_a?(Symbol)
|
24
|
+
method_name = args.pop
|
25
|
+
->(*aargs) { receiver.send(:"#{method_name}", *aargs) }
|
26
|
+
elsif args.last.is_a?(Proc)
|
27
|
+
args.pop
|
28
|
+
end
|
29
|
+
return call_back unless args.empty?
|
30
|
+
message = 'At least one operation must be passed in to the \'receives\' macro'
|
31
|
+
raise Legacy::Store::InvalidOperationError, message
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,274 @@
|
|
1
|
+
module Hyperstack
|
2
|
+
module Internal
|
3
|
+
module State
|
4
|
+
# State::Mapper bidirectionally maps observers to objects during a rendering cycle.
|
5
|
+
|
6
|
+
# Observers: Any object that responds to the `mutations` method can become
|
7
|
+
# an observer by calling Mapper.observing.
|
8
|
+
|
9
|
+
# State Objects: any object can be observed by calling the observed! method, and
|
10
|
+
# an object indicates that it has changed state by calling the mutated! method,
|
11
|
+
# which will notify all observers via their mutations methods.
|
12
|
+
|
13
|
+
# During each rendering cycle a list of objects observed during rendering
|
14
|
+
# is built called new_objects.
|
15
|
+
|
16
|
+
# At the end of the rendering cycle new_objects becomes current_objects, and
|
17
|
+
# its inverse called current_observers.
|
18
|
+
|
19
|
+
# When mutated! is called, the current_observers list is used to find the list of
|
20
|
+
# observers.
|
21
|
+
|
22
|
+
# Typically mutated! is called during some javascript event, and we will want to
|
23
|
+
# delay notification until the event handler has completed execution.
|
24
|
+
module Mapper
|
25
|
+
@rendering_level = 0
|
26
|
+
|
27
|
+
class << self
|
28
|
+
# Entry Points:
|
29
|
+
# observing setup an observer
|
30
|
+
# observed! indicate an object has been observed
|
31
|
+
# mutated! indicate an object has been mutated
|
32
|
+
# observed? has this object been observed?
|
33
|
+
# bulk_update prevent notifications until the event completes
|
34
|
+
# update_objects_to_observe called at end of each rendering cycle
|
35
|
+
# remove called when a component unmounts
|
36
|
+
|
37
|
+
# Observers wrap code in observe. Any calls
|
38
|
+
# made to the public entry points will then know which
|
39
|
+
# observer is executing, along with whether this is the
|
40
|
+
# outer most component, and whether to delay or handle state
|
41
|
+
# changes immediately.
|
42
|
+
|
43
|
+
# Once the observer's block completes execution, the
|
44
|
+
# context instance variables are restored.
|
45
|
+
def observing(observer, immediate_update, rendering, update_objects)
|
46
|
+
saved_context = [@current_observer, @immediate_update]
|
47
|
+
@current_observer = observer
|
48
|
+
@immediate_update = immediate_update && observer
|
49
|
+
if rendering
|
50
|
+
@rendering_level += 1
|
51
|
+
observed!(observer)
|
52
|
+
observed!(observer.class)
|
53
|
+
end
|
54
|
+
return_value = yield
|
55
|
+
update_objects_to_observe(observer) if update_objects
|
56
|
+
return_value
|
57
|
+
ensure
|
58
|
+
@current_observer, @immediate_update = saved_context
|
59
|
+
@rendering_level -= 1 if rendering
|
60
|
+
return_value
|
61
|
+
end
|
62
|
+
|
63
|
+
# called when an object has been observed (i.e. read) by somebody
|
64
|
+
def observed!(object)
|
65
|
+
return unless @current_observer
|
66
|
+
new_objects[@current_observer] << object
|
67
|
+
return unless update_exclusions[object]
|
68
|
+
update_exclusions[object] << @current_observer
|
69
|
+
end
|
70
|
+
|
71
|
+
# Called when an object has been mutated.
|
72
|
+
# Depending on the state of StateContext we will either
|
73
|
+
# schedule the update notification for later, immediately
|
74
|
+
# notify any observers, or do nothing.
|
75
|
+
def mutated!(object)
|
76
|
+
return if @ignore_mutations
|
77
|
+
if delay_updates?(object)
|
78
|
+
schedule_delayed_updater(object)
|
79
|
+
elsif @rendering_level.zero?
|
80
|
+
current_observers[object].each do |observer|
|
81
|
+
observer.mutations([object])
|
82
|
+
end if current_observers.key? object
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Check to see if an object has been observed.
|
87
|
+
def observed?(object)
|
88
|
+
# we don't want to unnecessarily create a reference to ourselves
|
89
|
+
# in the current_observers hash so we just look for the key.
|
90
|
+
current_observers.key?(object)# && current_observers[object].any?
|
91
|
+
end
|
92
|
+
|
93
|
+
# Code can be wrapped in the bulk_update method, and
|
94
|
+
# notifications of any mutations that occur during
|
95
|
+
# the yield will be scheduled for after the current
|
96
|
+
# event finishes.
|
97
|
+
def bulk_update
|
98
|
+
saved_bulk_update_flag = @bulk_update_flag
|
99
|
+
@bulk_update_flag = true
|
100
|
+
yield
|
101
|
+
ensure
|
102
|
+
@bulk_update_flag = saved_bulk_update_flag
|
103
|
+
end
|
104
|
+
|
105
|
+
def ignore_mutations
|
106
|
+
saved_ignore_mutations_flag = @ignore_mutations
|
107
|
+
@ignore_mutations = true
|
108
|
+
yield
|
109
|
+
ensure
|
110
|
+
@ignore_mutations = saved_ignore_mutations_flag
|
111
|
+
end
|
112
|
+
|
113
|
+
# Call after each component updates. (in the after_update/after_mount callbacks)
|
114
|
+
# During the rendering cycle the observers and objects are held in the
|
115
|
+
# current_observers and current_objects hashes, which are just inverses of each
|
116
|
+
# so that current_observers can be accessed via objects, and current_objects can be
|
117
|
+
# accessed by observers.
|
118
|
+
|
119
|
+
# While rendering is going on, observers may add new objects to the new_objects list
|
120
|
+
|
121
|
+
# When rendering completes we clear the current observers and objects lists, and
|
122
|
+
# the new_objects list gets transferred in to current_objects and current_observers
|
123
|
+
# and the process repeats.
|
124
|
+
|
125
|
+
# When an event triggers a state change the current_objects list is used to determine
|
126
|
+
# what observers (components) need to be updated. Note that the new_objects during this
|
127
|
+
# phase is still empty, and that is why we need two lists.
|
128
|
+
|
129
|
+
# TODO: see if we can get rid of all this and simply calling
|
130
|
+
# remove_current_observers_and_objects at the START of each components rendering
|
131
|
+
# cycle (i.e. before_mount and before_update)
|
132
|
+
def update_objects_to_observe(observer = @current_observer)
|
133
|
+
remove_current_observers_and_objects(observer)
|
134
|
+
objects = new_objects.delete(observer)
|
135
|
+
objects.each { |object| current_observers[object] << observer } if objects
|
136
|
+
current_objects[observer] = objects
|
137
|
+
end
|
138
|
+
|
139
|
+
# call remove before unmounting components to prevent stray events
|
140
|
+
# from being sent to unmounted components.
|
141
|
+
def remove(observer = @current_observer)
|
142
|
+
remove_current_observers_and_objects(observer)
|
143
|
+
new_objects.delete observer
|
144
|
+
# see run_delayed_updater for the purpose of @removed_observers
|
145
|
+
@removed_observers << observer if @removed_observers
|
146
|
+
end
|
147
|
+
|
148
|
+
# Internal (Private) Methods
|
149
|
+
|
150
|
+
# These four hashes track the current relationship between
|
151
|
+
# observers and observable objects
|
152
|
+
|
153
|
+
# new_objects are added as the @current_observer reads
|
154
|
+
# an objects state
|
155
|
+
def new_objects
|
156
|
+
@new_objects ||= Hash.new { |h, k| h[k] = Set.new }
|
157
|
+
end
|
158
|
+
|
159
|
+
# at the end of the rendering cycle the new_objects are
|
160
|
+
# processed into a list of observers indexed by objects...
|
161
|
+
def current_observers
|
162
|
+
@current_observers ||= Hash.new { |h, k| h[k] = [] }
|
163
|
+
end
|
164
|
+
|
165
|
+
# and a list of objects indexed by observers
|
166
|
+
def current_objects
|
167
|
+
@current_objects ||= Hash.new { |h, k| h[k] = [] }
|
168
|
+
end
|
169
|
+
|
170
|
+
# Normally notification of changes to state are queued up
|
171
|
+
# and will be run after the event has completed processing.
|
172
|
+
# Then each observer is notified of the states that changed
|
173
|
+
# during the event. The observers may then begin reading
|
174
|
+
# state before the notification has completed. To prevent
|
175
|
+
# redundant notifications in this case, a list of observers
|
176
|
+
# indexed by objects is kept in the update_exclusions hash.
|
177
|
+
|
178
|
+
# We avoid keeping empty lists of observers on the exclusion
|
179
|
+
# lists by not adding an object hash key unless the object
|
180
|
+
# already has pending state changes. (See the
|
181
|
+
# schedule_delayed_updater method below)
|
182
|
+
|
183
|
+
def update_exclusions
|
184
|
+
@update_exclusions ||= Hash.new
|
185
|
+
end
|
186
|
+
|
187
|
+
# remove_current_observers_and_objects clears the hashes between renders
|
188
|
+
|
189
|
+
def remove_current_observers_and_objects(observer)
|
190
|
+
raise 'state management called outside of watch block' unless observer
|
191
|
+
deleted_objects = current_objects.delete(observer)
|
192
|
+
return unless deleted_objects
|
193
|
+
deleted_objects.each do |object|
|
194
|
+
# to allow for GC we never want objects hanging around as keys in
|
195
|
+
# the current_observers hash, so we tread carefully here.
|
196
|
+
next unless current_observers.key? object
|
197
|
+
current_observers[object].delete(observer)
|
198
|
+
current_observers.delete object if current_observers[object].empty?
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# determine if updates should be delayed.
|
203
|
+
# always delay updates if the bulk_update_flag is set
|
204
|
+
# otherwise delayed updates only occurs if
|
205
|
+
# Hyperstack.on_client? is true WITH ONE EXCEPTION:
|
206
|
+
# observers can indicate that they need immediate updates in
|
207
|
+
# case that the object being updated is themselves.
|
208
|
+
|
209
|
+
def delay_updates?(object)
|
210
|
+
@bulk_update_flag ||
|
211
|
+
(Hyperstack.on_client? &&
|
212
|
+
(@immediate_update != @current_observer || @current_observer != object))
|
213
|
+
end
|
214
|
+
|
215
|
+
# schedule_delayed_updater adds a new set to the
|
216
|
+
# update_exclusions hash (indexed by object) then makes
|
217
|
+
# sure that the updater is scheduled to run as soon as the current
|
218
|
+
# event completes.
|
219
|
+
|
220
|
+
# the update_exclusions hash tells us two things. First any object that
|
221
|
+
# is a key in the hash has been changed, but the notification of the change
|
222
|
+
# has been delayed. Secondly the associated Set will contain a list of observers
|
223
|
+
# that have already read the current state, between the time
|
224
|
+
# schedule_delayed_updater has been called, and the updater runs. These
|
225
|
+
# observers don't need notification since they already know the current state.
|
226
|
+
|
227
|
+
# If an object changes state again then the Set will be reinitialized, and all
|
228
|
+
# the observers that might have been on a previous exclusion list, will now be
|
229
|
+
# notified.
|
230
|
+
|
231
|
+
def schedule_delayed_updater(object)
|
232
|
+
update_exclusions[object] = Set.new
|
233
|
+
@delayed_updater ||= after(0) { run_delayed_updater }
|
234
|
+
end
|
235
|
+
|
236
|
+
# run_delayed_updater will call the mutations method for each observer passing
|
237
|
+
# the entire list of objects that changed while waiting for the delay except
|
238
|
+
# those that the observer has already seen (the exclusion list). The observers
|
239
|
+
# mutation method may cause some other observer already on the observers_to_update
|
240
|
+
# list to be removed. To prevent these observers from receiving mutations we keep a
|
241
|
+
# temporary set of removed_observers. This is initialized before the mutations,
|
242
|
+
# and then cleared as soon as we are done.
|
243
|
+
|
244
|
+
def run_delayed_updater
|
245
|
+
current_update_exclusions = @update_exclusions
|
246
|
+
@update_exclusions = @delayed_updater = nil
|
247
|
+
@removed_observers = Set.new
|
248
|
+
observers_to_update(current_update_exclusions).each do |observer, objects|
|
249
|
+
observer.mutations objects unless @removed_observers.include? observer
|
250
|
+
end
|
251
|
+
ensure
|
252
|
+
@removed_observers = nil
|
253
|
+
end
|
254
|
+
|
255
|
+
# observers_to_update returns a hash with observers as keys, and lists of objects
|
256
|
+
# as values. The hash is built by filtering the current_observers list
|
257
|
+
# including only observers that have mutated objects, that are not on the exclusion
|
258
|
+
# list.
|
259
|
+
|
260
|
+
def observers_to_update(exclusions)
|
261
|
+
Hash.new { |hash, key| hash[key] = Array.new }.tap do |updates|
|
262
|
+
exclusions.each do |object, excluded_observers|
|
263
|
+
current_observers[object].each do |observer|
|
264
|
+
next if excluded_observers.include?(observer)
|
265
|
+
updates[observer] << object
|
266
|
+
end if current_observers.key? object
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|