seh 0.1.0 → 0.3.0

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.
@@ -0,0 +1,11 @@
1
+ module Event
2
+ class << self
3
+ def hostile event, aggressor, aggressee
4
+ event.target aggressor, aggressee
5
+ event.type :hostile
6
+ event.aggressor = aggressor
7
+ event.aggressee = aggressee
8
+ event
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,32 @@
1
+ require 'seh'
2
+ require_relative "damage"
3
+ require_relative "hostile"
4
+
5
+ module Event
6
+ class << self
7
+ def melee_attack event, attacker, defender, attack_hit, attack_damage
8
+ hostile event, attacker, defender
9
+
10
+ event.target attacker, defender
11
+ event.type :melee_attack
12
+
13
+ event.attacker = attacker
14
+ event.defender = defender
15
+ event.attack_hit = attack_hit # true/false if this attack is currently hitting
16
+ event.attack_damage = attack_damage # damage this attack will do if it hits
17
+
18
+ event.add_stage :melee_determine_hit # an opportunity to affect the outcome of attack_hit
19
+ event.add_stage :melee_miss do !event.attack_hit end # unless attack_hit, run melee_miss
20
+ event.add_stage :melee_hit do event.attack_hit end # if attack_hit is true, run melee_hit
21
+
22
+ event.finish do # when the melee_attack event is over, create a damage event if the attack hit
23
+ next unless event.attack_hit
24
+ damage_event = Seh::Event.new
25
+ damage damage_event, event.attacker, event.defender, event.attack_damage
26
+ damage_event.dispatch
27
+ end
28
+
29
+ event
30
+ end
31
+ end # class << self
32
+ end
@@ -0,0 +1,57 @@
1
+
2
+ COLORS = {
3
+ "{@" => "\033[0m", #reset
4
+ # styles
5
+ "{!" => "\033[1m", #bold
6
+ "underlineZZ" => "\033[4m",
7
+ "blinkZZ" => "\033[5m",
8
+ "reverseZZ" => "\033[7m",
9
+ "concealedZZZ" => "\033[8m",
10
+ # font colors
11
+ "{FB" => "\033[30m",
12
+ "{FR" => "\033[31m",
13
+ "{FG" => "\033[32m",
14
+ "{FY" => "\033[33m",
15
+ "{FU" => "\033[34m",
16
+ "{FM" => "\033[35m",
17
+ "{FC" => "\033[36m",
18
+ "{FW" => "\033[37m",
19
+ # background colors
20
+ "{BB" => "\033[40m",
21
+ "{BR" => "\033[41m",
22
+ "{BG" => "\033[42m",
23
+ "{BY" => "\033[43m",
24
+ "{BU" => "\033[44m",
25
+ "{BM" => "\033[45m",
26
+ "{BC" => "\033[46m",
27
+ "{BW" => "\033[47m"
28
+ }
29
+
30
+ def color msg
31
+ COLORS.each do |color,code|
32
+ msg.gsub! color, code
33
+ end
34
+ msg
35
+ end
36
+
37
+ FOREGROUND_COLORS = [
38
+ "{FR",
39
+ "{FG",
40
+ "{FY",
41
+ "{FU",
42
+ "{FM",
43
+ "{FC",
44
+ ]
45
+
46
+ # cycle through foreground colors
47
+ def next_foreground_color
48
+ color = FOREGROUND_COLORS.shift
49
+ FOREGROUND_COLORS.push color
50
+ color
51
+ end
52
+
53
+ EVENT_COLORS = {}
54
+ def puts_color_event event_id, msg
55
+ EVENT_COLORS[event_id] ||= next_foreground_color
56
+ puts color "{!#{EVENT_COLORS[event_id]}#{msg}{@"
57
+ end
@@ -0,0 +1,10 @@
1
+ require 'seh'
2
+
3
+ class Mob
4
+ include Seh::EventTarget
5
+ attr_accessor :hp, :observers
6
+ def initialize
7
+ @hp = 100
8
+ @observers = []
9
+ end
10
+ end
data/lib/seh.rb CHANGED
@@ -1,22 +1,19 @@
1
- require "seh/version"
2
- require "seh/event_type"
3
- require "seh/event_target"
4
- require "seh/event"
1
+ Dir[File.dirname(__FILE__) + '/seh/*.rb'].each {|file| require file }
5
2
 
