diff options
-rw-r--r-- | examples/hello_world.json | 9 | ||||
-rw-r--r-- | examples/hello_world.rb | 2 | ||||
-rw-r--r-- | lib/edoors.rb | 1 | ||||
-rw-r--r-- | lib/edoors/board.rb | 19 | ||||
-rw-r--r-- | lib/edoors/door.rb | 67 | ||||
-rw-r--r-- | lib/edoors/iota.rb | 24 | ||||
-rw-r--r-- | lib/edoors/link.rb | 64 | ||||
-rw-r--r-- | lib/edoors/particle.rb | 245 | ||||
-rw-r--r-- | lib/edoors/room.rb | 104 | ||||
-rw-r--r-- | lib/edoors/spin.rb | 93 | ||||
-rw-r--r-- | spec/board_spec.rb | 14 | ||||
-rw-r--r-- | spec/door_spec.rb | 6 | ||||
-rw-r--r-- | spec/link_spec.rb | 23 | ||||
-rw-r--r-- | spec/particle_spec.rb | 55 | ||||
-rw-r--r-- | spec/room_spec.rb | 19 | ||||
-rw-r--r-- | spec/spin_spec.rb | 8 | ||||
-rw-r--r-- | test/test_iotas.rb | 11 |
17 files changed, 591 insertions, 173 deletions
diff --git a/examples/hello_world.json b/examples/hello_world.json index 295b10d..6635bca 100644 --- a/examples/hello_world.json +++ b/examples/hello_world.json @@ -1,6 +1,6 @@ { "kls": "Edoors::Spin", - "timestamp": "2012-06-10 23:17:15 +0200", + "timestamp": "2012-06-24 22:50:00 +0200", "name": "dom0", "hibernation": false, "inner_room": { @@ -20,9 +20,8 @@ "kls": "Edoors::Link", "src": "input", "dsts": "output", - "fields": null, - "cond_fields": null, - "cond_value": null + "keys": null, + "value": null } ] } @@ -33,6 +32,6 @@ "app_fifo": [ ], - "debug_errors": false, + "debug_garbage": false, "debug_routing": false } diff --git a/examples/hello_world.rb b/examples/hello_world.rb index 3976d6f..e4ee3c2 100644 --- a/examples/hello_world.rb +++ b/examples/hello_world.rb @@ -62,7 +62,7 @@ if $0 == __FILE__ # This will output the receive data and let the system recycle the particle output = OutputDoor.new 'output', dom0 # This will be the unconditinal link leading from 'input' to 'output' - dom0.add_link Edoors::Link.new('input', 'output', nil, nil, nil) + dom0.add_link Edoors::Link.new('input', 'output', nil, nil) # # schedule the spinning particles untill the system cools down dom0.spin! diff --git a/lib/edoors.rb b/lib/edoors.rb index d70d5bf..8ef8823 100644 --- a/lib/edoors.rb +++ b/lib/edoors.rb @@ -23,7 +23,6 @@ require 'version' module Edoors # PATH_SEP = '/'.freeze - LINK_SEP = ','.freeze ACT_SEP = '?'.freeze # ACT_GET = 'get'.freeze diff --git a/lib/edoors/board.rb b/lib/edoors/board.rb index f845dcd..990d312 100644 --- a/lib/edoors/board.rb +++ b/lib/edoors/board.rb @@ -25,11 +25,20 @@ module Edoors # class Board < Door # + # creates a Board object from the arguments. + # + # @param [String] n the name of this Board + # @param [Iota] p the parent + # def initialize n, p super n, p @postponed = {} end # + # called by JSON#generate to serialize the Board object into JSON data + # + # @param [Array] a belongs to JSON generator + # def to_json *a { 'kls' => self.class.name, @@ -38,6 +47,12 @@ module Edoors }.merge(hibernate!).to_json *a end # + # creates a Board object from a JSON data + # + # @param [Hash] o belongs to JSON parser + # + # @raise Edoors::Exception if the json kls attribute is wrong + # def self.json_create o raise Edoors::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name board = self.new o['name'], o['parent'] @@ -48,6 +63,10 @@ module Edoors board end # + # process the given particle then forward it to user code + # + # @param [Particle] p the Particle to be processed + # def process_p p @viewer.receive_p p if @viewer if p.action!=Edoors::ACT_ERROR diff --git a/lib/edoors/door.rb b/lib/edoors/door.rb index 848c1b5..beb42ab 100644 --- a/lib/edoors/door.rb +++ b/lib/edoors/door.rb @@ -23,11 +23,20 @@ module Edoors # class Door < Iota # + # creates a Door object from the arguments. + # + # @param [String] n the name of this Door + # @param [Iota] p the parent + # def initialize n, p super n, p @saved = nil end # + # called by JSON#generate to serialize the Door object into JSON data + # + # @param [Array] a belongs to JSON generator + # def to_json *a { 'kls' => self.class.name, @@ -35,6 +44,12 @@ module Edoors }.merge(hibernate!).to_json *a end # + # creates a Door object from a JSON data + # + # @param [Hash] o belongs to JSON parser + # + # @raise Edoors::Exception if the json kls attribute is wrong + # def self.json_create o raise Edoors::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name door = self.new o['name'], o['parent'] @@ -42,23 +57,37 @@ module Edoors door end # - def require_p p_kls + # require a Particle of the given class + # + # @param [Class] p_kls the class of the desired Particle + # + def require_p p_kls=Edoors::Particle @spin.require_p p_kls end # + # release the given Particle + # + # @param [Particle] p the Particle to be released + # def release_p p @saved=nil if @saved==p # particle is released, all is good @spin.release_p p end # + # release the Particle that have not been released or sent by user code + # def _garbage - puts " ! #{path} didn't give back #{@saved}" if @spin.debug_errors + puts " ! #{path} didn't give back #{@saved}" if @spin.debug_garbage puts "\t#{@saved.data Edoors::FIELD_ERROR_MSG}" if @saved.action==Edoors::ACT_ERROR @spin.release_p @saved @saved = nil end private :_garbage # + # process the given particle then forward it to user code + # + # @param [Particle] p the Particle to be processed + # def process_p p @viewer.receive_p p if @viewer @saved = p @@ -66,12 +95,24 @@ module Edoors _garbage if not @saved.nil? end # + # dead end, for now user defined Door do not have to deal with system Particle + # the Particle is released + # + # @param [Particle] p the Particle to deal with + # def process_sys_p p # nothing todo with it now @spin.release_p p end # - def _send sys, p, a=nil, d=nil + # send the given Particle through the direct @parent + # + # @param [Particle] p the Particle to be sent + # @param [Boolean] sys if true send to system Particle fifo + # @param [String] a the post action + # @param [Iota] d the post destination + # + def _send p, sys, a, d p.init! self p.set_dst! a, d||self if a @saved=nil if @saved==p # particle is sent back the data, all is good @@ -80,12 +121,28 @@ module Edoors end private :_send # + # send the given Particle to the application Particle fifo + # + # @param [Particle] p the Particle to be sent + # @param [String] a the post action + # @param [Iota] d the post destination + # + # @see Door#_send real implementation + # def send_p p, a=nil, d=nil - _send false, p, a, d + _send p, false, a, d end # + # send the given Particle to the system Particle fifo + # + # @param [Particle] p the Particle to be sent + # @param [String] a the post action + # @param [Iota] d the post destination + # + # @see Door#_send real implementation + # def send_sys_p p, a=nil, d=nil - _send true, p, a, d + _send p, true, a, d end # end diff --git a/lib/edoors/iota.rb b/lib/edoors/iota.rb index fcf7573..e63aca0 100644 --- a/lib/edoors/iota.rb +++ b/lib/edoors/iota.rb @@ -25,6 +25,14 @@ module Edoors # class Iota # + # creates a Iota object from the arguments. + # + # @param [String] n the name of this Iota + # @param [Iota] p the parent + # + # @see Room#add_iota adds itself to it's parent children list + # @see Spin#add_to_world adds itself to @spin's world + # def initialize n, p raise Edoors::Exception.new "Iota name #{n} is not valid" if n.include? Edoors::PATH_SEP @name = n # unique in it's room @@ -41,23 +49,31 @@ module Edoors attr_reader :name, :path, :spin attr_accessor :viewer, :parent # + # override this to initialize your object on system start + # def start! - # override this to initialize your object on system start end # + # override this to initialize your object on system stop + # def stop! - # override this to initialize your object on system stop end # + # override this to save your object state on hibernate + # # def hibernate! - # override this to save your object state on hibernate {} end # + # override this to restore your object state on resume + # def resume! o - # override this to restore your object state on resume end # + # has to be override, used by user side code + # + # @raise NoMethodError + # def receive_p p raise NoMethodError.new "receive_p(p) must be overridden" end diff --git a/lib/edoors/link.rb b/lib/edoors/link.rb index b6a3fb9..ede2cd4 100644 --- a/lib/edoors/link.rb +++ b/lib/edoors/link.rb @@ -23,45 +23,65 @@ module Edoors # LNK_SRC = 'edoors_lnk_src'.freeze LNK_DSTS = 'edoors_lnk_dsts'.freeze - LNK_FIELDS = 'edoors_lnk_fields'.freeze - LNK_CONDF = 'edoors_lnk_condf'.freeze - LNK_CONDV = 'edoors_lnk_condv'.freeze + LNK_KEYS = 'edoors_lnk_keys'.freeze + LNK_VALUE = 'edoors_lnk_value'.freeze # class Link # - def initialize src, dsts, fields=nil, cond_fields=nil, cond_value=nil - @src = src # link source name - @dsts = dsts # , separated destinations to apply to the particle on linking success - @fields = fields # , separated fields to apply to the particle on linking success - @cond_fields = cond_fields # , separated fields used to generate the link value with particle payload - @cond_value = cond_value # value which will be compared to the particle link value to link or not - @door = nil # pointer to the source + # creates a Link object from the arguments. + # + # @param [String] src link source name + # @param [Array] dsts destinations to apply to the particle on linking success + # @param [Array] keys keys to apply as link_keys to the particle on linking success + # @param [Hash] value will be used to check linking with particles + # + # @see Room#_try_links try to apply links on a Particle + # @see Particle#link_with? linking test + # + def initialize src, dsts, keys=nil, value=nil + @src = src + @dsts = dsts + @keys = keys + @value = value + @door = nil # pointer to the source set from @src by Room#add_link end # + # called by JSON#generate to serialize the Link object into JSON data + # + # @param [Array] a belongs to JSON generator + # def to_json *a { - 'kls' => self.class.name, - 'src' => @src, - 'dsts' => @dsts, - 'fields' => @fields, - 'cond_fields' => @cond_fields, - 'cond_value' => @cond_value + 'kls' => self.class.name, + 'src' => @src, + 'dsts' => @dsts, + 'keys' => @keys, + 'value' => @value }.to_json *a end # + # creates a Link object from a JSON data + # + # @param [Hash] o belongs to JSON parser + # + # @raise Edoors::Exception if the json kls attribute is wrong + # def self.json_create o raise Edoors::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name - self.new o['src'], o['dsts'], o['fields'], o['cond_fields'], o['cond_value'] + self.new o['src'], o['dsts'], o['keys'], o['value'] end # - def self.from_particle_data p - Edoors::Link.new(p.get_data(Edoors::LNK_SRC), p.get_data(Edoors::LNK_DSTS), - p.get_data(Edoors::LNK_FIELDS), p.get_data(Edoors::LNK_CONDF), - p.get_data(Edoors::LNK_CONDV)) + # creates a Link object from the data of a particle + # + # @param [Particle] p the Particle to get Link attributes from + # + def self.from_particle p + pl = p.payload + Edoors::Link.new pl[Edoors::LNK_SRC], pl[Edoors::LNK_DSTS], pl[Edoors::LNK_KEYS], pl[Edoors::LNK_VALUE] end # attr_accessor :door - attr_reader :src, :dsts, :fields, :cond_fields, :cond_value + attr_reader :src, :dsts, :keys, :value # end # diff --git a/lib/edoors/particle.rb b/lib/edoors/particle.rb index f659fbc..763c578 100644 --- a/lib/edoors/particle.rb +++ b/lib/edoors/particle.rb @@ -24,6 +24,33 @@ module Edoors # class Particle # + # creates a Particle object from the arguments. + # + # @param [Hash] o a customizable set of options + # + # @option o 'ts' [String] + # creation time + # @option o 'src' [String] + # Iota where it's originated from + # @option o 'dst' [String] + # Iota where it's heading to + # @option o 'room' [String] + # Room path part of the current destination + # @option o 'door' [String] + # Door path part of the current destination + # @option o 'action' [String] + # action part of the current destination + # @option o 'dsts' [String] + # fifo of path?action strings where to travel to + # @option o 'link_keys' [String] + # unordered keys used has payload keys to build link_value + # @option o 'payload' [String] + # the data carried by this particle + # @option o 'merged' [String] + # list of merged particles + # + # @see Spin#require_p require a Particle + # def initialize o={} @ts = Time.now # creation time @src = nil # Iota where it's originated from @@ -31,11 +58,10 @@ module Edoors @room = nil # Room path part of the current destination @door = nil # Door path part of the current destination @action = nil # action part of the current destination - @link_value = nil # the value computed with the link_fields values extracted from the payload - # used for pearing Particles in Boards and linking in routing process @dsts = [] # fifo of path?action strings where to travel to - @link_fields = [] # the fields used to generate the link value - @payload = {} # the actual data carried by this particle + @link_keys = [] # unordered keys used has payload keys to build link_value + @link_value = {} # the payload keys and values corresponding to the link keys + @payload = {} # the data carried by this particle @merged = [] # list of merged particles # if not o.empty? @@ -46,77 +72,117 @@ module Edoors @payload = o['payload']||{} @src = o['spin'].search_down o['src'] if o['src'] @dst = o['spin'].search_down o['dst'] if o['dst'] - o['dsts'].each do |dst| add_dsts dst end if o['dsts'] - set_link_fields *o['link_fields'] if o['link_fields'] + add_dsts *o['dsts'] if o['dsts'] + set_link_keys *o['link_keys'] if o['link_keys'] o['merged'].each do |particle| merge! Particle.json_create(particle.merge!('spin'=>o['spin'])) end if o['merged'] end end # + # called by JSON#generate to serialize the Particle object into JSON data + # + # @param [Array] a belongs to JSON generator + # def to_json *a { - 'kls' => self.class.name, - 'ts' => @ts, - 'src' => (@src ? @src.path : nil ), - 'dst' => (@dst ? @dst.path : nil ), - 'room' => @room, - 'door' => @door, - 'action' => @action, - 'dsts' => @dsts, - 'link_fields' => @link_fields, - 'payload' => @payload, - 'merged' => @merged + 'kls' => self.class.name, + 'ts' => @ts, + 'src' => (@src ? @src.path : nil ), + 'dst' => (@dst ? @dst.path : nil ), + 'room' => @room, + 'door' => @door, + 'action' => @action, + 'dsts' => @dsts, + 'link_keys' => @link_keys, + 'payload' => @payload, + 'merged' => @merged }.to_json *a end # + # creates a Particle object from a JSON data + # + # @param [Hash] o belongs to JSON parser + # + # @raise Edoors::Exception if the json kls attribute is wrong + # def self.json_create o raise Edoors::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name self.new o end # - # called when released + # clears all attributes + # + # @see Spin#release_p called whe na Particle is released + # def reset! clear_merged! ( @src ? @src : ( @dst ? @dst : nil ) ) - @ts = @src = @dst = @room = @door = @action = @link_value = nil + @ts = @src = @dst = @room = @door = @action = nil @dsts.clear - @link_fields.clear + @link_value.clear + @link_keys.clear @payload.clear end # - # called when sent + # sets @src, @ts, and reset others + # + # @see Particle#apply_link! called when a Link is applied + # @see Door#_send called when a Door sends a Particle + # def init! src @src = src @ts = Time.now @dst = @room = @door = @action = nil end # - attr_reader :ts, :src, :dst, :room, :door, :action, :link_value, :payload + attr_reader :ts, :src, :dst, :room, :door, :action, :payload, :link_value # - # routing + # returns the next destination # def next_dst @dsts[0] end # + # clears the destination list + # def clear_dsts! @dsts.clear end # - def add_dsts dsts - dsts.split(Edoors::LINK_SEP).each do |dst| - if dst.empty? or dst[0]==Edoors::PATH_SEP or dst[0]==Edoors::PATH_SEP or dst=~/\/\?/\ - or dst=~/\/{2,}/ or dst=~/\s+/ or dst==Edoors::ACT_SEP + # adds destinations to the destination list + # + # @param [Array] dsts destinations to add + # + # @raise Edoors::Exception if a destination is not acceptable + # + # The parameters are checked before beeing added. + # they must not be empty or be '?' or start with '/' + # or contain '/?' or '//' or '\s+' + # + def add_dsts *dsts + dsts.each do |dst| + if dst.empty? or dst==Edoors::ACT_SEP or dst[0]==Edoors::PATH_SEP \ + or dst=~/\/\?/ or dst=~/\/{2,}/ or dst=~/\s+/ raise Edoors::Exception.new "destination #{dst} is not acceptable" end @dsts << dst end end # + # adds a destination to the destination list + # + # @param [String] a the action + # @param [String] d the destination + # def add_dst a, d='' add_dsts d+Edoors::ACT_SEP+a end # + # sets the current destination + # + # @param [String] a the action + # @param [String Iota] d the destination + # def set_dst! a, d @action = a if d.is_a? Edoors::Iota @@ -127,6 +193,11 @@ module Edoors end end # + # splits the next destination into @room, @door, @action attributes + # + # the @dst attribute is set to nil + # the @room, @door, @action attributes are set to nil if not defined + # def split_dst! @dst = @room = @door = @action = nil return if (n = next_dst).nil? @@ -134,6 +205,10 @@ module Edoors _split_path! p end # + # called by Particle#split_dst! to split the path part of the destination + # + # @param [String] p path to be splitted + # def _split_path! p i = p.rindex Edoors::PATH_SEP if i.nil? @@ -147,81 +222,147 @@ module Edoors end private :_split_path! # + # sets the current destination and shift the head of destination list + # + # @param [Iota] dst the current destination + # + # @see Room#_route routing success + # @see Room#_send routing failure + # def dst_routed! dst @dst = dst @dsts.shift end # + # sets the error message, the destination and action + # + # @param [String] e error message + # @param [Iota] dst the destination, @src if nil + # + # the error message is set into @payload[[Edoors::FIELD_ERROR_MSG] + # def error! e, dst=nil @action = Edoors::ACT_ERROR @dst = dst||@src @payload[Edoors::FIELD_ERROR_MSG]=e end # + # applies the effects of the given Link + # + # @param [Link] lnk the link to apply effects + # + # updates @src with Link @src, clears the destination list + # adds the Link destinations to @dsts, sets the @link_keys + # def apply_link! lnk init! lnk.door clear_dsts! - add_dsts lnk.dsts - set_link_fields lnk.fields + add_dsts *lnk.dsts + set_link_keys *lnk.keys end # - # data manipulation + # adds/updates a key value pair into payload # - def []= k, v - @payload[k]=v - compute_link_value! if @link_fields.include? k - end + # @param [String] k the key + # @param [Object] v the value # - def set_data k, v + # \@link_value attribute will be updated if impacted + # + def []= k, v + @link_value[k] = v if @link_keys.include? k @payload[k] = v - compute_link_value! if @link_fields.include? k end + alias :set_data :[]= # - def [] k - @payload[k] + # destroys the value paired with a key + # + # @param [String] k the key + # + # @return the associated value + # + # \@link_value attribute will be updated if impacted + # + def del_data k + @link_value.delete k if @link_keys.include? k + @payload.delete k end # - def get_data k + # retrieves a data value from a key + # + # @param [String] k the key + # + def [] k @payload[k] end - alias :data :get_data + # + alias :get_data :[] + alias :data :[] + # + # clones the payload of the given Particle + # + # @param [Particle] p the Particle to clone the payload of # def clone_data p @payload = p.payload.clone end # - # link value and fields + # sets the links keys + # + # @param [Array] args list of keys to set # - def set_link_fields *args - @link_fields.clear if not @link_fields.empty? + # \@link_value attribute will be updated + # + def set_link_keys *args + @link_keys.clear if not @link_keys.empty? args.compact! - args.each do |lfs| - lfs.split(',').each do |lf| - @link_fields << lf - end + args.each do |lf| + @link_keys << lf end - compute_link_value! + @link_value = @payload.select { |k,v| @link_keys.include? k } end # - def compute_link_value! - @link_value = @link_fields.inject('') { |s,lf| s+=@payload[lf].to_s if @payload[lf]; s } + # tries to link the Particle with the given Link + # + # @param [Link] link the link to try to link with + # + # returns true if the value of the Link is nil + # otherwise checks if the extracted key values pairs from the Particle + # payload using the Link value keys as selectors, equals the Link value + # + # @return [Boolean] true if the Link links with the Particle + # + def link_with? link + return true if link.value.nil? + link.value.keys.inject({}) { |h,k| h[k]=@payload[k] if @payload.has_key?(k); h }.eql? link.value end # - # merge particles management + # merges the given Particle in + # + # @param [Particle] p the Particle to merge in # def merge! p @merged << p end # + # returns a merged Particle + # + # @param [Integer] i the index into the merged Particle list + # def merged i @merged[i] end # + # shifts the merged Particle list + # def merged_shift @merged.shift end # - def clear_merged! r=nil + # recursively clears the merged Particle list + # + # @param [Boolean] r releases the cleared Particle if true + # + def clear_merged! r=false @merged.each do |p| p.clear_merged! r r.release_p p if r diff --git a/lib/edoors/room.rb b/lib/edoors/room.rb index 5936e27..decdb83 100644 --- a/lib/edoors/room.rb +++ b/lib/edoors/room.rb @@ -29,12 +29,21 @@ module Edoors # class Room < Iota # + # creates a Room object from the arguments. + # + # @param [String] n the name of this Room + # @param [Iota] p the parent + # def initialize n, p super n, p @iotas = {} @links = {} end # + # called by JSON#generate to serialize the Room object into JSON data + # + # @param [Array] a belongs to JSON generator + # def to_json *a { 'kls' => self.class.name, @@ -44,6 +53,12 @@ module Edoors }.to_json *a end # + # creates a Room object from a JSON data + # + # @param [Hash] o belongs to JSON parser + # + # @raise Edoors::Exception if the json kls attribute is wrong + # def self.json_create o raise Edoors::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name room = self.new o['name'], o['parent'] @@ -58,33 +73,65 @@ module Edoors room end # - def self.from_particle_data p, s + # creates a Room object from the data of a particle + # + # @param [Particle] p the Particle to get Room attributes from + # + def self.from_particle p, s Edoors::Room.new(p.get_data(Edoors::IOTA_NAME), s) end # - def add_iota s - raise Edoors::Exception.new "Iota #{s.name} already has #{s.parent.name} as parent" if not s.parent.nil? and s.parent!=self - raise Edoors::Exception.new "Iota #{s.name} already exists in #{path}" if @iotas.has_key? s.name - s.parent = self if s.parent.nil? - @iotas[s.name]=s + # adds the given Iota to this Room + # + # @param [Iota] i the Iota to add + # + # @raise Edoors::Exception if i already has a parent or if a Iota with the same name already exists + # + def add_iota i + raise Edoors::Exception.new "Iota #{i.name} already has #{i.parent.name} as parent" if not i.parent.nil? and i.parent!=self + raise Edoors::Exception.new "Iota #{i.name} already exists in #{path}" if @iotas.has_key? i.name + i.parent = self if i.parent.nil? + @iotas[i.name]=i end # + # adds the given Link to this Room + # + # @param [Link] l the Link to add + # + # @raise Edoors::Exception if the link source can't be found in this Room + # def add_link l l.door = @iotas[l.src] raise Edoors::Exception.new "Link source #{l.src} does not exist in #{path}" if l.door.nil? (@links[l.src] ||= [])<< l end # + # forward the call to each children + # + # @see Spin#spin! called on system bootup + # def start! puts " * start #{path}" if @spin.debug_routing @iotas.values.each do |iota| iota.start! end end # + # forward the call to each children + # + # @see Spin#spin! called on system shutdown + # def stop! puts " * stop #{path}" if @spin.debug_routing @iotas.values.each do |iota| iota.stop! end end # + # search through all children for a matching Iota + # + # @param [String] spath the full path of the earch Iota + # + # @return [Iota] if found + # + # @see Particle#initialize used to transform @src and @dst JSON data into refrences + # def search_down spath return self if spath==path return nil if (spath=~/^#{path}\/(\w+)\/?/)!=0 @@ -95,17 +142,17 @@ module Edoors nil end # + # search for a matching link + # + # @param [Particle] p the Particle searching for a matching link + # def _try_links p puts " -> try_links ..." if @spin.debug_routing links = @links[p.src.name] return false if links.nil? pending_link = nil - apply_link = false links.each do |link| - apply_link = link.cond_fields.nil? # unconditional link - p.set_link_fields link.cond_fields if not apply_link - if apply_link or (p.link_value==link.cond_value) - # link matches ! + if p.link_with? link if pending_link p2 = @spin.require_p p.class p2.clone_data p @@ -117,12 +164,16 @@ module Edoors end if pending_link p.apply_link! pending_link - _send false, p + _send p end pending_link end private :_try_links # + # route the given Particle + # + # @param [Particle] p the Particle to be routed + # def _route p if p.room.nil? or p.room==path if door = @iotas[p.door] @@ -138,7 +189,12 @@ module Edoors end private :_route # - def _send sys, p + # send the given Particle + # + # @param [Particle] p the Particle to send + # @param [Boolean] sys if true send to system Particle fifo + # + def _send p, sys=false if not sys and p.src.nil? # do not route non system orphan particles !! p.error! Edoors::ERROR_ROUTE_NS, @spin @@ -166,25 +222,39 @@ module Edoors end private :_send # + # send the given Particle to application Particle fifo + # + # @param [Particle] p the Particle to send + # def send_p p puts " * send_p #{(p.next_dst.nil? ? 'no dst' : p.next_dst)} ..." if @spin.debug_routing - _send false, p + _send p puts " -> #{p.dst.path}#{Edoors::ACT_SEP}#{p.action}" if @spin.debug_routing @spin.post_p p end # + # send the given Particle to system Particle fifo + # + # @param [Particle] p the Particle to send + # def send_sys_p p puts " * send_sys_p #{(p.next_dst.nil? ? 'no dst' : p.next_dst)} ..." if @spin.debug_routing - _send true, p + _send p, true puts " -> #{p.dst.path}#{Edoors::ACT_SEP}#{p.action}" if @spin.debug_routing @spin.post_sys_p p end # + # process the given system Particle + # + # @param [Particle] p the Particle to be processed + # + # @note the Particle is automatically released + # def process_sys_p p if p.action==Edoors::SYS_ACT_ADD_LINK - add_link Edoors::Link.from_particle_data p + add_link Edoors::Link.from_particle p elsif p.action==Edoors::SYS_ACT_ADD_ROOM - Edoors::Room.from_particle_data p, self + Edoors::Room.from_particle p, self end @spin.release_p p end diff --git a/lib/edoors/spin.rb b/lib/edoors/spin.rb index 9f62b47..7efacf1 100644 --- a/lib/edoors/spin.rb +++ b/lib/edoors/spin.rb @@ -23,6 +23,25 @@ module Edoors # class Spin < Room # + # creates a Spin object from the arguments. + # + # @param [String] n the name of this Spin + # @param [Hash] o a customizable set of options + # + # @option o 'debug_garbage' [String Symbol] + # output debug information about automatic garbage + # @option o 'debug_routing' [String Symbol] + # output debug information about routing + # @option o 'hibernation' [Boolean] + # if set to true Iota#start! won't be called within Spin#spin! + # @option o 'inner_room' [Hash] + # composed of 2 keys, 'iotas' and 'links' use to repopulate the super class Room + # @option o 'app_fifo' [Array] + # list of Particle to feed @app_fifo + # @option o 'sys_fifo' [Array] + # list of Particle to feed @sys_fifo + # + # def initialize n, o={} super n, nil # @@ -34,7 +53,7 @@ module Edoors @run = false @hibernation = o['hibernation']||false @hibernate_path = 'edoors-hibernate-'+n+'.json' - @debug_errors = o[:debug_errors]||o['debug_errors']||false + @debug_garbage = o[:debug_garbage]||o['debug_garbage']||false @debug_routing = o[:debug_routing]||o['debug_routing']||false # if not o.empty? @@ -58,7 +77,11 @@ module Edoors end end # - attr_accessor :run, :hibernate_path, :debug_errors, :debug_routing + attr_accessor :run, :hibernate_path, :debug_garbage, :debug_routing + # + # called by JSON#generate to serialize the Spin object into JSON data + # + # @param [Array] a belongs to JSON generator # def to_json *a { @@ -69,25 +92,46 @@ module Edoors 'inner_room' => { :iotas=>@iotas, :links=>@links }, 'sys_fifo' => @sys_fifo, 'app_fifo' => @app_fifo, - 'debug_errors' => @debug_errors, + 'debug_garbage' => @debug_garbage, 'debug_routing' => @debug_routing }.to_json(*a) end # + # creates a Spin object from a JSON data + # + # @param [Hash] o belongs to JSON parser + # + # @raise Edoors::Exception if the json kls attribute is wrong + # def self.json_create o raise Edoors::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name self.new o['name'], o end # + # add the given Iota to the global Hash + # + # @param [Iota] iota the Iota to register + # + # @see Room#_route @world hash is used for routing + # def add_to_world iota @world[iota.path] = iota end # + # search the global Hash for the matching Iota + # + # @param [String] path the path to the desired Iota + # + # @see Room#_route @world hash is used for routing + # def search_world path @world[path] end # + # clears all the structures + # def clear! + @links.clear @iotas.clear @world.clear @pool.clear @@ -95,6 +139,13 @@ module Edoors @app_fifo.clear end # + # releases the given Particle + # + # @parama [Particle] p the Particle to be released + # + # @note the Particle is stored into Hash @pool to be reused as soon as needed + # + # @see Particle#reset! the Particle is reseted before beeing stored # def release_p p # hope there is no circular loop @@ -105,6 +156,12 @@ module Edoors ( @pool[p.class] ||= [] ) << p end # + # requires a Particle of the given Class + # + # @param [Class] p_kls the desired Class of Particle + # + # @note if there is no Particle of the given Class, one is created + # def require_p p_kls l = @pool[p_kls] return p_kls.new if l.nil? @@ -113,14 +170,26 @@ module Edoors p end # + # add the given Particle to the application Particle fifo + # + # @param [Particle] p the Particle to add + # def post_p p @app_fifo << p end # + # add the given Particle to the system Particle fifo + # + # @param [Particle] p the Particle to add + # def post_sys_p p @sys_fifo << p end # + # process the given particle + # + # @param [Particle] p the Particle to be processed + # def process_sys_p p if p.action==Edoors::SYS_ACT_HIBERNATE stop! @@ -130,6 +199,12 @@ module Edoors end end # + # starts the system mainloop + # + # first Iota#start! is called on each children unless the system is resuming from hibernation + # then while there is Particle in the fifo, first process all system Particle then 1 application Particle + # after all Iota#stop! is called on each children, unless the system is going into hibernation + # def spin! @iotas.values.each do |iota| iota.start! end unless @hibernation @run = true @@ -148,15 +223,27 @@ module Edoors @iotas.values.each do |iota| iota.stop! end unless @hibernation end # + # stops the spinning + # def stop! @run=false end # + # sends the system into hibernation + # + # @param [String] path the path to the hibernation file + # + # the system is serialized into JSON data and flushed to disk + # def hibernate! path=nil @hibernation = true File.open(path||@hibernate_path,'w') do |f| f << JSON.pretty_generate(self) end end # + # resumes the system from the given hibernation file + # + # @param [String] path the hibernation file to load the system from + # def self.resume! path self.json_create JSON.load File.open(path,'r') { |f| f.read } end diff --git a/spec/board_spec.rb b/spec/board_spec.rb index 7e291c6..1b2e0c4 100644 --- a/spec/board_spec.rb +++ b/spec/board_spec.rb @@ -16,11 +16,11 @@ describe Edoors::Board do # it "require_p release_p" do board = Edoors::Board.new 'hell', @spin - p0 = board.require_p Edoors::Particle + p0 = board.require_p p1 = board.require_p Edoors::Particle (p0===p1).should be_false board.release_p p0 - p2 = board.require_p Edoors::Particle + p2 = board.require_p (p0===p2).should be_true end # @@ -29,14 +29,14 @@ describe Edoors::Board do p0['k0'] = 'v0' p0['k1'] = 'neither' p0['k2'] = 'v2' - p0.set_link_fields 'k0,k2' - p0.link_value.should eql 'v0v2' + p0.set_link_keys 'k0', 'k2' + p0.link_value.should == {'k0'=>'v0','k2'=>'v2'} p1 = Edoors::Particle.new p1['k0'] = 'v0' p1['k1'] = 'nore' p1['k2'] = 'v2' - p1.set_link_fields 'k0,k2' - p1.link_value.should eql 'v0v2' + p1.set_link_keys 'k0', 'k2' + p1.link_value.should == {'k0'=>'v0','k2'=>'v2'} P0 = p0 P1 = p1 class Board0 < Edoors::Board @@ -79,7 +79,7 @@ describe Edoors::Board do p0 = Edoors::Particle.new p1 = Edoors::Particle.new p1['v0']=0 - p1.set_link_fields 'v0' + p1.set_link_keys 'v0' board.process_p p0 board.process_p p1 hell = Edoors::Board.json_create( JSON.load( JSON.generate(board) ) ) diff --git a/spec/door_spec.rb b/spec/door_spec.rb index 0c2dafe..d86bb5a 100644 --- a/spec/door_spec.rb +++ b/spec/door_spec.rb @@ -17,10 +17,10 @@ describe Edoors::Door do it "require_p release_p" do door = Edoors::Door.new 'hell', @spin p0 = door.require_p Edoors::Particle - p1 = door.require_p Edoors::Particle + p1 = door.require_p (p0===p1).should be_false door.release_p p0 - p2 = door.require_p Edoors::Particle + p2 = door.require_p (p0===p2).should be_true end # @@ -67,7 +67,7 @@ describe Edoors::Door do p0.add_dst 'RELEASE' p0.split_dst! d0.process_p p0 - p1 = d0.require_p Edoors::Particle + p1 = d0.require_p p1.should be p0 p0.clear_dsts! # diff --git a/spec/link_spec.rb b/spec/link_spec.rb index 0de1d93..49f086a 100644 --- a/spec/link_spec.rb +++ b/spec/link_spec.rb @@ -10,26 +10,23 @@ describe Edoors::Link do @spin = Edoors::Spin.new 'dom0' p = @spin.require_p Edoors::Particle p.set_data Edoors::LNK_SRC, 'input1' - p.set_data Edoors::LNK_DSTS, 'concat1?follow,output1' - p.set_data Edoors::LNK_FIELDS, 'f0,f2' - p.set_data Edoors::LNK_CONDF, 'f0,f1,f2' - p.set_data Edoors::LNK_CONDV, 'v0v1v2' - lnk = Edoors::Link.from_particle_data p + p.set_data Edoors::LNK_DSTS, ['concat1?follow','output1'] + p.set_data Edoors::LNK_KEYS, ['f0','f2'] + p.set_data Edoors::LNK_VALUE, {'f0'=>'v0','f1'=>'v1','f2'=>'v2'} + lnk = Edoors::Link.from_particle p lnk.src.should eql 'input1' - lnk.dsts.should eql 'concat1?follow,output1' - lnk.fields.should eql 'f0,f2' - lnk.cond_fields.should eql 'f0,f1,f2' - lnk.cond_value.should eql 'v0v1v2' + lnk.dsts.should eql ['concat1?follow','output1'] + lnk.keys.should eql ['f0','f2'] + lnk.value.should == {'f0'=>'v0','f1'=>'v1','f2'=>'v2'} end # it "link->json->link" do - link = Edoors::Link.new 'input1', 'concat1?follow,output1', 'f0,f2', 'f0,f1,f2', 'v0v1v2' + link = Edoors::Link.new 'input1', ['concat1?follow','output1'], ['f0','f2'], {'f0'=>'v0','f1'=>'v1','f2'=>'v2'} lnk = Edoors::Link.json_create( JSON.load( JSON.generate(link) ) ) link.src.should eql lnk.src link.dsts.should eql lnk.dsts - link.fields.should eql lnk.fields - link.cond_fields.should eql lnk.cond_fields - link.cond_value.should eql lnk.cond_value + link.keys.should eql lnk.keys + link.value.should eql lnk.value JSON.generate(link).should eql JSON.generate(lnk) end # diff --git a/spec/particle_spec.rb b/spec/particle_spec.rb index fe6508f..8d926ef 100644 --- a/spec/particle_spec.rb +++ b/spec/particle_spec.rb @@ -74,7 +74,7 @@ describe Edoors::Particle do d1 = Edoors::Door.new 'door1', nil p.dst.should be_nil p.next_dst.should be_nil - p.add_dsts 'some?where,room0/room1/door?action,room/door,door' + p.add_dsts 'some?where', 'room0/room1/door?action', 'room/door', 'door' p.next_dst.should eql 'some?where' p.dst_routed! d0 p.dst.should be d0 @@ -155,7 +155,7 @@ describe Edoors::Particle do p.action.should eql 'action' p.clear_dsts! # - p.add_dsts 'door?action,?action' + p.add_dsts 'door?action', '?action' p.split_dst! p.room.should be_nil p.door.should eql 'door' @@ -175,7 +175,7 @@ describe Edoors::Particle do p = Edoors::Particle.new d = Edoors::Door.new 'door', nil p.init! d - p.add_dsts 'door?action,?action' + p.add_dsts 'door?action', '?action' p.next_dst.should eql 'door?action' p.error! 'err_msg' p[Edoors::FIELD_ERROR_MSG].should eql 'err_msg' @@ -183,17 +183,30 @@ describe Edoors::Particle do p.dst.should be d end # - it "link fields and link value" do + it "link keys and link values" do + p = Edoors::Particle.new + p['k0'] = 'v0' + p.set_data 'k1', 'v1' + p['k2'] = 'v2' + p['k3'] = 'v3' + p.set_link_keys 'k0', 'k2', 'k1' + p.link_value.should == {'k0'=>'v0','k1'=>'v1','k2'=>'v2'} + p.del_data 'k0' + p.link_value.should == {'k1'=>'v1','k2'=>'v2'} + p.set_link_keys 'k1', 'k0' + p.link_value.should == {'k1'=>'v1'} + p['k1']='vX' + p.link_value.should == {'k1'=>'vX'} + end + # + it 'link_with?' do p = Edoors::Particle.new p['k0'] = 'v0' p['k1'] = 'v1' p['k2'] = 'v2' - p.set_link_fields 'k0,k2' - p.link_value.should eql 'v0v2' - p.set_link_fields 'k1,k0' - p.link_value.should eql 'v1v0' - p['k0']='vx' - p.link_value.should eql 'v1vx' + p.link_with?(Edoors::Link.new('', '', '')).should be_true + p.link_with?(Edoors::Link.new('', '', '', {'k0'=>'v0','k1'=>'v1'})).should be_true + p.link_with?(Edoors::Link.new('', '', '', {'k0'=>'v2','k1'=>'v1'})).should be_false end # it "apply_link!" do @@ -201,18 +214,18 @@ describe Edoors::Particle do p['k0'] = 'v0' p['k1'] = 'v1' p['k2'] = 'v2' - p.set_link_fields 'k0,k2' - p.add_dsts 'door?action,?action' + p.set_link_keys 'k0', 'k2' + p.add_dsts 'door?action', '?action' p.src.should be_nil - p.link_value.should eql 'v0v2' + p.link_value.should == {'k0'=>'v0','k2'=>'v2'} p.next_dst.should eql 'door?action' - lnk = Edoors::Link.new('door0', 'door1?get,door2', 'k1', 'f0,f1', 'v0v1') + lnk = Edoors::Link.new('door0', ['door1?get','door2'], 'k1', {'f0'=>'v0','f1'=>'v1'}) f = Fake.new 'fake', nil lnk.door = f p.apply_link! lnk p.src.should be f p.next_dst.should eql 'door1?get' - p.link_value.should eql 'v1' + p.link_value.should == {'k1'=>'v1'} end # it "particle->json->particle" do @@ -226,8 +239,8 @@ describe Edoors::Particle do p0['k1'] = 'v1' p0['k2'] = 'v2' p0.init! s3 - p0.set_link_fields 'k0,k2' - p0.add_dsts 'room0/room1/room2/doorX?myaction,door?action,?action' + p0.set_link_keys 'k0', 'k2' + p0.add_dsts 'room0/room1/room2/doorX?myaction', 'door?action', '?action' p0.split_dst! p1 = Edoors::Particle.new p1['k3'] = 'v6' @@ -235,8 +248,8 @@ describe Edoors::Particle do p1['k5'] = 'v8' p1.init! s3 p1.dst_routed! s4 - p1.set_link_fields 'k5,k4,k3' - p1.add_dsts 'room0/room1/door?action,output?action' + p1.set_link_keys 'k5', 'k4', 'k3' + p1.add_dsts 'room0/room1/door?action', 'output?action' p0.merge! p1 o = JSON.load( JSON.generate(p0) ) o['spin'] = s0 @@ -248,7 +261,7 @@ describe Edoors::Particle do px.door.should eql 'doorX' px.action.should eql 'myaction' px.next_dst.should eql 'room0/room1/room2/doorX?myaction' - px.link_value.should eql 'v0v2' + px.link_value.should == {'k0'=>'v0','k2'=>'v2'} px['k0'].should eql 'v0' px['k1'].should eql 'v1' px['k2'].should eql 'v2' @@ -260,7 +273,7 @@ describe Edoors::Particle do py.door.should be_nil py.action.should be_nil py.next_dst.should eql 'room0/room1/door?action' - py.link_value.should eql 'v8v7v6' + py.link_value.should == {'k3'=>'v6','k4'=>'v7','k5'=>'v8'} py['k3'].should eql 'v6' py['k4'].should eql 'v7' py['k5'].should eql 'v8' diff --git a/spec/room_spec.rb b/spec/room_spec.rb index de697c1..3757951 100644 --- a/spec/room_spec.rb +++ b/spec/room_spec.rb @@ -203,7 +203,7 @@ describe Edoors::Room do room0 = Edoors::Room.new 'room0', @spin door0 = Edoors::Door.new 'door0', room0 door1 = Edoors::Door.new 'door1', room0 - room0.add_link Edoors::Link.new('door0', 'door1', 'fields', 'f0,f1', 'v0v1') + room0.add_link Edoors::Link.new('door0', 'door1', 'keys', {'f0'=>'v0','f1'=>'v1'}) p = @spin.require_p Edoors::Particle p['f0']='v0' p['f1']='v1' @@ -225,14 +225,16 @@ describe Edoors::Room do end door1 = Out.new 'door1', room0 room0.add_link Edoors::Link.new('door0', 'door1') - room0.add_link Edoors::Link.new('door0', 'door1', 'fields', 'f0,f1', 'v0v1') - room0.add_link Edoors::Link.new('door0', 'door1', 'fields', 'f0,f1', 'v0v2') + room0.add_link Edoors::Link.new('door0', 'door1', 'keys', {'f0'=>'v0'}) + room0.add_link Edoors::Link.new('door0', 'door1', 'keys', {'f0'=>'v0','f1'=>'v1'}) + room0.add_link Edoors::Link.new('door0', 'door1', 'keys', {'f0'=>'v0','f1'=>'v2'}) + room0.add_link Edoors::Link.new('door0', 'door1', 'keys', {'f0'=>'v0','f2'=>'v1'}) p = @spin.require_p Edoors::Particle p['f0']='v0' p['f1']='v1' door0.send_p p @spin.spin! - door1.count.should eql 2 + door1.count.should eql 3 end # it "system route error: system no destination" do @@ -278,9 +280,8 @@ describe Edoors::Room do p0 = @spin.require_p Edoors::Particle p0.set_data Edoors::LNK_SRC, 'door0' p0.set_data Edoors::LNK_DSTS, 'door1' - p0.set_data Edoors::LNK_FIELDS, 'fields' - p0.set_data Edoors::LNK_CONDF, 'f0,f1' - p0.set_data Edoors::LNK_CONDV, 'v0v1' + p0.set_data Edoors::LNK_KEYS, 'keys' + p0.set_data Edoors::LNK_VALUE, {'f0'=>'v0','f1'=>'v1'} p0.add_dst Edoors::SYS_ACT_ADD_LINK, room0.path room0.send_sys_p p0 @spin.spin! @@ -318,10 +319,10 @@ describe Edoors::Room do d0 = Edoors::Door.new 'd0', r1 d1 = Edoors::Door.new 'd1', r1 d2 = Edoors::Door.new 'd2', r2 - r1.add_link Edoors::Link.new('d0', 'd1', 'fields', 'f0,f1', 'v0v1') + r1.add_link Edoors::Link.new('d0', 'd1', 'keys', {'f0'=>'v0','f1'=>'v1'}) r1.add_link Edoors::Link.new('d0', 'd2') r1.add_link Edoors::Link.new('d1', 'd0') - r2.add_link Edoors::Link.new('d2', 'd1', 'fies', 'f5,f1', 'v9v1') + r2.add_link Edoors::Link.new('d2', 'd1', 'fies', {'f5'=>'v9','f1'=>'v1'}) rx = Edoors::Room.json_create( JSON.load( JSON.generate(r0) ) ) JSON.generate(r0).should eql JSON.generate(rx) end# diff --git a/spec/spin_spec.rb b/spec/spin_spec.rb index 9ceebf8..9c2e2ea 100644 --- a/spec/spin_spec.rb +++ b/spec/spin_spec.rb @@ -86,10 +86,10 @@ describe Edoors::Spin do it "option debug" do spin = Edoors::Spin.new 'dom0' spin.debug_routing.should be false - spin.debug_errors.should be false - spin = Edoors::Spin.new 'dom0', :debug_routing=>true, :debug_errors=>true + spin.debug_garbage.should be false + spin = Edoors::Spin.new 'dom0', :debug_routing=>true, :debug_garbage=>true spin.debug_routing.should be true - spin.debug_errors.should be true + spin.debug_garbage.should be true end # it "search world" do @@ -129,7 +129,7 @@ describe Edoors::Spin do p0.add_dst Edoors::SYS_ACT_HIBERNATE Edoors::Room.new 'input', spin Edoors::Room.new 'output', spin - spin.add_link Edoors::Link.new('input', 'output', nil, nil, nil) + spin.add_link Edoors::Link.new('input', 'output', nil, nil) spin.send_sys_p p0 spin.spin! dom0 = Edoors::Spin.resume! spin.hibernate_path diff --git a/test/test_iotas.rb b/test/test_iotas.rb index 59c9552..333d514 100644 --- a/test/test_iotas.rb +++ b/test/test_iotas.rb @@ -136,7 +136,7 @@ class OutputDoor < Edoors::Door # end # -spin = Edoors::Spin.new 'dom0', :debug_routing=>false, :debug_errors=>true +spin = Edoors::Spin.new 'dom0', :debug_routing=>false, :debug_garbage=>true # room0 = Edoors::Room.new 'room0', spin room1 = Edoors::Room.new 'room1', spin @@ -148,14 +148,13 @@ input1 = InputDoor.new 'input1', room1 output1 = OutputDoor.new 'output1', room1, true concat1 = ConcatBoard.new 'concat1', room1 # -room0.add_link Edoors::Link.new('input0', 'output0', nil, nil, nil) +room0.add_link Edoors::Link.new('input0', 'output0', nil, nil) # p0 = spin.require_p Edoors::Particle p0.set_data Edoors::LNK_SRC, 'input1' -p0.set_data Edoors::LNK_DSTS, 'concat1?follow,output1' -p0.set_data Edoors::LNK_FIELDS, 'f0,f2' -p0.set_data Edoors::LNK_CONDF, 'f0,f1,f2' -p0.set_data Edoors::LNK_CONDV, 'v0v1v2' +p0.set_data Edoors::LNK_DSTS, ['concat1?follow','output1'] +p0.set_data Edoors::LNK_KEYS, ['f0','f2'] +p0.set_data Edoors::LNK_VALUE, {'f0'=>'v0','f1'=>'v1','f2'=>'v2'} p0.add_dst Edoors::SYS_ACT_ADD_LINK, room1.path room1.send_sys_p p0 # send_sys_p -> room0 -> spin -> room1 -> input1 # |