Module: Inform::Daemons

Included in:
Object, System::Object
Defined in:
lib/runtime/daemon.rb

Overview

The Inform::Daemons module

Defined Under Namespace

Classes: Entheogen

Constant Summary collapse

DaemonScanElapsedMessage =
"Scanned %<daemons>s daemons in %<elapsed>0.2f milliseconds".freeze
DaemonExecutionElapsedMessage =
"\e[1m\e[33mExecuted daemons in %<elapsed>0.2f milliseconds\e[39m\e[22m".freeze
RecordNotFoundPattern =
%r{Record not found}.freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.daemonsObject



161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/runtime/daemon.rb', line 161

def self.daemons
  start = Time.ms
  Inform::Daemons.spawn.each do |daemon|
    # In realtime games, each_turn is a synonym for daemon
    next unless daemon.respond_to?(:daemon) || daemon.respond_to?(:each_turn)
    Inform::Daemons.execute daemon
  end
ensure
  finish = Time.ms
  elapsed = finish - start
  log.debug format(DaemonExecutionElapsedMessage, elapsed: elapsed) if finish % 300_000 == 0
end

.ensure_inform_library(obj) ⇒ Object



183
184
185
186
187
188
189
# File 'lib/runtime/daemon.rb', line 183

def self.ensure_inform_library(obj)
  return unless obj.inflib.nil?
  # TODO: Use a pool of InformLibraries until
  # comprehensive event contexts can be implemented
  log.warn "Using singleton Ghost InformLibrary for #{obj} daemon"
  obj.inflib = Inform::Runtime.libraries[Inform::Daemons.ghost]
end

.execute(obj) ⇒ Object



207
208
209
210
211
212
213
214
215
216
# File 'lib/runtime/daemon.rb', line 207

def self.execute(obj)
  return if obj.nil?
  if obj.respond_to? :daemon
    execute_daemon(obj)
  elsif obj.respond_to? :each_turn
    execute_each_turn(obj)
  end
rescue StandardError => e
  log.error "Error executing daemon for #{obj}", e
end

.execute_daemon(obj) ⇒ Object

TODO: Potentially go ahead and print any string results from the daemon invocation. For example:

obj.println obj.daemon


195
196
197
198
199
# File 'lib/runtime/daemon.rb', line 195

def self.execute_daemon(obj)
  refresh_or_remove(obj)
  ensure_inform_library(obj)
  obj.daemon
end

.execute_each_turn(obj) ⇒ Object



201
202
203
204
205
# File 'lib/runtime/daemon.rb', line 201

def self.execute_each_turn(obj)
  refresh_or_remove(obj)
  ensure_inform_library(obj)
  obj.each_turn
end

.refresh_or_remove(obj) ⇒ Object



176
177
178
179
180
181
# File 'lib/runtime/daemon.rb', line 176

def self.refresh_or_remove(obj)
  return if obj.nil?
  obj.refresh
rescue StandardError => e
  Spawn.remove obj if RecordNotFoundPattern.match?(e.message)
end

.scan_objectsObject



128
129
130
131
132
133
134
135
136
137
# File 'lib/runtime/daemon.rb', line 128

def self.scan_objects
  start = Time.now
  Inform::Object.all.each do |obj|
    Spawn.add obj if obj.respond_to?(:daemon) || obj.respond_to?(:each_turn)
  end
rescue StandardError => e
  log.error "Error executing daemon method: #{e.message}", e
ensure
  log.debug format(DaemonScanElapsedMessage, daemons: Spawn.length, elapsed: Time.now - start)
end

.startObject

rubocop: disable Metrics/AbcSize



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/runtime/daemon.rb', line 140

def self.start
  initial_delay = Inform::Game.config[:daemon_initial_delay]
  period = Inform::Game.config[:daemons_period]
  unit = defined?(Java) ? java.util.concurrent.TimeUnit::MILLISECONDS : 'milliseconds'
  Thread.new do
    Inform::Daemons.rescan
    log.debug "Starting daemons..."
    daemons = Inform::Daemons.method(:daemons).to_proc
    Inform::Daemons.ghost.heartbeat = Inform::Daemons.heart.scheduleAtFixedRate(
      daemons, initial_delay, period, unit)
  end