6
3
  module Seh
7
4
  class << self
8
5
  # @note alias of {EventType::And}
9
- def and( *types )
6
+ def and *types
10
7
  EventType::And.new *types
11
8
  end
12
9
 
13
10
  # @note alias of {EventType::Or}
14
- def or( *types )
11
+ def or *types
15
12
  EventType::Or.new *types
16
13
  end
17
14
 
18
15
  # @note alias of {EventType::Not}
19
- def not( type )
16
+ def not type
20
17
  EventType::Not.new type
21
18
  end
22
19
  end
@@ -1,178 +1,161 @@
1
+ require 'set'
1
2
  require 'ostruct'
2
3
 
3
4
  module Seh
4
- # @private
5
- module Private
6
- START = 0
7
- BEFORE = 100
8
-
9
- BEFORE_SUCCESS = 250
10
- BEFORE_FAILURE = 250
11
-
12
- SUCCESS = 500
13
- FAILURE = 500
14
-
15
- AFTER_SUCCESS = 750
16
- AFTER_FAILURE = 750
17
-
18
- AFTER = 900
19
- FINISH = 1000
20
-
21
- class EventData
22
- attr_accessor :types, :type_map, :target, :time, :staged_handlers, :success
23
-
24
- def initialize
25
- @types = []
26
- @type_map = {}
27
- @target = nil
28
- @time = Time.now
29
- @success = true
30
-
31
- # staged handlers
32
- @staged_handlers = {}
33
- end
34
- end
35
-
36
- class EventStateReady
37
- class << self
38
- def type( data, t )
39
- data.types << t
40
- apply_type_map data
41
- end
42
-
43
- def type_map( data, map = {} )
44
- map.each_pair do |type, implied_types|
45
- data.type_map[type] ||= []
46
- data.type_map[type].concat implied_types
47
- data.type_map[type].uniq!
48
- end
49
- apply_type_map data
50
- end
51
-
52
- private
53
- def apply_type_map( data )
54
- while true
55
- original_size = data.types.size
56
- data.type_map.each_pair { |type, implied_types| data.types.concat implied_types if data.types.include? type }
57
- data.types.uniq!
58
- break if original_size = data.types.size
59
- end
60
- end
61
- end
62
- end
63
-
64
- class EventStateInflight
65
- end
66
-
67
- class EventStateDone
68
- end
69
- end
70
-
71
5
  class Event < OpenStruct
72
- def initialize(target, &block)
73
- super()
74
- raise "Event expects a target" unless target
75
- raise "Event expects the target to include EventTarget" unless target.class.include? EventTarget
76
- @state = Private::EventStateReady
77
- @data = Private::EventData.new
78
- @data.target = target
79
- instance_eval(&block) if block
80
- end
81
-
82
- def fail
83
- @data.success = false
84
- end
85
-
86
- def success?
87
- @data.success
88
- end
89
-
6
+ def initialize
7
+ super
8
+ @state = :ready
9
+ @types = Set.new
10
+ @targets = Set.new
11
+ @start_callbacks = []
12
+ @finish_callbacks = []
13
+ @stage_callbacks = {}
14
+ @stage_decision_blocks = {}
15
+ @stages = Set.new
16
+ @abort = false
17
+ yield self if block_given?
18
+ end
19
+
20
+ # Dispatch this event, notifying all targets of the event and executing any callbacks.
21
+ # #dispatch may only be called once
22
+ # Dispatch algorithm:
23
+ # 1. determine the full set of targets affected by this event
24
+ # 2. run callbacks on targets which match this event's types
25
+ # 3. run stage callbacks contained in this event; typically targets will append stage callbacks to this event using Event#bind, #start, #finish
26
+ # Callback execution order:
27
+ # start callbacks
28
+ # stage callabcks - in the order stages were added
29
+ # finish callbacks
30
+ # Callbacks in the same stage have arbitrary execution order
31
+ # @return nil
90
32
  def dispatch
