Last active
October 21, 2016 00:44
-
-
Save SeijiEmery/28361dfe9ef8c148a3c8 to your computer and use it in GitHub Desktop.
ResourceLayer example (for an internal project)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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") | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.