#include "AttributeBinder.h" #include #include #include #include // Is this the best way to specify MSB when you don't want to know bitsize of the CPU? #define MSBMASK (~1u>>1) #define MSB ~(~1u>>1) namespace { static std::map* TypeNames() { static std::map* typenames = new std::map(); return typenames; } } //static StringMap* AttributeBinder::ClassFactories() { static StringMap* binders = new StringMap(); return binders; } //static StringMap* AttributeBinder::Binders() { static StringMap* binders = new StringMap(); return binders; } // If a type hasn't been seen before, enumerate it, otherwise return // the enumerant generated the first time it was seen //static int AttributeBinder::GetTypeId(const char* type) { static int unique = 0; int result; // map of typenames to type ids static StringMap* typeIds = new StringMap(); // look up the type in the typeId StringMap int data = 0; if (!typeIds->find(type, &data)) { // If the type wasn't found, add it and assign it an id // insert critical section here std::map* typeNames = TypeNames(); result = unique; typeIds->insert(type, unique); (*typeNames)[unique] = type; unique++; } else result = data; return result; } // GetTypeId must have been called for every type for this to return a useful value. //static const char* AttributeBinder::GetTypeName(int i) { std::map* typeNames = TypeNames(); std::map::const_iterator j = typeNames->find(i); return j == typeNames->end() ? 0 : j->second; } std::string AttributeBinder::AttributeDescriptor::TypeName() const { return GetTypeName(typeId); } int intType() { static int t = AttributeBinder::GetTypeId("int"); return t; } int floatType() { static int t = AttributeBinder::GetTypeId("float"); return t; } int boolType() { static int t = AttributeBinder::GetTypeId("bool"); return t; } int charPtrType() { static int t = AttributeBinder::GetTypeId("char*"); return t; } int stringType() { static int t = AttributeBinder::GetTypeId("std::string"); return t; } // used for text writing std::string AttributeBinder::AttributeDescriptor::PODToString(ptrdiff_t base) const { std::stringstream o; if (typeId == intType()) o << *(int*) (base + off); else if (typeId == floatType()) o << *(float*) (base + off); else if (typeId == boolType()) o << ((*(bool*) (base + off)) ? "true" : "false"); else if (typeId == charPtrType()) o << (char*) (base + off); else if (typeId == stringType()) o << *(std::string*) (base + off); else o << "Error: Unknown type"; return o.str(); } // used for binary writing size_t AttributeBinder::AttributeDescriptor::PODDataSize(ptrdiff_t base) const { if (typeId == intType()) return sizeof(int); else if (typeId == floatType()) return sizeof(float); else if (typeId == boolType()) return sizeof(bool); else if (typeId == charPtrType()) return strlen((char*) base + off) + 1; else if (typeId == stringType()) return (*(std::string*)(base + off)).size() + 1; else return 0; } void* AttributeBinder::AttributeDescriptor::DataAddr(ptrdiff_t base) const { static int stringType = GetTypeId("std::string"); if (typeId == stringType) { return (void*) (*(std::string*)(base + off)).c_str(); } else { return (void*) (base + off); } } /* Provided for debugging help only. I'm not a fan at all of C++ streams, they show up on profiles. */ void WriteTextAttributes(std::ofstream& o, ptrdiff_t foo, const AttributeBinder& binding, int tabDepth) { char tabs[100]; int t; for (t = 0; t < tabDepth; ++t) tabs[t] = '\t'; tabs[t] = '\0'; std::vector::const_iterator j; for (j = binding.bases.begin(); j != binding.bases.end(); ++j) { WriteTextAttributes(o, foo, **j, tabDepth); } std::vector > ads; ads.reserve(binding.attribs.size); binding.attribs.inOrder(ads); for (std::vector >::const_iterator i = ads.begin(); i != ads.end(); ++i) { std::string attribType = (*i).second->TypeName(); if ((*i).second->PtrType()) { attribType = attribType.substr(0, attribType.length()-1); AttributeBinder* binder = 0; if (AttributeBinder::Binders()->find(attribType.c_str(), &binder)) { ptrdiff_t varAddr = foo + (*i).second->off; int* iPtr = reinterpret_cast(varAddr); if (*iPtr == 0) { o << tabs << "(" << attribType << "*)" << (*i).second->Name() << "-> NULL\n"; } else { // if it's a registered type, recurse std::string temp = (*i).second->Name(); temp += "->"; //(*(j->second)).WriteText(o, *iPtr, temp.c_str(), tabDepth); binder->WriteText(o, *iPtr, temp.c_str(), tabDepth); } } else { o << "error case, pointer to POD is not covered yet\n"; } } else { AttributeBinder* binder = 0; if (AttributeBinder::Binders()->find(attribType.c_str(), &binder)) //std::map::const_iterator j = AttributeBinder::Binders()->find(typeName); //if (j != AttributeBinder::Binders()->end()) { // if it's a registered type, recurse //(*(j->second)).WriteText(o, foo + (*i).second->off, (*i).second->Name(), tabDepth); binder->WriteText(o, foo + (*i).second->off, (*i).second->Name(), tabDepth); } else { o << tabs << "(" << attribType << ")" << (*i).second->Name() << "=" << (*i).second->PODToString(foo) << "\n"; } } } } void AttributeBinder::WriteText(std::ofstream& o, ptrdiff_t foo, char const*const varName, int tabDepth) { char tabs[100]; int t; for (t = 0; t < tabDepth; ++t) tabs[t] = '\t'; tabs[t] = '\0'; o << tabs << GetTypeName(typeId) << " " << varName << "\n" << tabs << "{\n"; WriteTextAttributes(o, foo, *this, tabDepth+1); o << tabs << "}\n"; } // Recurse a binding, and add all encountered types to a vector. void GatherTypes(ptrdiff_t foo, const AttributeBinder& binding, std::vector& types) { // push back containing type int t = binding.typeId; std::vector::const_iterator f = std::find(types.begin(), types.end(), t); if (f == types.end()) { types.push_back(t); std::sort(types.begin(), types.end()); } std::vector::const_iterator j; for (j = binding.bases.begin(); j != binding.bases.end(); ++j) { GatherTypes(foo, **j, types); } std::vector > ads; ads.reserve(binding.attribs.size); binding.attribs.inOrder(ads); for (std::vector >::const_iterator i = ads.begin(); i != ads.end(); ++i) { std::string attribType = (*i).second->TypeName(); AttributeBinder* binder = 0; AttributeBinder::Binders()->find(attribType.c_str(), &binder); if ((*i).second->PtrType()) { if (binder) { GatherTypes(foo + (*i).second->off, *binder, types); } else { // error condition. This could should treat it properly // If arrays are handled, this would be the arrays case } } else { if (binder) { GatherTypes(foo + (*i).second->off, *binder, types); } else { t = AttributeBinder::GetTypeId(attribType.c_str()); f = std::find(types.begin(), types.end(), t); if (f == types.end()) { types.push_back(t); std::sort(types.begin(), types.end()); } } } } } // recurse a binding, and add all encountered variables to a StringMap static int uniqueVarId = 0; void GatherVars(ptrdiff_t foo, const AttributeBinder& binding, StringMap& varTable) { // recurse if there's bases std::vector::const_iterator j; for (j = binding.bases.begin(); j != binding.bases.end(); ++j) { GatherVars(foo, **j, varTable); } // run through all the vars std::vector > ads; ads.reserve(binding.attribs.size); binding.attribs.inOrder(ads); for (std::vector >::const_iterator i = ads.begin(); i != ads.end(); ++i) { std::string attribType = (*i).second->TypeName(); int dummy; if (!varTable.find((*i).second->Name(), &dummy)) { varTable.insert((*i).second->Name(), uniqueVarId++); } // recurse if the variable is not a POD AttributeBinder* binder = 0; if ((*i).second->PtrType()) { attribType = attribType.substr(0, attribType.length()-1); } AttributeBinder::Binders()->find(attribType.c_str(), &binder); if (binder && (*i).second->PtrType()) { char** p = (char**) (foo + (*i).second->off); GatherVars((ptrdiff_t) *p, *binder, varTable); } else if (binder) { GatherVars(foo + (*i).second->off, *binder, varTable); } } } // not fast. Once a type table has been collapsed, we need to know // the index of the type that is in the table being written. // This is provided for two reasons. One, it's not necessary for // the enumerated types in the code to be the same as those in the // file (in other words, the code can change but the file will stay // valid). Two, in a large application, there may be dozens (or // hundreds) of reflected types, it's wasteful to store types that // aren't being saved in a file. // This iteration of the AttributeBinder is optimized for run time // look up of attributes, for example from a scripting system, not // for speed of reading and writing. int flattenedType(int t, const std::vector& gatheredTypes) { for (size_t i = 0; i < gatheredTypes.size(); ++i) { if (gatheredTypes[i] == t) return i; } return -1; } // This is the file format of an individual reflected class: // // BASE defined as: // // [4] type, high bit set means recursive type, second MSB means pointer type // [4] var name index // [4] base count // repeat base count times // [BASE] // [4] attr count // repeat attr count times // if recursive type // [BASE] // else // [4] type, high bit clear // [4] var name index // [4] datasize // [datasize] data // bool AttributeBinder::WriteBinaryAttribute(ptrdiff_t foo, const char* enclosingVarName, const std::vector& gatheredTypes, const std::vector >& flattenedVarTable, std::filebuf* pbuf) { // write out class name index (from type table) // set the high bit so we know this type is recursive int t = flattenedType(typeId, gatheredTypes); if (t < 0) return false; else t |= MSB; pbuf->sputn((const char*) &t, sizeof(t)); // write var name index; bases have 0 as varName if (enclosingVarName) { for (int v = 0; v < (int) flattenedVarTable.size(); ++v) { if (!strcmp(flattenedVarTable[v].first, enclosingVarName)) { pbuf->sputn((const char*) &v, sizeof(v)); break; } } } else { // no name to record int v = -1; pbuf->sputn((const char*) &v, sizeof(v)); } // write count of bases // t = bases.size(); pbuf->sputn((const char*) &t, sizeof(t)); // write the attributes of all the bases // std::vector::const_iterator j; for (j = bases.begin(); j != bases.end(); ++j) { (*j)->WriteBinaryAttribute(foo, 0, gatheredTypes, flattenedVarTable, pbuf); } // write the attributes of this // std::vector > ads; ads.reserve(attribs.size); attribs.inOrder(ads); // Write the attribute count t = ads.size(); pbuf->sputn((const char*) &t, sizeof(t)); for (std::vector >::const_iterator i = ads.begin(); i != ads.end(); ++i) { std::string attribType = (*i).second->TypeName(); int v; // find var name index for (v = 0 ; v < (int) flattenedVarTable.size(); ++v) { if (!strcmp(flattenedVarTable[v].first, (*i).second->Name())) { break; } } if (v == flattenedVarTable.size()) v = -1; if ((*i).second->PtrType()) { attribType = attribType.substr(0, attribType.length() - 1); AttributeBinder* binder = 0; AttributeBinder::Binders()->find(attribType.c_str(), &binder); if (binder) { void** dPtr = (void**)(*i).second->DataAddr(foo); void* data = *dPtr; if (data == 0) { // write type with high bit set t = flattenedType(AttributeBinder::GetTypeId(attribType.c_str()), gatheredTypes); if (t < 0) return false; else t |= MSB; pbuf->sputn((const char*) &t, sizeof(t)); // write var name index pbuf->sputn((const char*) &v, sizeof(v)); data = (void*) ~0; // mark the file with the fact that the pointer is empty pbuf->sputn((const char*) &data, sizeof(data)); } else { bool result = binder->WriteBinaryAttribute((ptrdiff_t)data, (*i).second->Name(), gatheredTypes, flattenedVarTable, pbuf); if (!result) return false; } } else { // POD pointer data or arrays not yet supported return false; } } else { AttributeBinder* binder = 0; AttributeBinder::Binders()->find(attribType.c_str(), &binder); if (binder) { bool result = binder->WriteBinaryAttribute(foo + (*i).second->off, (*i).second->Name(), gatheredTypes, flattenedVarTable, pbuf); if (!result) return false; } else { // write type t = flattenedType(AttributeBinder::GetTypeId(attribType.c_str()), gatheredTypes); if (t < 0) return false; pbuf->sputn((const char*) &t, sizeof(t)); // write var name index pbuf->sputn((const char*) &v, sizeof(v)); // write data size t = (*i).second->PODDataSize(foo); pbuf->sputn((const char*) &t, sizeof(t)); // write data pbuf->sputn((const char*) (*i).second->DataAddr(foo), t); } } } return true; } // File format // [4] 'spas' // [4] version (1) // [4] type count // [4*type count] offsets to type names in type string buff // [4] size of type string buff // [size of type string buff] // [4] var count // [var count * 8] pairs of type index, var name index // [4] size of var name buff // [size of var name buff] // attributes, see above bool AttributeBinder::WriteBinary(std::ofstream& f, ptrdiff_t foo) { //////////////////////////// Header struct Header { char sig[4]; int version; }; Header header; std::filebuf* pbuf = f.rdbuf(); // Smallest Possible Attribute System = spas. header.sig[0] = 's'; header.sig[1] = 'p'; header.sig[2] = 'a'; header.sig[3] = 's'; header.version = 1; //---- pbuf->sputn((const char*)&header, sizeof(Header)); //////////////////////////// Type Table // std::vector types; GatherTypes(foo, *this, types); std::vector gatheredTypes; std::vector stringOffsets; std::vector stringBuff; for (size_t i = 0; i < types.size(); ++i) { stringOffsets.push_back(stringBuff.size()); const char* s = AttributeBinder::GetTypeName(types[i]); for (size_t k = 0; k < strlen(s); ++k) { stringBuff.push_back(s[k]); } stringBuff.push_back('\0'); gatheredTypes.push_back(types[i]); } //---- the number of types int count = types.size(); pbuf->sputn((const char*)&count, sizeof(int)); //---- offsets to the names of the types into the string buffer pbuf->sputn((const char*)&stringOffsets[0], sizeof(int) * stringOffsets.size()); //---- size of string buffer count = stringBuff.size(); pbuf->sputn((const char*)&count, sizeof(int)); //---- the strings pbuf->sputn(&stringBuff[0], stringBuff.size()); //////////////////////////// Var Table // make a stringmap of var names to indices // then write out offsets and strings // StringMap varTable; GatherVars(foo, *this, varTable); std::vector > flattenedVarTable; varTable.inOrder(flattenedVarTable); stringOffsets.clear(); stringBuff.clear(); count = flattenedVarTable.size(); for (int i = 0; i < count; ++i) { stringOffsets.push_back(*flattenedVarTable[i].second); stringOffsets.push_back(stringBuff.size()); size_t len = strlen(flattenedVarTable[i].first); for (size_t k = 0; k < len; ++k) { stringBuff.push_back(flattenedVarTable[i].first[k]); } stringBuff.push_back('\0'); } //---- variable count pbuf->sputn((const char*)&count, sizeof(int)); //---- offsets to the variables in the variable name block // type, offset pbuf->sputn((const char*)&stringOffsets[0], sizeof(int) * stringOffsets.size()); //---- length of variable name block count = stringBuff.size(); pbuf->sputn((const char*)&count, sizeof(int)); //---- variable names pbuf->sputn(&stringBuff[0], stringBuff.size()); //////////////////////////// data, enclosingVarName is 0 because root return WriteBinaryAttribute(foo, 0, gatheredTypes, flattenedVarTable, pbuf); } struct LoadData { int typeTableSize; int* typeIndices; const char* typeStrings; int varTableSize; int* varIndices; const char* varStrings; }; void* ReadBinaryRecursive(void* curr, LoadData* ld, void* obj, bool& result) { int* curri = (int*) curr; // vars int rawTypeIndex = *curri++; int varNameIndex = *curri++; int baseCount = *curri++; int typeIndex = rawTypeIndex & MSBMASK; const char* type = &ld->typeStrings[ld->typeIndices[typeIndex]]; if (obj == 0) { AttributeBinder::ClassFactoryFnPtr cf = 0; AttributeBinder::ClassFactories()->find(type, &cf); obj = cf(); } AttributeBinder* binder = 0; AttributeBinder::Binders()->find(type, &binder); // load all the bases for (int i = 0; i < baseCount; ++i) { curri = (int*) ReadBinaryRecursive(curri, ld, obj, result); if (!result) return curri; } // read the number of attributes int attrCount = *curri++; for (int i = 0; i < attrCount; ++i) { typeIndex = *curri++; const char* attrType = &ld->typeStrings[ld->typeIndices[typeIndex]]; int varNameIndex = *curri++; const char* varName = &ld->varStrings[ld->varIndices[varNameIndex*2 + 1]]; // if high bit set, recurse if (typeIndex < 0) { // find the attribute AttributeBinder::AttributeDescriptor ad; binder->attribs.find(varName, &ad); // address of data void* objData = (void*) ((ptrdiff_t)obj + ad.off); if (ad.PtrType()) { std::string temp = attrType; void** objDataPtr = (void**) objData; if (*curri == ~0) *objDataPtr = (void*) 0; else { AttributeBinder::ClassFactoryFnPtr cf = 0; AttributeBinder::ClassFactories()->find(type, &cf); *objDataPtr = (void*) cf(); } objData = *objDataPtr; } if (objData) { // recursive type, so back up to the type for recursion curri = (int*) ReadBinaryRecursive(curri-2, ld, objData, result); if (!result) return curr; } else { ++curri; // skip the ~0 } } else { int dataSize = *curri++; void* data = (void*) curri; curri = (int*)((char*) curri + dataSize); // find the attribute AttributeBinder::AttributeDescriptor ad; binder->attribs.find(varName, &ad); // address of data void* objData = (void*) ((ptrdiff_t)obj + ad.off); // copy the data if (!strcmp(attrType, "std::string")) { std::string* s = (std::string*) objData; s->assign((const char*)data); } else if (!strcmp(attrType, "char*")) { char* pChar = (char*) objData; pChar = (char*) malloc(dataSize); memcpy(pChar, data, dataSize); } else { memcpy(objData, data, dataSize); } } } return curri; } bool AttributeBinder::ReadBinary(std::ifstream& f) { if (!f) return false; std::filebuf *pbuf = f.rdbuf(); size_t size = pbuf->pubseekoff(0, std::ios::end, std::ios::in); pbuf->pubseekpos(0, std::ios::in); void* data = malloc(size); pbuf->sgetn((char*) data, size); // signature char* curr = (char*) data; if ((curr[0] != 's') || (curr[1] != 'p') || (curr[2] != 'a') || (curr[3] != 's')) return false; curr += 4; // version int* curri = (int*) curr; if (*curri != 1) return false; ++curri; LoadData ld; // type table size, indices, strings size, strings ld.typeTableSize = *curri++; ld.typeIndices = curri; curri += ld.typeTableSize; int typeStringsLength = *curri++; ld.typeStrings = (const char*) curri; curri = (int*) (ld.typeStrings + typeStringsLength); // var table ld.varTableSize = *curri++; ld.varIndices = curri; curri += ld.varTableSize * 2; // type, offset pairs int varStringsLength = *curri++; ld.varStrings = (const char*) curri; curri = (int*) (ld.varStrings + varStringsLength); bool result = true; ReadBinaryRecursive((void*) curri, &ld, 0, result); return result; }