91
- raise "Event#dispatch may only be called once" unless @state == Private::EventStateReady
92
- @state = Private::EventStateInflight
93
- collect_targets.each { |t| t.each_bind { |bind| bind.block.call self if bind.event_type.match @data.types } }
94
- @data.staged_handlers.each_key.sort.each { |stage| @data.staged_handlers[stage].each { |block| block.call self } }
95
- @state = Private::EventStateDone
96
- end
97
-
98
- def target
99
- @data.target
100
- end
101
-
102
- def type( event_type )
103
- @state.type @data, event_type
33
+ raise "Event#dispatch may only be called once" unless @state == :ready
34
+ @state = :inflight
35
+ return if @abort
36
+ run_target_callbacks
37
+ run_stage_callbacks
38
+ @state = :done
104
39
  nil
105
40
  end
106
41
 
107
- def type_map( map )
108
- @state.type_map @data, map
42
+ # Abort this Event. After #abort is called, some in-progress work may still be completed.
43
+ # Abort semantics:
44
+ # - if #abort is called before #dispatch, #dispatch return immediately, no target will know the event occurred, and no callbacks will be executed
45
+ # - if #abort is called by a target while visiting the set of targets, each target will still receive the event but no stage callbacks will be exewcuted
46
+ # - if #abort is called during start callbacks, start will complete and no stage or finish callbacks will be run
47
+ # - if #abort is called during a stage callback, the current stage will complete and no other stage or finish callbacks will be run
48
+ # - if #abort is called during a finish callback, finish will complete, i.e. calling #abort during a finish callbacks is fairly pointless
49
+ # @return nil
50
+ def abort
51
+ @abort = true
109
52
  nil
110
53
  end
111
54
 
112
- def match_type( event_type )
113
- event_type = EventType.new event_type unless event_type.is_a? EventType
114
- event_type.match @data.types
55
+ # @return true if #abort has been called, false otherwise
56
+ def aborted?
57
+ @abort
115
58
  end
116
59
 
117
- def time
118
- @data.time.dup
119
- end
120
-
121
- def start(&block)
122
- staged_handler Private::START, block if block_given?
123
- end
124
-
125
- def before(&block)
126
- staged_handler Private::BEFORE, block if block_given?
127
- end
128
-
129
- def before_success(&block)
130
- staged_handler Private::BEFORE_SUCCESS, ->e{ block.call e if e.success? } if block_given?
60
+ # Add targets to this event. May not be called after or during #dispatch
61
+ # @param targets - zero or more EventTarget objects to add to this event
62
+ # @return nil
63
+ def target *targets
64
+ raise "Event#target is disallowed after Event#dispatch is called" unless @state == :ready
65
+ targets.each { |target| @targets << target }
66
+ nil
131
67
  end
132
68
 
133
- def before_failure(&block)
134
- staged_handler Private::BEFORE_FAILURE, ->e{ block.call e unless e.success? } if block_given?
69
+ # Add event types to this event. May not be called after or during #dispatch
70
+ # @param types - zero or more types to add to this Event. The Event is simultaneously all of these types
71
+ # @return nil
72
+ def type *event_types
73
+ raise "Event#type is disallowed after Event#dispatch is called" unless @state == :ready
74
+ event_types.each { |type| @types << type }
75
+ nil
135
76
  end
136
77
 
137
- def success(&block)
138
- staged_handler Private::SUCCESS, ->e{ block.call e if e.success? } if block_given?
78
+ # Add the passed new stage to this event
79
+ # @param new_stage - a stage to add to this event
80
+ # @block - a block with a single parameter |event|; during #dispatch, this block will be called with self immediately prior to executing new_stage's callbacks. new_stage and its callbacks will be skipped (not executed) if this block returns falsy.
81
+ # @return nil
82
+ def add_stage new_stage, &stage_decision_block
83
+ raise "Event#add_stage is disallowed after Event#dispatch is called" unless @state == :ready
84
+ @stages << new_stage
85
+ @stage_callbacks[new_stage] ||= []
86
+ @stage_decision_blocks[new_stage] = stage_decision_block if block_given?
87
+ nil
139
88
  end
140
89
 
141
- def failure(&block)
142
- staged_handler Private::FAILURE, ->e{ block.call e unless e.success? } if block_given?
90
+ # Return true if this event's types match the passed EventType
91
+ # @param event_type - an EventType to match against this event's types
92
+ # @return true or false - result of passed EventType#match on this event's types
93
+ def match_type? event_type
94
+ event_type = EventType.new event_type unless event_type.is_a? EventType
95
+ event_type.match @types
143
96
  end
