/*] Copyright (c) 2009-2010, Charles McGarvey [************************** **] All rights reserved. * * vi:ts=4 sw=4 tw=75 * * Distributable under the terms and conditions of the 2-clause BSD license; * see the file COPYING for a complete text of the license. * **************************************************************************/ #include "config.h" #include #if ENABLE_HOTLOADING #include #include #endif #include #include #include #include "hash.hh" #include "resource.hh" #ifndef BUF_SIZE #define BUF_SIZE 4096 #endif namespace moof { /*] Filesystem searching *************************************************************************/ static std::vector search_paths_; void resource::set_search_paths(const std::string& paths) { boost::split(search_paths_, paths, boost::is_any_of(":")); } std::string resource::find_file(const std::string& name) { std::string ext = stlplus::extension_part(name); std::string prefix; std::string path; loader_ptr loader; call_registry(ext, loader, lookup); if (loader) prefix = loader->prefix(); std::vector::iterator it; for (it = search_paths_.begin(); it != search_paths_.end(); ++it) { if (!prefix.empty()) { // try it with the prefix first path = stlplus::filespec_to_path(*it, prefix); path = stlplus::filespec_to_path(path, name); log_debug("looking for", name, "at", path); if (stlplus::file_exists(path)) return path; } path = stlplus::filespec_to_path(*it, name); log_debug("looking for", name, "at", path); if (stlplus::file_exists(path)) return path; } log_error("cannot find resource file:", name); return std::string(); } std::string resource::find_file(const std::string& name, const std::string& ext) { std::string actual_ext = stlplus::extension_part(name); if (actual_ext != ext) { // try the explicit extension first return find_file(stlplus::create_filename(name, ext)); } return find_file(name); } /*] Resource loading *************************************************************************/ typedef boost::weak_ptr resource_weakptr; static struct rsrc_list { // this table holds weak references to any and all loaded resources hash table; #ifdef DEBUG // this destructor will let us know if the program exits while // resources are still being held ~rsrc_list() { hash::iterator it; for (it = table.begin(); it != table.end(); ++it) { log_warning("leaked resource:", (*it).first); } } #endif } rsrc_list; #if ENABLE_HOTLOADING static struct watch_list { // this table associates a watch descriptor with a loaded resource hash table; int fd; // the inotify file descriptor watch_list() : fd(inotify_init1(IN_NONBLOCK)) {} ~watch_list() { close(fd); } int add(resource_ptr rsrc) { int wd = inotify_add_watch(fd, rsrc->path().c_str(), IN_DELETE_SELF | IN_MODIFY); table[wd] = rsrc; return wd; } void remove(int wd) { if (wd != -1) inotify_rm_watch(fd, wd); } } watch_list; #endif resource_ptr resource::load(const std::string& name) { std::string ext = stlplus::extension_part(name); return load_with_path(find_file(name, ext), ext); } resource_ptr resource::load(const std::string& name, const std::string& ext) { return load_with_path(find_file(name, ext), ext); } resource_ptr resource::load_with_path(const std::string& path, const std::string& ext) { if (path.empty()) return resource_ptr(); // first try to lookup the resource hash::iterator it; it = rsrc_list.table.find(path); if (it != rsrc_list.table.end()) { resource_ptr rsrc = (*it).second.lock(); if (rsrc) return rsrc; } // then proceed to use the registered loader to get the resource loader_ptr loader; call_registry(ext, loader, lookup); if (loader) { resource_ptr rsrc(loader->load(path)); rsrc_list.table[path] = rsrc; rsrc->path_ = path; rsrc->type_ = ext; #if ENABLE_HOTLOADING rsrc->wd_ = watch_list.add(rsrc); #endif return rsrc; } log_warning("cannot load resource of unregistered type:", path); return resource_ptr(); } /*] Hotloading *************************************************************************/ int resource::reload_as_needed() { int count = 0; #if ENABLE_HOTLOADING char bytes[BUF_SIZE]; int num_bytes; // an inotify file descriptor lets your read inotify_event structures if (0 < (num_bytes = read(watch_list.fd, bytes, sizeof(bytes)))) { char* end = bytes + num_bytes; char* byte = bytes; while (byte < end) { struct inotify_event* event = (struct inotify_event*)byte; byte += sizeof(*event) + event->len; hash::iterator it; it = watch_list.table.find(event->wd); if (it != watch_list.table.end()) { resource_ptr rsrc = (*it).second.lock(); if (rsrc) { rsrc->reload(); ++count; if (event->mask & IN_IGNORED) { watch_list.table.erase(event->wd); rsrc->wd_ = watch_list.add(rsrc); } } else { // if we can't lock the resource, it was unloaded so we // don't need to watch it anymore watch_list.table.erase(event->wd); } } } } #endif return count; } void resource::reload() { loader_ptr loader; call_registry(type_, loader, lookup); resource_ptr resource(loader->load(path_)); resource_ = resource->resource_; typeinfo_ = resource->typeinfo_; unloader_ = resource->unloader_; } resource::~resource() { rsrc_list.table.erase(path_); #if ENABLE_HOTLOADING watch_list.remove(wd_); #endif } /*] Resource registration *************************************************************************/ bool resource::call_registry(const std::string& ext, loader_ptr& loader, registry_action action) { static std::map table; switch (action) { case set: { if (loader) table[ext] = loader; else table.erase(ext); break; } case lookup: { std::map::iterator it; it = table.find(ext); if (it != table.end()) loader = (*it).second; break; } } return loader; } } // namespace moof