bevy 1.0.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.
- checksums.yaml +7 -0
- data/Cargo.lock +4279 -0
- data/Cargo.toml +36 -0
- data/README.md +226 -0
- data/crates/bevy/Cargo.toml +52 -0
- data/crates/bevy/src/app.rs +43 -0
- data/crates/bevy/src/component.rs +111 -0
- data/crates/bevy/src/entity.rs +30 -0
- data/crates/bevy/src/error.rs +32 -0
- data/crates/bevy/src/event.rs +190 -0
- data/crates/bevy/src/input_bridge.rs +300 -0
- data/crates/bevy/src/lib.rs +42 -0
- data/crates/bevy/src/mesh_renderer.rs +328 -0
- data/crates/bevy/src/query.rs +53 -0
- data/crates/bevy/src/render_app.rs +689 -0
- data/crates/bevy/src/resource.rs +28 -0
- data/crates/bevy/src/schedule.rs +186 -0
- data/crates/bevy/src/sprite_renderer.rs +355 -0
- data/crates/bevy/src/system.rs +44 -0
- data/crates/bevy/src/text_renderer.rs +258 -0
- data/crates/bevy/src/types/color.rs +114 -0
- data/crates/bevy/src/types/dynamic.rs +131 -0
- data/crates/bevy/src/types/math.rs +260 -0
- data/crates/bevy/src/types/mod.rs +9 -0
- data/crates/bevy/src/types/transform.rs +166 -0
- data/crates/bevy/src/world.rs +163 -0
- data/crates/bevy_ruby_render/Cargo.toml +22 -0
- data/crates/bevy_ruby_render/src/asset.rs +360 -0
- data/crates/bevy_ruby_render/src/audio.rs +511 -0
- data/crates/bevy_ruby_render/src/camera.rs +365 -0
- data/crates/bevy_ruby_render/src/gamepad.rs +398 -0
- data/crates/bevy_ruby_render/src/lib.rs +26 -0
- data/crates/bevy_ruby_render/src/material.rs +310 -0
- data/crates/bevy_ruby_render/src/mesh.rs +491 -0
- data/crates/bevy_ruby_render/src/sprite.rs +289 -0
- data/ext/bevy/Cargo.toml +20 -0
- data/ext/bevy/extconf.rb +6 -0
- data/ext/bevy/src/conversions.rs +137 -0
- data/ext/bevy/src/lib.rs +29 -0
- data/ext/bevy/src/ruby_app.rs +65 -0
- data/ext/bevy/src/ruby_color.rs +149 -0
- data/ext/bevy/src/ruby_component.rs +189 -0
- data/ext/bevy/src/ruby_entity.rs +33 -0
- data/ext/bevy/src/ruby_math.rs +384 -0
- data/ext/bevy/src/ruby_query.rs +64 -0
- data/ext/bevy/src/ruby_render_app.rs +779 -0
- data/ext/bevy/src/ruby_system.rs +122 -0
- data/ext/bevy/src/ruby_world.rs +107 -0
- data/lib/bevy/animation.rb +597 -0
- data/lib/bevy/app.rb +675 -0
- data/lib/bevy/asset.rb +613 -0
- data/lib/bevy/audio.rb +545 -0
- data/lib/bevy/audio_effects.rb +224 -0
- data/lib/bevy/camera.rb +412 -0
- data/lib/bevy/component.rb +91 -0
- data/lib/bevy/diagnostics.rb +227 -0
- data/lib/bevy/ecs_advanced.rb +296 -0
- data/lib/bevy/event.rb +199 -0
- data/lib/bevy/gizmos.rb +158 -0
- data/lib/bevy/gltf.rb +227 -0
- data/lib/bevy/hierarchy.rb +444 -0
- data/lib/bevy/input.rb +514 -0
- data/lib/bevy/lighting.rb +369 -0
- data/lib/bevy/material.rb +248 -0
- data/lib/bevy/mesh.rb +257 -0
- data/lib/bevy/navigation.rb +344 -0
- data/lib/bevy/networking.rb +335 -0
- data/lib/bevy/particle.rb +337 -0
- data/lib/bevy/physics.rb +396 -0
- data/lib/bevy/plugins/default_plugins.rb +34 -0
- data/lib/bevy/plugins/input_plugin.rb +49 -0
- data/lib/bevy/reflect.rb +361 -0
- data/lib/bevy/render_graph.rb +210 -0
- data/lib/bevy/resource.rb +185 -0
- data/lib/bevy/scene.rb +254 -0
- data/lib/bevy/shader.rb +319 -0
- data/lib/bevy/shape.rb +195 -0
- data/lib/bevy/skeletal.rb +248 -0
- data/lib/bevy/sprite.rb +152 -0
- data/lib/bevy/sprite_sheet.rb +444 -0
- data/lib/bevy/state.rb +277 -0
- data/lib/bevy/system.rb +206 -0
- data/lib/bevy/text.rb +99 -0
- data/lib/bevy/text_advanced.rb +455 -0
- data/lib/bevy/timer.rb +147 -0
- data/lib/bevy/transform.rb +158 -0
- data/lib/bevy/ui.rb +454 -0
- data/lib/bevy/ui_advanced.rb +568 -0
- data/lib/bevy/version.rb +5 -0
- data/lib/bevy/visibility.rb +250 -0
- data/lib/bevy/window.rb +302 -0
- data/lib/bevy.rb +390 -0
- metadata +150 -0
data/lib/bevy/asset.rb
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Bevy
|
|
4
|
+
module AssetState
|
|
5
|
+
NOT_LOADED = 'NotLoaded'
|
|
6
|
+
LOADING = 'Loading'
|
|
7
|
+
LOADED = 'Loaded'
|
|
8
|
+
FAILED = 'Failed'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Handle
|
|
12
|
+
attr_reader :id, :type_name, :path
|
|
13
|
+
|
|
14
|
+
def initialize(id:, type_name:, path: nil)
|
|
15
|
+
@id = id
|
|
16
|
+
@type_name = type_name
|
|
17
|
+
@path = path
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def strong?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def weak?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def ==(other)
|
|
29
|
+
return false unless other.is_a?(Handle)
|
|
30
|
+
|
|
31
|
+
@id == other.id && @type_name == other.type_name
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def eql?(other)
|
|
35
|
+
self == other
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def hash
|
|
39
|
+
[@id, @type_name].hash
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def to_s
|
|
43
|
+
"Handle<#{@type_name}>(#{@id})"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def inspect
|
|
47
|
+
"#<#{self.class} id=#{@id} type=#{@type_name} path=#{@path.inspect}>"
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class AssetServer
|
|
52
|
+
def initialize
|
|
53
|
+
@next_id = 0
|
|
54
|
+
@assets = {}
|
|
55
|
+
@handles = {}
|
|
56
|
+
@states = {}
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def load(path, type_name = nil)
|
|
60
|
+
type_name ||= infer_type(path)
|
|
61
|
+
return @handles[path] if @handles.key?(path)
|
|
62
|
+
|
|
63
|
+
id = generate_id
|
|
64
|
+
handle = Handle.new(id: id, type_name: type_name, path: path)
|
|
65
|
+
@handles[path] = handle
|
|
66
|
+
@states[id] = AssetState::NOT_LOADED
|
|
67
|
+
handle
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_async(path, type_name = nil)
|
|
71
|
+
handle = load(path, type_name)
|
|
72
|
+
@states[handle.id] = AssetState::LOADING
|
|
73
|
+
handle
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def get(handle)
|
|
77
|
+
@assets[handle.id]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def get_state(handle)
|
|
81
|
+
@states[handle.id] || AssetState::NOT_LOADED
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def loaded?(handle)
|
|
85
|
+
get_state(handle) == AssetState::LOADED
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def loading?(handle)
|
|
89
|
+
get_state(handle) == AssetState::LOADING
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def failed?(handle)
|
|
93
|
+
get_state(handle) == AssetState::FAILED
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def set_loaded(handle, asset)
|
|
97
|
+
@assets[handle.id] = asset
|
|
98
|
+
@states[handle.id] = AssetState::LOADED
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def set_failed(handle)
|
|
102
|
+
@states[handle.id] = AssetState::FAILED
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def get_handle(path)
|
|
106
|
+
@handles[path]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def all_handles
|
|
110
|
+
@handles.values
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def loaded_handles
|
|
114
|
+
@handles.values.select { |h| loaded?(h) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def pending_handles
|
|
118
|
+
@handles.values.select { |h| loading?(h) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def generate_id
|
|
124
|
+
id = @next_id
|
|
125
|
+
@next_id += 1
|
|
126
|
+
id
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def infer_type(path)
|
|
130
|
+
ext = File.extname(path).downcase
|
|
131
|
+
case ext
|
|
132
|
+
when '.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp'
|
|
133
|
+
'Image'
|
|
134
|
+
when '.ogg', '.wav', '.mp3', '.flac'
|
|
135
|
+
'AudioSource'
|
|
136
|
+
when '.ttf', '.otf'
|
|
137
|
+
'Font'
|
|
138
|
+
when '.gltf', '.glb'
|
|
139
|
+
'Gltf'
|
|
140
|
+
when '.json'
|
|
141
|
+
'JsonAsset'
|
|
142
|
+
when '.ron'
|
|
143
|
+
'RonAsset'
|
|
144
|
+
else
|
|
145
|
+
'Unknown'
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
class Assets
|
|
151
|
+
def initialize(type_name)
|
|
152
|
+
@type_name = type_name
|
|
153
|
+
@assets = {}
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def add(asset)
|
|
157
|
+
id = @assets.length
|
|
158
|
+
handle = Handle.new(id: id, type_name: @type_name)
|
|
159
|
+
@assets[id] = asset
|
|
160
|
+
handle
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def get(handle)
|
|
164
|
+
return nil unless handle.type_name == @type_name
|
|
165
|
+
|
|
166
|
+
@assets[handle.id]
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def get_mut(handle)
|
|
170
|
+
get(handle)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def remove(handle)
|
|
174
|
+
@assets.delete(handle.id)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def contains?(handle)
|
|
178
|
+
@assets.key?(handle.id)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def iter
|
|
182
|
+
@assets.map { |id, asset| [Handle.new(id: id, type_name: @type_name), asset] }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def each(&block)
|
|
186
|
+
iter.each(&block)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def len
|
|
190
|
+
@assets.length
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def empty?
|
|
194
|
+
@assets.empty?
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
class AssetPath
|
|
199
|
+
attr_reader :path, :label
|
|
200
|
+
|
|
201
|
+
def initialize(path, label: nil)
|
|
202
|
+
@path = path
|
|
203
|
+
@label = label
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def self.parse(asset_path)
|
|
207
|
+
if asset_path.include?('#')
|
|
208
|
+
path, label = asset_path.split('#', 2)
|
|
209
|
+
new(path, label: label)
|
|
210
|
+
else
|
|
211
|
+
new(asset_path)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def full_path
|
|
216
|
+
@label ? "#{@path}##{@label}" : @path
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def extension
|
|
220
|
+
File.extname(@path).delete_prefix('.')
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def file_name
|
|
224
|
+
File.basename(@path)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def parent
|
|
228
|
+
File.dirname(@path)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def ==(other)
|
|
232
|
+
return false unless other.is_a?(AssetPath)
|
|
233
|
+
|
|
234
|
+
@path == other.path && @label == other.label
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def to_s
|
|
238
|
+
full_path
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
class AssetEvent
|
|
243
|
+
attr_reader :type, :handle
|
|
244
|
+
|
|
245
|
+
CREATED = :created
|
|
246
|
+
MODIFIED = :modified
|
|
247
|
+
REMOVED = :removed
|
|
248
|
+
LOADED = :loaded
|
|
249
|
+
FAILED = :failed
|
|
250
|
+
|
|
251
|
+
def initialize(type, handle)
|
|
252
|
+
@type = type
|
|
253
|
+
@handle = handle
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def created?
|
|
257
|
+
@type == CREATED
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def modified?
|
|
261
|
+
@type == MODIFIED
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def removed?
|
|
265
|
+
@type == REMOVED
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def loaded?
|
|
269
|
+
@type == LOADED
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def failed?
|
|
273
|
+
@type == FAILED
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
class AssetLoader
|
|
278
|
+
attr_reader :extensions
|
|
279
|
+
|
|
280
|
+
def initialize(extensions: [])
|
|
281
|
+
@extensions = extensions
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def load(path)
|
|
285
|
+
raise NotImplementedError, 'Subclasses must implement #load'
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def can_load?(path)
|
|
289
|
+
ext = File.extname(path).delete_prefix('.').downcase
|
|
290
|
+
@extensions.include?(ext)
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
class ImageAsset
|
|
295
|
+
attr_reader :path, :width, :height, :data
|
|
296
|
+
|
|
297
|
+
def initialize(path:, width: 0, height: 0, data: nil)
|
|
298
|
+
@path = path
|
|
299
|
+
@width = width
|
|
300
|
+
@height = height
|
|
301
|
+
@data = data
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def loaded?
|
|
305
|
+
!@data.nil?
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def aspect_ratio
|
|
309
|
+
return 1.0 if @height.zero?
|
|
310
|
+
|
|
311
|
+
@width.to_f / @height.to_f
|
|
312
|
+
end
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
class FontAsset
|
|
316
|
+
attr_reader :path, :family
|
|
317
|
+
|
|
318
|
+
def initialize(path:, family: nil)
|
|
319
|
+
@path = path
|
|
320
|
+
@family = family || File.basename(path, '.*')
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
class AudioAsset
|
|
325
|
+
attr_reader :path, :duration
|
|
326
|
+
|
|
327
|
+
def initialize(path:, duration: 0.0)
|
|
328
|
+
@path = path
|
|
329
|
+
@duration = duration
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
class ImageLoader < AssetLoader
|
|
334
|
+
def initialize
|
|
335
|
+
super(extensions: %w[png jpg jpeg gif bmp webp])
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def load(path)
|
|
339
|
+
return nil unless File.exist?(path)
|
|
340
|
+
|
|
341
|
+
ImageAsset.new(path: path)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
class FontLoader < AssetLoader
|
|
346
|
+
def initialize
|
|
347
|
+
super(extensions: %w[ttf otf woff woff2])
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def load(path)
|
|
351
|
+
return nil unless File.exist?(path)
|
|
352
|
+
|
|
353
|
+
FontAsset.new(path: path)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
class AudioLoader < AssetLoader
|
|
358
|
+
def initialize
|
|
359
|
+
super(extensions: %w[ogg wav mp3 flac])
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def load(path)
|
|
363
|
+
return nil unless File.exist?(path)
|
|
364
|
+
|
|
365
|
+
AudioAsset.new(path: path)
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
class AssetManager
|
|
370
|
+
def initialize
|
|
371
|
+
@asset_server = AssetServer.new
|
|
372
|
+
@loaders = {
|
|
373
|
+
'Image' => ImageLoader.new,
|
|
374
|
+
'Font' => FontLoader.new,
|
|
375
|
+
'AudioSource' => AudioLoader.new
|
|
376
|
+
}
|
|
377
|
+
@hot_reload_enabled = false
|
|
378
|
+
@file_watcher = FileWatcher.new
|
|
379
|
+
@reload_callbacks = {}
|
|
380
|
+
@dependencies = {}
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
attr_reader :asset_server
|
|
384
|
+
attr_reader :hot_reload_enabled
|
|
385
|
+
|
|
386
|
+
def register_loader(type_name, loader)
|
|
387
|
+
@loaders[type_name] = loader
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
def enable_hot_reload
|
|
391
|
+
@hot_reload_enabled = true
|
|
392
|
+
end
|
|
393
|
+
|
|
394
|
+
def disable_hot_reload
|
|
395
|
+
@hot_reload_enabled = false
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def on_reload(handle, &callback)
|
|
399
|
+
@reload_callbacks[handle.id] ||= []
|
|
400
|
+
@reload_callbacks[handle.id] << callback
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
def add_dependency(asset_handle, dependency_handle)
|
|
404
|
+
@dependencies[asset_handle.id] ||= []
|
|
405
|
+
@dependencies[asset_handle.id] << dependency_handle.id
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def load(path)
|
|
409
|
+
handle = @asset_server.load(path)
|
|
410
|
+
type_name = handle.type_name
|
|
411
|
+
|
|
412
|
+
if @loaders.key?(type_name)
|
|
413
|
+
loader = @loaders[type_name]
|
|
414
|
+
asset = loader.load(path)
|
|
415
|
+
|
|
416
|
+
if asset
|
|
417
|
+
@asset_server.set_loaded(handle, asset)
|
|
418
|
+
@file_watcher.watch(path) if @hot_reload_enabled
|
|
419
|
+
else
|
|
420
|
+
@asset_server.set_failed(handle)
|
|
421
|
+
end
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
handle
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def load_async(path, &callback)
|
|
428
|
+
handle = @asset_server.load_async(path)
|
|
429
|
+
|
|
430
|
+
Thread.new do
|
|
431
|
+
type_name = handle.type_name
|
|
432
|
+
|
|
433
|
+
if @loaders.key?(type_name)
|
|
434
|
+
loader = @loaders[type_name]
|
|
435
|
+
asset = loader.load(path)
|
|
436
|
+
|
|
437
|
+
if asset
|
|
438
|
+
@asset_server.set_loaded(handle, asset)
|
|
439
|
+
@file_watcher.watch(path) if @hot_reload_enabled
|
|
440
|
+
callback&.call(handle, :loaded)
|
|
441
|
+
else
|
|
442
|
+
@asset_server.set_failed(handle)
|
|
443
|
+
callback&.call(handle, :failed)
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
handle
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
def reload(handle)
|
|
452
|
+
path = handle.path
|
|
453
|
+
return false unless path
|
|
454
|
+
|
|
455
|
+
type_name = handle.type_name
|
|
456
|
+
return false unless @loaders.key?(type_name)
|
|
457
|
+
|
|
458
|
+
loader = @loaders[type_name]
|
|
459
|
+
asset = loader.load(path)
|
|
460
|
+
|
|
461
|
+
if asset
|
|
462
|
+
@asset_server.set_loaded(handle, asset)
|
|
463
|
+
notify_reload(handle)
|
|
464
|
+
reload_dependents(handle)
|
|
465
|
+
true
|
|
466
|
+
else
|
|
467
|
+
false
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def check_for_changes
|
|
472
|
+
return [] unless @hot_reload_enabled
|
|
473
|
+
|
|
474
|
+
changes = @file_watcher.check_changes
|
|
475
|
+
reloaded = []
|
|
476
|
+
|
|
477
|
+
changes.each do |path, change_type|
|
|
478
|
+
handle = @asset_server.get_handle(path)
|
|
479
|
+
next unless handle
|
|
480
|
+
|
|
481
|
+
case change_type
|
|
482
|
+
when :modified
|
|
483
|
+
if reload(handle)
|
|
484
|
+
reloaded << AssetEvent.new(AssetEvent::MODIFIED, handle)
|
|
485
|
+
end
|
|
486
|
+
when :deleted
|
|
487
|
+
reloaded << AssetEvent.new(AssetEvent::REMOVED, handle)
|
|
488
|
+
end
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
reloaded
|
|
492
|
+
end
|
|
493
|
+
|
|
494
|
+
def get(handle)
|
|
495
|
+
@asset_server.get(handle)
|
|
496
|
+
end
|
|
497
|
+
|
|
498
|
+
def loaded?(handle)
|
|
499
|
+
@asset_server.loaded?(handle)
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def loading?(handle)
|
|
503
|
+
@asset_server.loading?(handle)
|
|
504
|
+
end
|
|
505
|
+
|
|
506
|
+
def failed?(handle)
|
|
507
|
+
@asset_server.failed?(handle)
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def get_state(handle)
|
|
511
|
+
@asset_server.get_state(handle)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def unload(handle)
|
|
515
|
+
@file_watcher.unwatch(handle.path) if handle.path
|
|
516
|
+
@reload_callbacks.delete(handle.id)
|
|
517
|
+
@dependencies.delete(handle.id)
|
|
518
|
+
end
|
|
519
|
+
|
|
520
|
+
private
|
|
521
|
+
|
|
522
|
+
def notify_reload(handle)
|
|
523
|
+
callbacks = @reload_callbacks[handle.id]
|
|
524
|
+
return unless callbacks
|
|
525
|
+
|
|
526
|
+
callbacks.each { |cb| cb.call(handle) }
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
def reload_dependents(handle)
|
|
530
|
+
@dependencies.each do |asset_id, deps|
|
|
531
|
+
next unless deps.include?(handle.id)
|
|
532
|
+
|
|
533
|
+
dependent_handle = find_handle_by_id(asset_id)
|
|
534
|
+
reload(dependent_handle) if dependent_handle
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
def find_handle_by_id(id)
|
|
539
|
+
@asset_server.all_handles.find { |h| h.id == id }
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
class FileWatcher
|
|
544
|
+
def initialize
|
|
545
|
+
@watched = {}
|
|
546
|
+
@poll_interval = 1.0
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
attr_accessor :poll_interval
|
|
550
|
+
|
|
551
|
+
def watch(path)
|
|
552
|
+
return unless File.exist?(path)
|
|
553
|
+
|
|
554
|
+
@watched[path] = File.mtime(path)
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
def unwatch(path)
|
|
558
|
+
@watched.delete(path)
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def check_changes
|
|
562
|
+
changes = []
|
|
563
|
+
to_remove = []
|
|
564
|
+
|
|
565
|
+
@watched.each do |path, last_mtime|
|
|
566
|
+
if File.exist?(path)
|
|
567
|
+
current_mtime = File.mtime(path)
|
|
568
|
+
if current_mtime > last_mtime
|
|
569
|
+
changes << [path, :modified]
|
|
570
|
+
@watched[path] = current_mtime
|
|
571
|
+
end
|
|
572
|
+
else
|
|
573
|
+
changes << [path, :deleted]
|
|
574
|
+
to_remove << path
|
|
575
|
+
end
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
to_remove.each { |p| @watched.delete(p) }
|
|
579
|
+
changes
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def watched_count
|
|
583
|
+
@watched.size
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
def watching?(path)
|
|
587
|
+
@watched.key?(path)
|
|
588
|
+
end
|
|
589
|
+
end
|
|
590
|
+
|
|
591
|
+
class HotReloadPlugin
|
|
592
|
+
def initialize(poll_interval: 1.0)
|
|
593
|
+
@poll_interval = poll_interval
|
|
594
|
+
@last_check = ::Time.now
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def build(app)
|
|
598
|
+
app.asset_manager.enable_hot_reload
|
|
599
|
+
app.asset_manager.instance_variable_get(:@file_watcher).poll_interval = @poll_interval
|
|
600
|
+
end
|
|
601
|
+
|
|
602
|
+
def update(app, _delta)
|
|
603
|
+
now = ::Time.now
|
|
604
|
+
return if now - @last_check < @poll_interval
|
|
605
|
+
|
|
606
|
+
@last_check = now
|
|
607
|
+
events = app.asset_manager.check_for_changes
|
|
608
|
+
events.each do |event|
|
|
609
|
+
puts "[HotReload] #{event.type}: #{event.handle.path}" if ENV['BEVY_DEBUG']
|
|
610
|
+
end
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
end
|