Jump to content
  • Advertisement


  • Content Count

  • Joined

  • Last visited

Community Reputation

311 Neutral

About occash

  • Rank

Personal Information

  • Interests
  1. Thank you all for comments!   Josh Petrie, Thank you for your suggestions on how to improve the article. I will try to modify article and code accordingly. I have only a few remarks. One still have to build GCCXML and resolve all errors, what can be annoying. uMOF is designed to be independant from external tools. Type modifiers only omitted for utility class Any. This doesn't break a class invariant.   Servant of the Lord, Thank you for your remarks. I have corrected the readme on bitbucket page. Certainly I will add the prefix. Unfortunately uMOF can't handle namespaces and globals. uMOF exposes classes, not instances.   SeanMiddleditch, Thank you, I really like your solution!   augus1990, The question is still yet to be answered. uMOF can't help with garbage collection.   syskrank, Thank you. I agree that premake is unnesesary to build such a small project. I will add makefile for linux users.
  2. Hi everybody! I am relatively new to game development, however I would like to share my experience in developing a meta system for C++. I faced the problem of embedding a scripting language when I was developing my own 3D game engine. There can be many solutions for embedding a specific language (for example, Luabind for Lua and boost.Python for Python). Having such a variety of tools, one obviously should not reinvent the wheel. I started by embedding the simple and fast Lua programming language with the Luabind library. I think it is very good, you may wish to see yourself: class_("BaseComponent") .def(constructor()) .def("start", &BaseScript::start, &ScriptComponentWrapper::default_start) .def("update", &BaseScript::update, &ScriptComponentWrapper::default_update) .def("stop", &BaseScript::stop, &ScriptComponentWrapper::default_stop) .property("camera", &BaseScript::getCamera) .property("light", &BaseScript::getLight) .property("material", &BaseScript::getMaterial) .property("meshFilter", &BaseScript::getMeshFilter) .property("renderer", &BaseScript::getRenderer) .property("transform", &BaseScript::getTransform) This piece of code looks highly readable to me. Class registration is simple, at least I see no obstacles. However, this solution is for Lua only. Inspired by the Unity script system, I decided to add support for several languages into the engine, as well as a platform for interaction between them. Yet such tools as Luabind are not quite suitable for these: most of them are built on C++ templates and generate code only for a pre-specified language. Each class must be registered in each of the systems. Any user of a system has to manually define template instantiations of every class for every scripting language. It would be great to have just one database for all script engines. Moreover, it would be nice to have the ability to load a type's specifications from plugins within runtime. Binding libraries are not good for this - it must be a real metasystem! I could see no way for adopting an existing solution. Existing libraries turned out to be huge and awkward. Some seemingly smart solutions have additional dependencies or require special tools such as Qt moc and gccxml. Of course, one could find good alternatives, such as the Camp reflection library. It looks like Luabind: camp::Class::declare("MyClass") // ***** constant value ***** .function("f0", &MyClass::f).callable(false) .function("f1", &MyClass::f).callable(true) // ***** function ***** .function("f2", &MyClass::f).callable(&MyClass::b1) .function("f3", &MyClass::f).callable(&MyClass::b2) .function("f4", &MyClass::f).callable(boost::bind(&MyClass::b1, _1)) .function("f5", &MyClass::f).callable(&MyClass::m_b) .function("f6", &MyClass::f).callable(boost::function(&MyClass::m_b)); However, the performances of such solutions leave much to be desired. Therefore, I decided to develop my own metasystem, as any normal programmer would, I think. This is why the uMOF library has been developed. Meet the uMOF uMOF is a cross-platform open source library for meta programming. It resembles Qt, but, unlike Qt, it is developed by using templates. Qt declined the use of templates due to syntax matters. Although their approach brings high speed and safe memory use, it requires the use of an external tool (MOC compiler) which is not always convenient. Now let's get down to business. To make meta information available to users in objects inherited from Object class, you should write OBJECT macro in the class definition. Now you can write EXPOSE and PROPERTIES macros to define functions and properties. Take a look at this example: class Test : public Object { OBJECT(Test, Object) EXPOSE(Test, METHOD(func), METHOD(null), METHOD(test) ) public: Test() = default; float func(float a, float b) { return a + b; } int null() { return 0; } void test() { std::cout I also considered testing other libraries: Boost.Mirror XcppRefl Reflex XRtti However, currently this appears impossible because of various reasons. The Boost.Mirror and XcppRefl look promising, but they are not yet in an active development stage. Reflex needs GCCXML tool, but I failed to find any adequate substitution of that for Windows. Xrtti does not support Windows either in the current release. What is in the pipeline? So, how does it work? Variadic templates and templates with functions as arguments give speed and a compact binary. All meta information is organized as a set of static tables. No additional actions are required at runtime. A simple structure of pointer tables keeps binary tight. Find an example of function description below: template struct Invoker { typedef Return(Class::*Fun)(Args...); inline static int argCount() { return sizeof...(Args); } inline static const TypeTable **types() { static const TypeTable *staticTypes[] = { Table::get(), getTable()... }; return staticTypes; } template inline static Any invoke(Object *obj, F f, const Any *args, unpack::indices) { return (static_cast(obj)->*f)(any_cast(args[Is])...); } template static Any invoke(Object *obj, int argc, const Any *args) { if (argc != sizeof...(Args)) throw std::runtime_error("Bad argument count"); return invoke(obj, fun, args, unpack::indices_gen()); } }; The Any class plays an important role in the library performance. It allows allocating memory for instances and stores the associated type information efficiently. I used hold_any class from the boost.spirit library as a reference. Boost also uses templates to wrap types. Types, which are smaller than a pointer, are stored in void* directly. For a bigger type, the pointer refers to an instance of the type. template struct AnyHelper { typedef Bool is_pointer; typedef typename CheckType::type T_no_cv; inline static void clone(const T **src, void **dest) { new (dest)T(*reinterpret_cast(src)); } }; template struct AnyHelper { typedef Bool is_pointer; typedef typename CheckType::type T_no_cv; inline static void clone(const T **src, void **dest) { *dest = new T(**src); } }; template Any::Any(T const& x) : _table(Table::get()), _object(nullptr) { const T *src = &x; AnyHelper::clone(&src, &_object); } I had to reject using RTTI - it is too slow. Types are checked only by comparison of pointers to the static tables. All type modifiers are omitted, so that, for example, int and const int are treated as the same type. template inline T* any_cast(Any* operand) { if (operand && operand->_table == Table::get()) return AnyHelper::cast(&operand->_object); return nullptr; } How to use the library? Script engine building becomes simple and nice. For example, it is enough to define an generic call function for Lua. It will check the number of arguments and their types and, of course, call the function itself. Binding is also not difficult: just save MetaMethod in upvalue for each function in Lua. All objects in uMof are "thin", that is to say they only wrap around pointers referring to records in the static table. Therefore, you can copy them without worrying about the performance. Find an example of Lua binding below: #include #include #include #include class Test : public Object { OBJECT(Test, Object) EXPOSE( METHOD(sum), METHOD(mul) ) public: static double sum(double a, double b) { return a + b; } static double mul(double a, double b) { return a * b; } }; int genericCall(lua_State *L) { Method *m = (Method *)lua_touserdata(L, lua_upvalueindex(1)); assert(m); // Retrieve the argument count from Lua int argCount = lua_gettop(L); if (m->parameterCount() != argCount) { lua_pushstring(L, "Wrong number of args!"); lua_error(L); } Any *args = new Any[argCount]; for (int i = 0; i < argCount; ++i) { int ltype = lua_type(L, i + 1); switch (ltype) { case LUA_TNUMBER: args.reset(luaL_checknumber(L, i + 1)); break; case LUA_TUSERDATA: args = *(Any*)luaL_checkudata(L, i + 1, "Any"); break; default: break; } } Any res = m->invoke(nullptr, argCount, args); double d = any_cast(res); if (!m->returnType().valid()) return 0; return 0; } void bindMethod(lua_State *L, const Api *api, int index) { Method m = api->method(index); luaL_getmetatable(L, api->name()); // 1 lua_pushstring(L, m.name()); // 2 Method *luam = (Method *)lua_newuserdata(L, sizeof(Method)); // 3 *luam = m; lua_pushcclosure(L, genericCall, 1); lua_settable(L, -3); // 1[2] = 3 lua_settop(L, 0); } void bindApi(lua_State *L, const Api *api) { luaL_newmetatable(L, api->name()); // 1 // Set the "__index" metamethod of the table lua_pushstring(L, "__index"); // 2 lua_pushvalue(L, -2); // 3 lua_settable(L, -3); // 1[2] = 3 lua_setglobal(L, api->name()); lua_settop(L, 0); for (int i = 0; i < api->methodCount(); i++) bindMethod(L, api, i); } int main(int argc, char *argv[]) { lua_State *L = luaL_newstate(); luaL_openlibs(L); bindApi(L, Test::classApi()); int erred = luaL_dofile(L, "test.lua"); if (erred) std::cout
  3. Thank you all for your positive comments.   Endurion, Sure, you can bind free functions as well as static methods.   majo33, Sorry, link is correct now.   These macros will not increase the executable. This works because I define the static array inside a method. And for methods, the linker does recognize multiple identical definitions and throws away the copies.
  • Advertisement

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!