Skip to content

Instantly share code, notes, and snippets.

@SeijiEmery
Last active October 21, 2016 00:44
Show Gist options
  • Save SeijiEmery/28361dfe9ef8c148a3c8 to your computer and use it in GitHub Desktop.
Save SeijiEmery/28361dfe9ef8c148a3c8 to your computer and use it in GitHub Desktop.
ResourceLayer example (for an internal project)
// Note on used types:
// - ResourceLayer is a threadsafe singleton class with a bunch of static methods
// - ResourceLayer calls return a Resource*******Request temp object, which uses a
// bunch of declarative chained method calls to set its state. The destructor (or
// an explicit call) launches the request.
// - TextureHandle wraps / owns a gl texture (the implementation is not important)
// - TextureHandleRef is a std::shared_ptr<TextureHandle>
// - There's some implicit path stuff (PROJECT_DIR is a path object; PROJECT_DIR + string => new path).
//
// Also, architecturally we're running in an environment with multiple threads, including
// a graphics and event / main thread (which may or may not be the same), but come
// with the assumption that certain subsystems may only be called from certain threads
// (eg. gl calls may only be called from the graphics thread).
void setup () {
// add rule for loading texture files from the main directory
ResourceLayer::createPrefix()
.inDirectory(PROJECT_DIR + "/assets/")
.forFileExts(".jpg", ".png")
.watchForAndNotifyFileChanges()
.reloadWhenFileSizeOrHashChanges();
// This creates a resource prefix (which may / may not have an actual path prefix like 'assets:/'),
// and is basically a set of rules for translating internal paths to filesystem paths (this one is
// basically '[^/].*[(\.jpg)|(\.png)]' => PROJECT_DIR/assets/... => /Users/semery/projects/GLSandbox/assets/...),
// along with a bunch of semantic data how we load files and dispatch events.
//
// .inDirectory() and .forFileExts() are pretty straightforward, but .watchForAndNotifyFileChanges()
// tells the resource layer to also watch these files for filesystem events, and .notifyWhenFileSizeOrHashChanges()
// signals that we want all file load requests on these files to be re-run when the condition (file size
// or file hash changes) occurs, which means we get automatic file reloading (see below).
//
// On osx (the only platform I'm interested in supporting atm, though for linux I'd just swap out fsevents
// for inotify), all the above means that we need to:
// - Use the fsevents api to create a file watcher on PROJECT_DIR/assets; thankfully it is recursive unlike inotify
// - Create a thread + CFRunLoop to run the file watchers (we only do this once for all ResourceLayer stuff)
// (note: the CoreFoundation library, which is written for objc but totally usable from plain c and c++,
// is a _really_ well written high-level c api, and actually easier to use in xcode than c++ xD)
// - Also scan these dirs with lstat, and cache a bunch of file info (fsevents tells you that something changed,
// not _what_ changed, just like inotify)
// - Also hash files that we get a load request for, though we do this lazily and short-circuit w/ a file
// size check first (hence 'WhenFileSizeOrHashChanges').
// - Store lists of file requests so we can submit reloads when qualifying file changes happen, and optimize
// this and all of the above so it runs fast.
}
TextureHandleRef loadTexture (std::string texture_file) {
texture_handle = some_gl_call();
load_default_checkered_texture(texture_handle);
// Create a file load request: load the file asynchronously if it exists, or log an error if it doesn't.
ResourceLayer::loadFileAsBuffer(texture_file)
.whenLoaded(RUN_ON_GRAPHICS_THREAD, [=](TempFileBuffer & buffer) {
// upload texture using buffer.bytes (uint8_t), buffer.size (size_t)
})
.ifNotAvailable(RUN_ON_EVENT_THREAD, [=](auto & file_name) {
getLog(ASSET_LOG).logError() << "Failed to load '" << file_name << "'\n";
});
return std::make_shared<TextureHandle>(texture_handle);
// What's neat about this:
// - Async calls can get be requested to run on a specific thread, which means we don't have to use locks
// most of the time. Each thread (including the main thread) has an event loop and a queue of
// closures (std::function<void()>) which can run any arbitrary work.
// - whenLoaded gets called when the file is first loaded (iff available), AND whenever the file
// is changed (timestamp + file size or file hash changes), which means textures will get automatically
// reloaded when they are changed on disk.
// - ifNotAvailable gets called if the file doesn't exist; we could use whenNotAvailable for if the file
// does not exist _and_ if the file is ever deleted (which we can ignore since we'd already have it
// loaded by then). There's also whenDeleted, etc
// - the exact semantics are controlled by the resource prefix
}
// Somewhere on the graphics thread...
auto myTexture = loadTexture("foo.png")
@SeijiEmery
Copy link
Author

Note: this is fairly old, and was my original concept for a multithreaded, reload-files-when-changed ResourceManager framework, which would have handled texture, model, shader, and script loading in a pretty high-level manner (and implicitly supported automatic hotloading and piped operations across multiple threads, which would've been a pretty nice feature).

This was for a personal project (gsb), and was written when said project was still written in c++ and called GLSandbox (I switched to D b/c compile times were horrible and stuff like split header/src files was super annoying); the concept has fallen by the wayside since then, but I might revive it at some point when/if I get back to gsb.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment