inform-runtime 1.0.4
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/LICENSE +623 -0
- data/README.md +185 -0
- data/Rakefile +65 -0
- data/config/database.yml +37 -0
- data/exe/inform.rb +6 -0
- data/game/config.yml +5 -0
- data/game/example.inf +76 -0
- data/game/example.rb +90 -0
- data/game/forms/example_form.rb +2 -0
- data/game/grammar/game_grammar.inf.rb +11 -0
- data/game/languages/english.rb +2 -0
- data/game/models/example_model.rb +2 -0
- data/game/modules/example_module.rb +9 -0
- data/game/rules/example_state.rb +2 -0
- data/game/scripts/example_script.rb +2 -0
- data/game/topics/example_topic.rb +2 -0
- data/game/verbs/game_verbs.rb +15 -0
- data/game/verbs/metaverbs.rb +2028 -0
- data/lib/runtime/articles.rb +138 -0
- data/lib/runtime/builtins.rb +359 -0
- data/lib/runtime/color.rb +145 -0
- data/lib/runtime/command.rb +470 -0
- data/lib/runtime/config.rb +48 -0
- data/lib/runtime/context.rb +78 -0
- data/lib/runtime/daemon.rb +266 -0
- data/lib/runtime/database.rb +500 -0
- data/lib/runtime/events.rb +771 -0
- data/lib/runtime/experimental/handler_dsl.rb +175 -0
- data/lib/runtime/game.rb +74 -0
- data/lib/runtime/game_loader.rb +132 -0
- data/lib/runtime/grammar_parser.rb +553 -0
- data/lib/runtime/helpers.rb +177 -0
- data/lib/runtime/history.rb +45 -0
- data/lib/runtime/inflector.rb +195 -0
- data/lib/runtime/io.rb +174 -0
- data/lib/runtime/kernel.rb +450 -0
- data/lib/runtime/library.rb +59 -0
- data/lib/runtime/library_loader.rb +135 -0
- data/lib/runtime/link.rb +158 -0
- data/lib/runtime/logging.rb +197 -0
- data/lib/runtime/mixins.rb +570 -0
- data/lib/runtime/module.rb +202 -0
- data/lib/runtime/object.rb +761 -0
- data/lib/runtime/options.rb +104 -0
- data/lib/runtime/persistence.rb +292 -0
- data/lib/runtime/plurals.rb +60 -0
- data/lib/runtime/prototype.rb +307 -0
- data/lib/runtime/publication.rb +92 -0
- data/lib/runtime/runtime.rb +321 -0
- data/lib/runtime/session.rb +202 -0
- data/lib/runtime/stdlib.rb +604 -0
- data/lib/runtime/subscription.rb +47 -0
- data/lib/runtime/tag.rb +287 -0
- data/lib/runtime/tree.rb +204 -0
- data/lib/runtime/version.rb +24 -0
- data/lib/runtime/world_tree.rb +69 -0
- data/lib/runtime.rb +35 -0
- metadata +199 -0
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# frozen_string_literal: false
|
|
3
|
+
|
|
4
|
+
# Copyright Nels Nelson 2008-2023 but freely usable (see license)
|
|
5
|
+
#
|
|
6
|
+
# This file is part of the Inform Runtime.
|
|
7
|
+
#
|
|
8
|
+
# The Inform Runtime is free software: you can redistribute it and/or
|
|
9
|
+
# modify it under the terms of the GNU General Public License as published
|
|
10
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
|
11
|
+
# (at your option) any later version.
|
|
12
|
+
#
|
|
13
|
+
# The Inform Runtime is distributed in the hope that it will be useful,
|
|
14
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
# GNU General Public License for more details.
|
|
17
|
+
#
|
|
18
|
+
# You should have received a copy of the GNU General Public License
|
|
19
|
+
# along with the Inform Runtime. If not, see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
require 'set'
|
|
22
|
+
|
|
23
|
+
# In order to support atmospheric messages and NPC automation
|
|
24
|
+
# based on timers, I'm trying to come up with an approach for
|
|
25
|
+
# incorporating the Inform 6 version of daemons into a multi-
|
|
26
|
+
# player environment.
|
|
27
|
+
#
|
|
28
|
+
# Perhaps some form of scanner will detect which objects implement
|
|
29
|
+
# the daemon method, and then schedule its invocation once every
|
|
30
|
+
# so often.
|
|
31
|
+
#
|
|
32
|
+
# For scheduled events:
|
|
33
|
+
#
|
|
34
|
+
# def after_initialize
|
|
35
|
+
# schedule('every ten minutes') do
|
|
36
|
+
# do_something
|
|
37
|
+
# end
|
|
38
|
+
# end
|
|
39
|
+
#
|
|
40
|
+
# For daemon events:
|
|
41
|
+
#
|
|
42
|
+
# def daemon
|
|
43
|
+
# do_something
|
|
44
|
+
# end
|
|
45
|
+
#
|
|
46
|
+
# The realtime application turns the Inform each_turn method
|
|
47
|
+
# into a synonym for daemon.
|
|
48
|
+
#
|
|
49
|
+
# Timers must also be supported.
|
|
50
|
+
#
|
|
51
|
+
# Object -> "blue box"
|
|
52
|
+
# with name 'blue' 'box',
|
|
53
|
+
# after [;
|
|
54
|
+
# SwitchOn: StartDaemon(self);
|
|
55
|
+
# SwitchOff: StopDaemon(self);
|
|
56
|
+
# ],
|
|
57
|
+
# daemon [;
|
|
58
|
+
# if (IndirectlyContains(location,self))
|
|
59
|
+
# "^An ominous ticking noise is coming from the box.";
|
|
60
|
+
# ],
|
|
61
|
+
# has switchable ~on;
|
|
62
|
+
#
|
|
63
|
+
# Object -> "red box"
|
|
64
|
+
# with name 'red' 'box',
|
|
65
|
+
# after [;
|
|
66
|
+
# SwitchOn: StartTimer(self,3);
|
|
67
|
+
# SwitchOff: StopTimer(self);
|
|
68
|
+
# ],
|
|
69
|
+
# time_out [;
|
|
70
|
+
# if (IndirectlyContains(location,self))
|
|
71
|
+
# print "^The box explodes into a zillion fragments.^";
|
|
72
|
+
# remove self;
|
|
73
|
+
# ],
|
|
74
|
+
# time_left 0,
|
|
75
|
+
# has switchable ~on;
|
|
76
|
+
#
|
|
77
|
+
|
|
78
|
+
# The Inform module
|
|
79
|
+
module Inform
|
|
80
|
+
def run_daemons
|
|
81
|
+
return if defined?(TURN_BASED)
|
|
82
|
+
Inform::Daemons.start
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# The Inform::Daemons module
|
|
86
|
+
module Daemons
|
|
87
|
+
def spawn
|
|
88
|
+
@spawn ||= defined?(Java) ? java.util.concurrent.ConcurrentSkipListSet.new : Set.new
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def heart
|
|
92
|
+
@heart ||= init_heart
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def init_heart
|
|
96
|
+
# TODO: Figure out ruby-concurrency Executors?
|
|
97
|
+
return unless defined?(Java)
|
|
98
|
+
java.util.concurrent.Executors.newSingleThreadScheduledExecutor()
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# The Inform::Daemons::Entheogen class encapsulates a heartbeat which
|
|
102
|
+
# is a ScheduledFuture created by the Heart executor and instantiated
|
|
103
|
+
# when Inform::Daemons.start is executed.
|
|
104
|
+
class Entheogen < Inform::System::Object
|
|
105
|
+
attr_accessor :heartbeat
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ghost
|
|
109
|
+
@ghost ||= Entheogen.new
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def schedules
|
|
113
|
+
@schedules ||= defined?(Java) ? java.util.concurrent.ConcurrentHashMap.new : {}
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def on?
|
|
117
|
+
return false if Inform::Daemons.ghost.heartbeat.nil?
|
|
118
|
+
Inform::Daemons.ghost.heartbeat.isCancelled() == false
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def rescan
|
|
122
|
+
Inform::Daemons.spawn.clear
|
|
123
|
+
Inform::Daemons.scan_objects
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
DaemonScanElapsedMessage = "Scanned %<daemons>s daemons in %<elapsed>0.2f milliseconds".freeze
|
|
127
|
+
|
|
128
|
+
def self.scan_objects
|
|
129
|
+
start = Time.now
|
|
130
|
+
Inform::Object.all.each do |obj|
|
|
131
|
+
Spawn.add obj if obj.respond_to?(:daemon) || obj.respond_to?(:each_turn)
|
|
132
|
+
end
|
|
133
|
+
rescue StandardError => e
|
|
134
|
+
log.error "Error executing daemon method: #{e.message}", e
|
|
135
|
+
ensure
|
|
136
|
+
log.debug format(DaemonScanElapsedMessage, daemons: Spawn.length, elapsed: Time.now - start)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# rubocop: disable Metrics/AbcSize
|
|
140
|
+
def self.start
|
|
141
|
+
initial_delay = Inform::Game.config[:daemon_initial_delay]
|
|
142
|
+
period = Inform::Game.config[:daemons_period]
|
|
143
|
+
unit = defined?(Java) ? java.util.concurrent.TimeUnit::MILLISECONDS : 'milliseconds'
|
|
144
|
+
Thread.new do
|
|
145
|
+
Inform::Daemons.rescan
|
|
146
|
+
log.debug "Starting daemons..."
|
|
147
|
+
daemons = Inform::Daemons.method(:daemons).to_proc
|
|
148
|
+
Inform::Daemons.ghost.heartbeat = Inform::Daemons.heart.scheduleAtFixedRate(
|
|
149
|
+
daemons, initial_delay, period, unit)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
# rubocop: enable Metrics/AbcSize
|
|
153
|
+
|
|
154
|
+
def self.stop
|
|
155
|
+
Inform::Daemons.ghost.heartbeat.cancel(false)
|
|
156
|
+
Inform::Daemons.spawn.clear
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
DaemonExecutionElapsedMessage = "\e[1m\e[33mExecuted daemons in %<elapsed>0.2f milliseconds\e[39m\e[22m".freeze
|
|
160
|
+
|
|
161
|
+
def self.daemons
|
|
162
|
+
start = Time.ms
|
|
163
|
+
Inform::Daemons.spawn.each do |daemon|
|
|
164
|
+
# In realtime games, each_turn is a synonym for daemon
|
|
165
|
+
next unless daemon.respond_to?(:daemon) || daemon.respond_to?(:each_turn)
|
|
166
|
+
Inform::Daemons.execute daemon
|
|
167
|
+
end
|
|
168
|
+
ensure
|
|
169
|
+
finish = Time.ms
|
|
170
|
+
elapsed = finish - start
|
|
171
|
+
log.debug format(DaemonExecutionElapsedMessage, elapsed: elapsed) if finish % 300_000 == 0
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
RecordNotFoundPattern = %r{Record not found}.freeze
|
|
175
|
+
|
|
176
|
+
def self.refresh_or_remove(obj)
|
|
177
|
+
return if obj.nil?
|
|
178
|
+
obj.refresh
|
|
179
|
+
rescue StandardError => e
|
|
180
|
+
Spawn.remove obj if RecordNotFoundPattern.match?(e.message)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def self.ensure_inform_library(obj)
|
|
184
|
+
return unless obj.inflib.nil?
|
|
185
|
+
# TODO: Use a pool of InformLibraries until
|
|
186
|
+
# comprehensive event contexts can be implemented
|
|
187
|
+
log.warn "Using singleton Ghost InformLibrary for #{obj} daemon"
|
|
188
|
+
obj.inflib = Inform::Runtime.libraries[Inform::Daemons.ghost]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# TODO: Potentially go ahead and print any string results
|
|
192
|
+
# from the daemon invocation. For example:
|
|
193
|
+
#
|
|
194
|
+
# obj.println obj.daemon
|
|
195
|
+
def self.execute_daemon(obj)
|
|
196
|
+
refresh_or_remove(obj)
|
|
197
|
+
ensure_inform_library(obj)
|
|
198
|
+
obj.daemon
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def self.execute_each_turn(obj)
|
|
202
|
+
refresh_or_remove(obj)
|
|
203
|
+
ensure_inform_library(obj)
|
|
204
|
+
obj.each_turn
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def self.execute(obj)
|
|
208
|
+
return if obj.nil?
|
|
209
|
+
if obj.respond_to? :daemon
|
|
210
|
+
execute_daemon(obj)
|
|
211
|
+
elsif obj.respond_to? :each_turn
|
|
212
|
+
execute_each_turn(obj)
|
|
213
|
+
end
|
|
214
|
+
rescue StandardError => e
|
|
215
|
+
log.error "Error executing daemon for #{obj}", e
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def StartDaemon(obj)
|
|
219
|
+
Inform::Daemons.spawn.add obj if obj.respond_to?(:daemon) || obj.respond_to?(:each_turn)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def StopDaemon(obj)
|
|
223
|
+
return if obj.nil?
|
|
224
|
+
log.warn "Stopping daemon: #{obj}"
|
|
225
|
+
obj.undef_method(:daemon) # TODO: Maybe don't do this
|
|
226
|
+
Inform::Daemons.spawn.remove obj
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def StartTimer(obj, interval)
|
|
230
|
+
# TODO: Implement
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def StopTimer(obj)
|
|
234
|
+
# TODO: Implement
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def daemonize(event)
|
|
238
|
+
Inform::Daemons.schedules[self] = event
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def daemonic?
|
|
242
|
+
Inform::Daemons.schedules.include? self
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def before_destroy
|
|
246
|
+
log.debug "#{self}: before_destroy stopping daemon"
|
|
247
|
+
StopDaemon(self) if daemonic?
|
|
248
|
+
super
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# rubocop: disable Metrics/AbcSize
|
|
252
|
+
def occasionally(time = nil, &block)
|
|
253
|
+
time = Inform::Game.config[:daemons_frequency_default] if time.nil?
|
|
254
|
+
return if active? || daemonic?
|
|
255
|
+
frequency = ((self.&:frequency) || time).to_i
|
|
256
|
+
occasion = rand(frequency) + 1
|
|
257
|
+
log.trace "Scheduling occasional daemonic event for #{self} in #{occasion} seconds"
|
|
258
|
+
(Inform::Daemons.schedules[self] = delay(occasion, &block)).finally do
|
|
259
|
+
Inform::Daemons.schedules.delete self
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
# rubocop: enable Metrics/AbcSize
|
|
264
|
+
# module Daemons
|
|
265
|
+
end
|
|
266
|
+
# module Inform
|