-/*] Copyright (c) 2009-2010, Charles McGarvey [**************************
+/*] Copyright (c) 2009-2011, 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.
*
-**************************************************************************/
+*****************************************************************************/
+
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <queue>
#include <boost/algorithm/string.hpp>
+#include <boost/weak_ptr.hpp>
+
+#include <stlplus/portability/file_system.hpp>
+#if ENABLE_HOTLOADING
+#include <sys/inotify.h>
+#include <sys/ioctl.h>
+#endif
+
+#include "hash.hh"
#include "log.hh"
#include "resource.hh"
+#ifndef BUF_SIZE
+#define BUF_SIZE 8192
+#endif
+
namespace moof {
-// static member
-std::vector<std::string> resource::search_paths_;
+/*] Filesystem searching
+ *************************************************************************/
+static std::vector<std::string> search_paths_;
-void resource::add_search_paths(const std::string& paths)
+void resource::set_search_paths(const std::string& paths)
{
- std::vector<std::string> pathList;
- boost::split(pathList, paths, boost::is_any_of(":"));
+ 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<std::string>::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;
+ }
- add_search_paths(pathList);
+ 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();
}
-void resource::add_search_paths(const std::vector<std::string>& pathList)
+std::string
+resource::find_file(const std::string& name, const std::string& ext)
{
- std::vector<std::string>::const_iterator it;
- for (it = pathList.begin(); it != pathList.end(); ++it)
+ std::string actual_ext = stlplus::extension_part(name);
+ if (actual_ext != ext)
{
- std::string path(*it);
+ // try the explicit extension first
+ return find_file(stlplus::create_filename(name, ext));
+ }
+ return find_file(name);
+}
+
- ASSERT(!path.empty() && "empty search path string");
+/*] Resource loading
+ *************************************************************************/
- // add a slash if there isn't one already
- if (*path.rbegin() != '/') path += '/';
+typedef boost::weak_ptr<resource> resource_weakptr;
+static struct rsrc_list
+{
+ // this table holds weak references to any and all loaded resources
+ hash<std::string,resource_weakptr,hash_function> table;
-#if defined(_WIN32)
- boost::replace_all(path, "/", "\\");
+#ifdef DEBUG
+ // this destructor will let us know if the program exits while
+ // resources are still being held
+ ~rsrc_list()
+ {
+ hash<std::string,resource_weakptr,hash_function>::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<int,resource_weakptr,hash_function> table;
- search_paths_.push_back(path);
- log_info << "added search path " << path << std::endl;
+ 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);
+}
-bool resource::find_path(std::string& path,
- const std::string& prefix,
- const std::string& extension)
+resource_ptr
+resource::load_with_path(const std::string& path, const std::string& ext)
{
- FILE* file = open_file(path, prefix, extension);
- if (file)
+ if (path.empty()) return resource_ptr();
+
+ // first try to lookup the resource
+ hash<std::string,resource_weakptr,hash_function>::iterator it;
+ it = rsrc_list.table.find(path);
+ if (it != rsrc_list.table.end())
{
- fclose(file);
- return true;
+ resource_ptr rsrc = (*it).second.lock();
+ if (rsrc) return rsrc;
}
- return false;
+ // 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();
}
-FILE* resource::open_file(std::string& path,
- const std::string& prefix,
- const std::string& extension,
- const std::string& mode)
-{
-#if defined(_WIN32)
- // windows always has to be a little different
- boost::replace_all(path, "/", "\\");
- std::string temp_prefix(prefix);
- boost::replace_all(temp_prefix, "/", "\\");
- std::vector<std::string> preList;
- boost::split(preList, temp_prefix, boost::is_any_of(":"));
-#else
- std::vector<std::string> preList;
- boost::split(preList, prefix, boost::is_any_of(":"));
-#endif
+/*] Hotloading
+ *************************************************************************/
- std::vector<std::string> postList;
- boost::split(postList, extension, boost::is_any_of(":"));
+int resource::reload_as_needed()
+{
+ int count = 0;
- std::vector<std::string>::iterator it;
- for (it = search_paths_.begin(); it != search_paths_.end(); ++it)
+#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))))
{
- std::vector<std::string>::iterator jt;
- for (jt = preList.begin(); jt != preList.end(); ++jt)
+ char* end = bytes + num_bytes;
+ char* byte = bytes;
+
+ while (byte < end)
{
- std::vector<std::string>::iterator kt;
- for (kt = postList.begin(); kt != postList.end(); ++kt)
+ struct inotify_event* event = (struct inotify_event*)byte;
+ byte += sizeof(*event) + event->len;
+
+ hash<int,resource_weakptr,hash_function>::iterator it;
+ it = watch_list.table.find(event->wd);
+ if (it != watch_list.table.end())
{
- std::string realPath(*it);
- realPath += *jt;
- realPath += path;
- realPath += ".";
- realPath += *kt;
-
- FILE* file = fopen(realPath.c_str(), mode.c_str());
- if (file)
+ 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
{
- path = realPath;
- return file;
+ // 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;
+}
- // check path relative to search path
- std::string realPath(*it);
- realPath += path;
+void resource::reload()
+{
+ loader_ptr loader;
+ call_registry(type_, loader, lookup);
- FILE* file = fopen(realPath.c_str(), mode.c_str());
- if (file)
- {
- path = realPath;
- return file;
- }
+ 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<std::string,loader_ptr> table;
+
+ switch (action)
+ {
+ case lookup:
+ {
+ std::map<std::string,loader_ptr>::iterator it;
+ it = table.find(ext);
+ if (it != table.end()) loader = (*it).second;
+ break;
+ }
+
+ case set:
+ if (loader)
+ table[ext] = loader;
+ else
+ table.erase(ext);
+ break;
}
- // last ditch effort; maybe it's already a path to a valid resource
- return fopen(path.c_str(), mode.c_str());
+ return loader;
}