#+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) thread.destroy(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}) }