Class: Demiurge::ActionItem

Inherits:
StateItem show all
Defined in:
lib/demiurge/action_item.rb

Overview

A Demiurge::ActionItem keeps track of actions from Ruby code blocks and implements the Demiurge DSL for action code, including inside World Files.

Since:

  • 0.0.1

Direct Known Subclasses

Agent, Container

Constant Summary

[ "name", "block", "busy", "engine_code", "tags" ]

Instance Attribute Summary

Attributes inherited from StateItem

#engine, #name

Instance Method Summary collapse

Methods inherited from StateItem

#agent?, from_name_type, #get_structure, #state, #state_type, #zone?

Constructor Details

#initialize(name, engine, state) ⇒ void

Constructor. Set up ActionItem-specific things like EveryXTicks actions.

Parameters:

  • name (String)

    The registered StateItem name

  • engine (Demiurge::Engine)

    The Engine this item is part of

  • state (Hash)

    The state hash for this item

Since:

  • 0.0.1



15
16
17
18
19
# File 'lib/demiurge/action_item.rb', line 15

def initialize(name, engine, state)
  super # Set @name and @engine and @state
  @every_x_ticks_intention = ActionItemInternal::EveryXTicksIntention.new(engine, name)
  nil
end

Instance Method Details

#__state_internalHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

An internal function that provides the object's internal state to an action block via a Runner class.

Returns:

  • (Hash)

    The internal state of this item for use in DSL action blocks

Since:

  • 0.0.1



88
89
90
# File 'lib/demiurge/action_item.rb', line 88

def __state_internal
  @state
end

#finished_initvoid

This method returns an undefined value.

Callback to be called from the Engine when all items are loaded.

Since:

  • 0.0.1



25
26
27
28
29
# File 'lib/demiurge/action_item.rb', line 25

def finished_init
  loc = self.location
  loc.move_item_inside(self) unless loc.nil?
  nil
end

#get_action(action_name) ⇒ Hash?

Get the action hash structure for a given action name. This is normally done to verify that a specific action name exists at all.

Parameters:

  • action_name (String)

    The action name to query

Returns:

  • (Hash, nil)

    A hash of information about the action, or nil if the action doesn't exist

Since:

  • 0.0.1



181
182
183
184
185
186
187
188
# File 'lib/demiurge/action_item.rb', line 181

def get_action(action_name)
  action = @engine.action_for_item(@name, action_name)
  if !action && state["parent"]
    # Do we have a parent and no action definition yet? If so, defer to the parent.
    action = @engine.item_by_name(state["parent"]).get_action(action_name)
  end
  action
end

#get_actions_with_tags(tags) ⇒ Array<Hash>

Return all actions which have the given String tags specified for them.

Parameters:

  • tags (Array<String>)

    An array of tags the returned actions should match

Returns:

  • (Array<Hash>)

    An array of action structures. The "name" field of each gives the action name

Since:

  • 0.0.1



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/demiurge/action_item.rb', line 195

def get_actions_with_tags(tags)
  tags = [tags].flatten # Allow calling with a single tag string
  @actions = []
  @engine.actions_for_item(@name).each do |action_name, action_struct|
    # I'm sure there's some more clever way to check if the action contains all these tags...
    if (tags - (action_struct["tags"] || [])).empty?
      @actions.push action_struct
    end
  end
  @actions
end

#intentions_for_next_stepArray<Demiurge::Intention>

Get this item's intentions for the next tick.

Returns:

Since:

  • 0.0.1



96
97
98
99
100
# File 'lib/demiurge/action_item.rb', line 96

def intentions_for_next_step
  everies = @state["everies"]
  return [] if everies.nil? || everies.empty?
  [@every_x_ticks_intention]
end

#locationDemiurge::StateItem?

Get the location StateItem where this item is located.

Returns:

Since:

  • 0.0.1



46
47
48
49
50
# File 'lib/demiurge/action_item.rb', line 46

def location
  ln = location_name
  return nil if ln == "" || ln == nil
  @engine.item_by_name(location_name)
end

#location_nameString?

Get the name of this item's location. This is compatible with complex positions, and removes any sub-location suffix, if there is one.

Returns:

  • (String, nil)

    The location name where this item exists, or nil if it has no location

Since:

  • 0.0.1



37
38
39
40
# File 'lib/demiurge/action_item.rb', line 37

def location_name
  pos = @state["position"]
  pos ? pos.split("#",2)[0] : nil
end

#positionString?

