#ifndef ATTRIBUTE_BINDER_H #define ATTRIBUTE_BINDER_H #include #include #include #include /* Intended applications of the AttributeBinder are: - Script binding - Disk serialization - Network transmission - Editor binding Design: - No overhead or wrappers on bound variables - Does not introduce vtables - Minimal programmer interface - One AttributeDescriptor, instead of templated Attributes - Support POD data, and pointer data types - reinterpret_cast to get at the real variable - Does not use C++ RTTI mechanisms - Binary serialization, textual serialization is an exercise for the reader Current flaws of disk serialization and network transmission: - No platform swizzles - Not compatible between 32/64 bit systems - No enforcement of aligned memory reads through padding - Arrays of data not yet supported */ // The AttributeBinder class has a static RTTI interface for general use; // this interface can convert between an enumerant id, and a name. // There is a map of type names to AttributeBinders, and Factory functions // to create objects of registered types. class AttributeBinder { public: // When an attribute is bound, a descriptor is created. // The descriptor is used to find data in the containing object, // DataAttr is provided for that purpose. class AttributeDescriptor { public: AttributeDescriptor() : typeId(0), off(0) { } AttributeDescriptor(const char* name, const char* attribType, ptrdiff_t off) : name(name), off(off) { typeId = GetTypeId(attribType); // treat char* as string type, not as pointer type if (!strcmp(attribType, "char*")) ptrType = false; else ptrType = attribType[strlen(attribType)-1] == '*'; } AttributeDescriptor(const AttributeDescriptor& rhs) : name(rhs.name), typeId(rhs.typeId), off(rhs.off) { } bool typeEqual(const AttributeDescriptor& rhs) { return typeId == rhs.typeId; } std::string TypeName() const; void* DataAddr(ptrdiff_t base) const; // only to be used for attributes which are POD. size_t PODDataSize(ptrdiff_t) const; const char* Name() const { return name; } const bool PtrType() const { return ptrType; } ptrdiff_t off; // only provided for the WriteText method on AttributeBinder. // Purely illustrative, as is the WriteText method. // Only for POD types std::string PODToString(ptrdiff_t) const; private: int typeId; const char* name; bool ptrType; }; typedef void* (*ClassFactoryFnPtr)(); public: AttributeBinder() { } void bind(const char* varname, const char* attribType, ptrdiff_t offset) { attribs.insert(varname, AttributeDescriptor(varname, attribType, offset)); } // A convenience function, provided for illustrative purposes only void WriteText(std::ofstream& o, ptrdiff_t foo, char const*const varName, int tabDepth); // Read and write a class. bool WriteBinary(std::ofstream&, ptrdiff_t foo); bool ReadBinary(std::ifstream&); // A map for finding all the bound attributes on the class StringMap attribs; // The base classes to be serialized before the present class. Note that // at the moment, this system doesn't support multiple inheritance, so the // length of this vector will always be zero or one. std::vector bases; // The typeId of the present class int typeId; // static RTTI interface static int GetTypeId(const char* type); static const char* GetTypeName(int i); // Note: the following maps are all accessed via a method, and created on // first access. Static maps won't work, because there is no guarantee that // these maps will be instantiated before the objects specified by the // BIND_ATTRIBUTES macro are instantiated. // map of registered bindable classes to the binder itself static StringMap* Binders(); // map of registered bindable classes to a class factory static StringMap* ClassFactories(); private: bool WriteBinaryAttribute(ptrdiff_t foo, const char* enclosingVarName, const std::vector& gatheredTypes, const std::vector >& flattenedVarTable, std::filebuf*); }; // Within a class, create a binding object, and start the Bind method. // Note that it is valid to put other code between BIND_START and BIND_END // if you want it to run during the static Bind initialization. // Semicolons are optional after BIND_START, you can use them if you use // a pretty-printer on code and want things to line up nicely #define BIND_START \ static AttributeBinder binding; \ static void Bind() \ { // Bind a single variable #define BIND(className, val, vartype) \ binding.bind(#val, #vartype, (ptrdiff_t)&((className*)0)->val) // Bind a base // This only works for single inheritance, since we currently eschew C++ RTTI // retaining the vector of bases for the moment in case I discover a way to // implement DataAddr for multiply inherited objects #define BIND_BASE(c) \ binding.bases.push_back(&c::binding); // End the bind method // Semicolons are optional after BIND_END, you can use them if you use // a pretty-printer on code and want things to line up nicely #define BIND_END \ } // Every bound class needs this macro defined in the global scope. // Instantiate the binding object for class C, and make a globally // instantiated struct whose constructor will invoke C's Bind method // which has been declared in BIND_START // Also register the binding object in a map of bindable classes // This goes in the implementation file, in the same namespace as the // class C. // BIND_ATTRIBUTES does not suffer from order of initialization issues. // However, to support multiple inheritance, it might have to, with the // BIND_BASE macro calling a modified version of bind that puts the // members from the base classes into the subclass. #define BIND_ATTRIBUTES(c) \ AttributeBinder c::binding; \ namespace { \ void* c ## Factory() { return (void*) new c(); } \ struct Static ## c ## Binder \ { \ Static ## c ## Binder() \ { \ c::binding.typeId = AttributeBinder::GetTypeId(#c); \ c::Bind(); \ (*AttributeBinder::Binders()).insert(#c, &c::binding); \ (*AttributeBinder::ClassFactories()).insert(#c, c ## Factory); \ } \ }; \ static Static ## c ## Binder static ## c ## binder; \ } #endif