gutter_runner/game/assets/watcher_desktop.odin

149 lines
3.7 KiB
Odin

#+build !js
package assets
import "base:runtime"
import "common:name"
import "core:log"
import "core:sync/chan"
import "core:thread"
import "libs:physfs"
import "libs:tracy"
ASSET_WATCHER_OPS_BUFFER :: 256
// Add asset to watch list
Asset_Watcher_Op_Add :: struct {
type: Asset_Type,
path: name.Name,
modtime: physfs.sint64,
}
// Remove asset from watch list
Asset_Watcher_Op_Remove :: struct {
type: Asset_Type,
path: name.Name,
}
Asset_Watcher_Op :: union #no_nil {
Asset_Watcher_Op_Add,
Asset_Watcher_Op_Remove,
}
Asset_Modtime_Watcher :: struct {
ops: chan.Chan(Asset_Watcher_Op),
modified_assets: chan.Chan(Watcher_Asset),
loaded_assets: [dynamic]Watcher_Asset,
thread: ^thread.Thread,
}
modtime_watcher_init :: proc(watcher: ^Asset_Modtime_Watcher, allocator := context.allocator) {
err: runtime.Allocator_Error
watcher.ops, err = chan.create_buffered(
chan.Chan(Asset_Watcher_Op),
ASSET_WATCHER_OPS_BUFFER,
allocator,
)
assert(err == nil)
watcher.modified_assets, err = chan.create_buffered(
chan.Chan(Watcher_Asset),
ASSET_WATCHER_OPS_BUFFER,
allocator,
)
watcher.loaded_assets = make_dynamic_array([dynamic]Watcher_Asset, allocator)
watcher.thread = thread.create(modtime_watcher_thread_proc)
watcher.thread.data = watcher
watcher_context := runtime.default_context()
watcher_context.logger = context.logger
watcher_context.allocator = context.allocator
watcher.thread.init_context = watcher_context
thread.start(watcher.thread)
}
modtime_watcher_deinit :: proc(watcher: ^Asset_Modtime_Watcher) {
if !chan.is_closed(&watcher.ops) {
chan.close(&watcher.ops)
thread.join(watcher.thread)
watcher.thread = nil
}
chan.destroy(&watcher.ops)
chan.close(&watcher.modified_assets)
chan.destroy(&watcher.modified_assets)
delete(watcher.loaded_assets)
}
modtime_watcher_next :: proc(watcher: ^Asset_Modtime_Watcher) -> (asset: Watcher_Asset, ok: bool) {
return chan.try_recv(watcher.modified_assets)
}
@(private = "file")
modtime_watcher_thread_proc :: proc(t: ^thread.Thread) {
tracy.SetThreadName("Asset Watcher")
watcher := cast(^Asset_Modtime_Watcher)t.data
log.debugf("watcher thread")
for !chan.is_closed(&watcher.ops) {
for recv_op in chan.try_recv(watcher.ops) {
switch op in recv_op {
case Asset_Watcher_Op_Add:
log.debugf("add [{}] {}", op.type, name.to_string(op.path))
append(
&watcher.loaded_assets,
Watcher_Asset{type = op.type, path = op.path, modtime = op.modtime},
)
case Asset_Watcher_Op_Remove:
log.debugf("remove [{}] {}", op.type, name.to_string(op.path))
i := 0
for i < len(watcher.loaded_assets) {
if op.path == watcher.loaded_assets[i].path &&
op.type == watcher.loaded_assets[i].type {
unordered_remove(&watcher.loaded_assets, i)
} else {
i += 1
}
}
}
}
for &asset in watcher.loaded_assets {
modtime := physfs.getLastModTime(name.to_cstring(asset.path))
if asset.modtime != modtime {
log.debugf("change [{}] {}", asset.type, name.to_string(asset.path))
ok := chan.send(
watcher.modified_assets,
Watcher_Asset{type = asset.type, path = asset.path, modtime = modtime},
)
assert(ok)
}
asset.modtime = modtime
}
// To avoid busy loop just in case
thread.yield()
}
}
modtime_watcher_add_asset :: proc(
watcher: ^Asset_Modtime_Watcher,
type: Asset_Type,
path: name.Name,
modtime: physfs.sint64,
) -> bool {
return chan.send(
watcher.ops,
Asset_Watcher_Op_Add{type = type, path = path, modtime = modtime},
)
}
modtime_watcher_remove_asset :: proc(
watcher: ^Asset_Modtime_Watcher,
type: Asset_Type,
path: name.Name,
) -> bool {
return chan.send(watcher.ops, Asset_Watcher_Op_Remove{type = type, path = path})
}