144
97
 
145
- def after_success(&block)
146
- staged_handler Private::AFTER_SUCCESS, ->e{ block.call e if e.success? } if block_given?
98
+ # Bind the passed block as a callback for the passed stage
99
+ # @param stage - a stage which has been added using #add_stage
100
+ # @block - a callback receiving a single parameter, |event|, which will be added to stage's callbacks
101
+ # @return nil
102
+ def bind stage, &block
103
+ @stage_callbacks[stage] << block if block_given?
104
+ nil
147
105
  end
148
106
 
149
- def after_failure(&block)
150
- staged_handler Private::AFTER_FAILURE, ->e{ block.call e unless e.success? } if block_given?
107
+ # Bind the passed block as a start callback
108
+ # @block - a callback receiving a single parameter, |event|, which will be added to the start callbacks
109
+ # @return nil
110
+ def start &block
111
+ @start_callbacks << block if block_given?
112
+ nil
151
113
  end
152
114
 
153
- def after(&block)
154
- staged_handler Private::AFTER, block if block_given?
115
+ # Bind the passed block as a finish callback
116
+ # @block - a callback receiving a single parameter, |event|, which will be added to the finish callbacks
117
+ # @return nil
118
+ def finish &block
119
+ @finish_callbacks << block if block_given?
120
+ nil
155
121
  end
156
122
 
157
- def finish(&block)
158
- staged_handler Private::FINISH, block if block_given?
123
+ private
124
+ # Used in #dispatch, run callbacks that match our types on each target in the target closure
125
+ def run_target_callbacks
126
+ collect_targets.each do |target|
127
+ target.each_matching_callback(@types) { |callback| callback.call self }
128
+ end
159
129
  end
160
130
 
161
- private
162
- def staged_handler( stage, block )
163
- @data.staged_handlers[stage] ||= []
164
- @data.staged_handlers[stage] << block
165
- nil
131
+ # Used in #dispatch, run the stage callbacks on this event; to be run after each target had a chance to append callbacks
132
+ # Callback execution order:
133
+ # start callbacks
134
+ # stage callabcks - in the order stages were added
135
+ # finish callbacks
136
+ # Callbacks in the same stage have arbitrary execution order
137
+ def run_stage_callbacks
138
+ return if @abort
139
+ @start_callbacks.each { |block| block.call self }
140
+ @stages.each do |stage|
141
+ return if @abort
142
+ next if @stage_decision_blocks.key? stage and not @stage_decision_blocks[stage].call self
143
+ @stage_callbacks[stage].each { |block| block.call self }
144
+ end
145
+ return if @abort
146
+ @finish_callbacks.each { |block| block.call self }
166
147
  end
167
148
 
149
+ # Compute the target closure, equal to the set of targets on this event and their observers (and recursive observers)
168
150
  def collect_targets
169
- targets_working = [@data.target]
170
- targets_final = []
171
- while t = targets_working.shift do
172
- targets_final << t
173
- targets_working.concat t.parents if t.respond_to? :parents
174
- end
175
- targets_final.uniq
151
+ all_targets = @targets.dup # @targets must remain the original set of targets on this event, and all_targets will be mutated. NOTE: @targets isn't actually used after collect_targets() is called, so we might remove the .dup()
152
+ observers = Set.new
153
+ begin
154
+ original_size = all_targets.size
155
+ all_targets.each { |target| observers.merge target.observers }
156
+ all_targets.merge observers
157
+ end while all_targets.size != original_size
158
+ all_targets
176
159
  end
177
160
  end
178
161
  end
@@ -0,0 +1,15 @@
1
+ require_relative 'event_type'
2
+
3
+ module Seh
4
+ # @private
5
+ # Internal use only. Container for an event_type,block pair bound to an EventTarget. Converts event_type into a Seh::EventType
6
+ class EventBind
7
+ attr_reader :event_type, :block
8
+
9
+ def initialize event_type, &block
10
+ event_type = EventType.new event_type unless event_type.is_a? EventType
11
+ @event_type = event_type
12
+ @block = block
13
+ end
14
+ end
15
+ end