end

.stopObject

rubocop: enable Metrics/AbcSize



154
155
156
157
# File 'lib/runtime/daemon.rb', line 154

def self.stop
  Inform::Daemons.ghost.heartbeat.cancel(false)
  Inform::Daemons.spawn.clear
end

Instance Method Details

#before_destroyObject



245
246
247
248
249
# File 'lib/runtime/daemon.rb', line 245

def before_destroy
  log.debug "#{self}: before_destroy stopping daemon"
  StopDaemon(self) if daemonic?
  super
end

#daemonic?Boolean

Returns:

  • (Boolean)


241
242
243
# File 'lib/runtime/daemon.rb', line 241

def daemonic?
  Inform::Daemons.schedules.include? self
end

#daemonize(event) ⇒ Object



237
238
239
# File 'lib/runtime/daemon.rb', line 237

def daemonize(event)
  Inform::Daemons.schedules[self] = event
end

#ghostObject



108
109
110
# File 'lib/runtime/daemon.rb', line 108

def ghost
  @ghost ||= Entheogen.new
end

#heartObject



91
92
93
# File 'lib/runtime/daemon.rb', line 91

def heart
  @heart ||= init_heart
end

#init_heartObject



95
96
97
98
99
# File 'lib/runtime/daemon.rb', line 95

def init_heart
  # TODO: Figure out ruby-concurrency Executors?
  return unless defined?(Java)
  java.util.concurrent.Executors.newSingleThreadScheduledExecutor()
end

#occasionally(time = nil, &block) ⇒ Object

rubocop: disable Metrics/AbcSize



252
253
254
255
256
257
258
259
260
261
# File 'lib/runtime/daemon.rb', line 252

def occasionally(time = nil, &block)
  time = Inform::Game.config[:daemons_frequency_default] if time.nil?
  return if active? || daemonic?
  frequency = ((self.&:frequency) || time).to_i
  occasion = rand(frequency) + 1
  log.trace "Scheduling occasional daemonic event for #{self} in #{occasion} seconds"
  (Inform::Daemons.schedules[self] = delay(occasion, &block)).finally do
    Inform::Daemons.schedules.delete self
  end
end

#on?Boolean

Returns:

  • (Boolean)


116
117
118
119
# File 'lib/runtime/daemon.rb', line 116

def on?
  return false if Inform::Daemons.ghost.heartbeat.nil?
  Inform::Daemons.ghost.heartbeat.isCancelled() == false
end

#rescanObject



121
122
123
124
# File 'lib/runtime/daemon.rb', line 121

def rescan
  Inform::Daemons.spawn.clear
  Inform::Daemons.scan_objects
end

#schedulesObject



112
113
114
# File 'lib/runtime/daemon.rb', line 112

def schedules
  @schedules ||= defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {}
end

#spawnObject



87
88
89
# File 'lib/runtime/daemon.rb', line 87

def spawn
  @spawn ||= defined?(Java) ? java.util.concurrent.ConcurrentSkipListSet.new : Set.new
end

#StartDaemon(obj) ⇒ Object



218
219
220
# File 'lib/runtime/daemon.rb', line 218

def StartDaemon(obj)
  Inform::Daemons.spawn.add obj if obj.respond_to?(:daemon) || obj.respond_to?(:each_turn)
end

#StartTimer(obj, interval) ⇒ Object



229
230
231
# File 'lib/runtime/daemon.rb', line 229

def StartTimer(obj, interval)
  # TODO: Implement
end

#StopDaemon(obj) ⇒ Object



222
223
224
225
226
227
# File 'lib/runtime/daemon.rb', line 222

def StopDaemon(obj)
  return if obj.nil?
  log.warn "Stopping daemon: #{obj}"
  obj.undef_method(:daemon) # TODO: Maybe don't do this
  Inform::Daemons.spawn.remove obj
end

#StopTimer(obj) ⇒ Object



233
234
235
# File 'lib/runtime/daemon.rb', line 233

def StopTimer(obj)
  # TODO: Implement
end