A Position can be simply a location ("here's a room-type object and you're in it") or something more specific, such as a specific coordinate within a room. In general, a Position consists of a location's unique item name, optionally followed by an optional pound sign ("#") and zone-specific additional coordinates.

Returns:

  • (String, nil)

    This item's position, or nil if it has no location.

Since:

  • 0.0.1



60
61
62
# File 'lib/demiurge/action_item.rb', line 60

def position
  @state["position"]
end

#register_actions(action_hash) ⇒ Object

This method is called by (among other things) define_action to specify things about an action. It's how to specify the action's code, how busy it makes an agent when it occurs, what Runner to use with it, and any appropriate String tags. While it can be called multiple times to specify different things about a single action, it must not be called with the same information. So the block can only be specified once, "busy" can only be specified once and so on.

This means that if an action's block is given implicitly by something like an every_X_ticks declaration, it can use define_action to set "busy" or "engine_code". But it can't define a different block of code to run with define_action.

Parameters:

  • action_hash (Hash)

    Specify something or everything about an action by its name.

Options Hash (action_hash):

  • name (String)

    The name of the action, which is required.

  • block (Proc)

    The block of code for the action itself

Returns:

  • void

Since:

  • 0.0.1



125
126
127
# File 'lib/demiurge/action_item.rb', line 125

def register_actions(action_hash)
  @engine.register_actions_by_item_and_action_name(@name => action_hash)
end

#run_action(action_name, *args, current_intention: nil) ⇒ void

This method returns an undefined value.

This is a raw, low-level way to execute an action of an ActionItem. It doesn't wait for Intentions. It doesn't send extra notifications. It doesn't offer or give a chance to cancel the action. It just runs.

Parameters:

  • action_name (String)

    The name of the action to run. Must already be registered.

  • args (Array)

    Additional arguments to pass to the action's code block

  • current_intention (nil, Intention)

    Current intention being executed, if any. This is used for to cancel an intention, if necessary

Raises:

Since:

  • 0.0.1



139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/demiurge/action_item.rb', line 139

def run_action(action_name, *args, current_intention: nil)
  action = get_action(action_name)
  raise ::Demiurge::Errors::NoSuchActionError.new("No such action as #{action_name.inspect} for #{@name.inspect}!",
                                                  "item" => self.name, "action" => action_name,
                                                  execution_context: @engine.execution_context) unless action
  block = action["block"]
  raise ::Demiurge::Errors::NoSuchActionError.new("Action was never defined for #{action_name.inspect} of object #{@name.inspect}!",
                                                  "item" => self.name, "action" => action_name,
                                                  execution_context: @engine.execution_context) unless block

  runner_constructor_args = {}
  if action["engine_code"]
    block_runner_type = ActionItemInternal::EngineBlockRunner
  elsif self.agent?
    block_runner_type = ActionItemInternal::AgentBlockRunner
    runner_constructor_args[:current_intention] = current_intention
  else
    block_runner_type = ActionItemInternal::ActionItemBlockRunner
    runner_constructor_args[:current_intention] = current_intention
  end
  # TODO: can we save block runners between actions?
  block_runner = block_runner_type.new(self, **runner_constructor_args)
  begin
    @engine.push_context("running_action" => action_name, "running_action_item" => @name) do
      block_runner.instance_exec(*args, &block)
    end
  rescue
    #STDERR.puts "#{$!.message}\n#{$!.backtrace.join("\n")}"
    raise ::Demiurge::Errors::BadScriptError.new("Script error of type #{$!.class} with message: #{$!.message}",
                                                 { "runner type": block_runner_type.to_s, "action" => action_name, },
                                                 execution_context: @engine.execution_context);
  end
  nil
end

#zoneStateItem?

Get the StateItem of the Zone where this item is located. This may be different from its "home" Zone.

Returns:

  • (StateItem, nil)

    This item's Zone's StateItem, or nil in the very unusual case that it has no current Zone.

Since:

  • 0.0.1



68
69
70
71
# File 'lib/demiurge/action_item.rb', line 68

def zone
  zn = zone_name
  zn ? @engine.item_by_name(zn) : nil
end

#zone_nameString?

Get the Zone name for this StateItem's current location, which may be different from its "home" Zone.

Returns:

  • (String, nil)

    This item's Zone's name, or nil in the very unusual case that it has no current Zone.

Since:

  • 0.0.1



77
78
79
80
# File 'lib/demiurge/action_item.rb', line 77

def zone_name
  l = location
  l ? l.zone_name : state["zone"]
end