/* Crystal Space Entity Layer Copyright (C) 2001 by Jorrit Tyberghein This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "cssysdef.h" #include "physicallayer/pl.h" #include "physicallayer/entity.h" #include "physicallayer/propclas.h" #include "physicallayer/propfact.h" #include "behaviourlayer/bl.h" #include "behaviourlayer/behave.h" #include "plugins/persist/xml/xmlpersist.h" #include "csutil/csendian.h" #include "csutil/memfile.h" #include "csutil/cscolor.h" #include "csutil/xmltiny.h" #include "csutil/databuf.h" #include "iutil/vfs.h" #include "iutil/objreg.h" #include "ivaria/reporter.h" //--------------------------------------------------------------------------- // define this to track save/loading problems // #define PERSIST_DEBUG CS_IMPLEMENT_PLUGIN SCF_IMPLEMENT_FACTORY (celPersistXML) SCF_IMPLEMENT_IBASE (celPersistXML) SCF_IMPLEMENTS_INTERFACE (iCelPersistence) SCF_IMPLEMENTS_EMBEDDED_INTERFACE (iComponent) SCF_IMPLEMENT_IBASE_END SCF_IMPLEMENT_EMBEDDED_IBASE (celPersistXML::Component) SCF_IMPLEMENTS_INTERFACE (iComponent) SCF_IMPLEMENT_EMBEDDED_IBASE_END celPersistXML::celPersistXML (iBase* parent) { SCF_CONSTRUCT_IBASE (parent); SCF_CONSTRUCT_EMBEDDED_IBASE (scfiComponent); object_reg = 0; } celPersistXML::~celPersistXML () { SCF_DESTRUCT_EMBEDDED_IBASE (scfiComponent); SCF_DESTRUCT_IBASE (); } bool celPersistXML::Initialize (iObjectRegistry* object_reg) { celPersistXML::object_reg = object_reg; vfs = CS_QUERY_REGISTRY (object_reg, iVFS); if (!vfs) return false; pl = CS_QUERY_REGISTRY (object_reg, iCelPlLayer); if (!pl) return false; return true; } bool celPersistXML::Report (const char* msg, ...) { va_list arg; va_start (arg, msg); csRef rep (CS_QUERY_REGISTRY (object_reg, iReporter)); if (rep) rep->ReportV (CS_REPORTER_SEVERITY_ERROR, "cel.persist.xml", msg, arg); else { csPrintfV (msg, arg); csPrintf ("\n"); fflush (stdout); } va_end (arg); return false; } //------------------------------------------------------------------------ bool celPersistXML::Write (iDocumentNode* node, iCelDataBuffer* db) { node->SetAttributeAsInt ("serial", db->GetSerialNumber ()); size_t i; for (i = 0 ; i < db->GetDataCount () ; i++) { if (!Write (node, db->GetData (i))) return false; } return true; } bool celPersistXML::Write (iDocumentNode* node, celData* data) { csRef attrnode = node->CreateNodeBefore (CS_NODE_ELEMENT, 0); switch (data->type) { case CEL_DATA_NONE: case CEL_DATA_ACTION: CS_ASSERT (false); break; case CEL_DATA_BOOL: attrnode->SetValue ("bool"); attrnode->SetAttributeAsInt ("v", int (data->value.bo)); break; case CEL_DATA_BYTE: attrnode->SetValue ("byte"); attrnode->SetAttributeAsInt ("v", int (data->value.b)); break; case CEL_DATA_WORD: attrnode->SetValue ("word"); attrnode->SetAttributeAsInt ("v", int (data->value.w)); break; case CEL_DATA_LONG: attrnode->SetValue ("long"); attrnode->SetAttributeAsInt ("v", int (data->value.l)); break; case CEL_DATA_UBYTE: attrnode->SetValue ("ubyte"); attrnode->SetAttributeAsInt ("v", int (data->value.ub)); break; case CEL_DATA_UWORD: attrnode->SetValue ("uword"); attrnode->SetAttributeAsInt ("v", int (data->value.uw)); break; case CEL_DATA_ULONG: attrnode->SetValue ("ulong"); attrnode->SetAttributeAsInt ("v", int (data->value.ul)); break; case CEL_DATA_FLOAT: attrnode->SetValue ("float"); attrnode->SetAttributeAsFloat ("v", data->value.f); break; case CEL_DATA_VECTOR2: attrnode->SetValue ("vector2"); attrnode->SetAttributeAsFloat ("x", data->value.v.x); attrnode->SetAttributeAsFloat ("y", data->value.v.y); break; case CEL_DATA_VECTOR3: attrnode->SetValue ("vector3"); attrnode->SetAttributeAsFloat ("x", data->value.v.x); attrnode->SetAttributeAsFloat ("y", data->value.v.y); attrnode->SetAttributeAsFloat ("z", data->value.v.z); break; case CEL_DATA_COLOR: attrnode->SetValue ("color"); attrnode->SetAttributeAsFloat ("r", data->value.col.red); attrnode->SetAttributeAsFloat ("g", data->value.col.green); attrnode->SetAttributeAsFloat ("b", data->value.col.blue); break; case CEL_DATA_STRING: attrnode->SetValue ("string"); if (!data->value.s->IsEmpty ()) attrnode->SetAttribute ("v", *data->value.s); break; case CEL_DATA_PCLASS: attrnode->SetValue ("pc"); if (!Write (attrnode, data->value.pc, false)) return false; break; case CEL_DATA_ENTITY: attrnode->SetValue ("entity"); if (!Write (attrnode, data->value.ent, false)) return false; break; case CEL_DATA_IBASE: return Report ("Data type iBase is not allowed for persistence!"); default: return Report ("Found a DataType I'm not able to write!"); } return true; } bool celPersistXML::Write (iDocumentNode* pcnode, iCelPropertyClass* pc, bool savelocal) { if (!pc) { // 0 pc. pcnode->SetAttribute ("null", "true"); return true; } if (!set->IsLocal (pc)) { // This is a non-local pc. pcnode->SetAttribute ("extref", "true"); csRef db = set->SaveExternalPC (pc); return Write (pcnode, db); } else if (!savelocal) { // This is a local pc and we're not saving local pc's. // In that case we still have to save a reference. size_t entity_idx = entities_map.Get (pc->GetEntity (), csArrayItemNotFound); CS_ASSERT(entity_idx != csArrayItemNotFound); pcnode->SetAttributeAsInt ("locref", (int)entity_idx); pcnode->SetAttribute ("name", pc->GetName ()); if (pc->GetTag ()) pcnode->SetAttribute ("tag", pc->GetTag ()); return true; } if (pc->GetName ()) pcnode->SetAttribute ("name", pc->GetName ()); if (pc->GetTag ()) pcnode->SetAttribute ("tag", pc->GetTag ()); csRef db = pc->Save (); if (!db) return Report ("pc '%s' doesn't support saving!\n", pc->GetName ()); return Write (pcnode, db); } bool celPersistXML::Write (iDocumentNode* entnode, iCelEntity* entity, bool savelocal) { if (!entity) { // 0 entity. entnode->SetAttribute ("null", "true"); return true; } if (!set->IsLocal (entity)) { // This is a non-local entity. entnode->SetAttribute ("extref", "true"); csRef db = set->SaveExternalEntity (entity); return Write (entnode, db); } else if (!savelocal) { // This is a local entity and we're not saving local entities. // In that case we still have to save a reference. size_t idx = entities_map.Get (entity, csArrayItemNotFound); CS_ASSERT(idx != csArrayItemNotFound); entnode->SetAttributeAsInt ("locref", (int)idx); return true; } if (entity->GetName ()) entnode->SetAttribute ("name", entity->GetName ()); iCelPropertyClassList* pl = entity->GetPropertyClassList (); size_t i; for (i = 0 ; i < pl->GetCount () ; i++) { csRef pcnode = entnode->CreateNodeBefore ( CS_NODE_ELEMENT, 0); pcnode->SetValue ("pc"); if (!Write (pcnode, pl->Get (i), true)) return Report ("Error writing property class!\n"); } iCelBehaviour* bh = entity->GetBehaviour (); if (bh) { csRef behnode = entnode->CreateNodeBefore ( CS_NODE_ELEMENT, 0); behnode->SetValue ("behaviour"); behnode->SetAttribute ("layer", bh->GetBehaviourLayer ()->GetName ()); behnode->SetAttribute ("name", bh->GetName ()); } return true; } bool celPersistXML::WriteFirstPass (iDocumentNode* entnode, iCelEntity* entity) { iCelPropertyClassList* pl = entity->GetPropertyClassList (); size_t i; for (i = 0 ; i < pl->GetCount () ; i++) { iCelPropertyClass* pc = pl->Get (i); csRef db = pc->SaveFirstPass (); if (db) { csRef pcnode = entnode->CreateNodeBefore ( CS_NODE_ELEMENT, 0); pcnode->SetValue ("pc"); if (pc->GetName ()) pcnode->SetAttribute ("name", pc->GetName ()); if (pc->GetTag ()) pcnode->SetAttribute ("tag", pc->GetTag ()); if (!Write (pcnode, db)) return Report ("Error writing property class!\n"); } } return true; } //------------------------------------------------------------------------ bool celPersistXML::Read (iDocumentNode* node, celData* cd) { // @@@ Using a tokenizer would be better here. const char* value = node->GetValue (); if (!strcmp ("bool", value)) { cd->Set ((node->GetAttributeValueAsBool ("v"))); } else if (!strcmp ("byte", value)) { cd->Set ((int8)(node->GetAttributeValueAsInt ("v"))); } else if (!strcmp ("ubyte", value)) { cd->Set ((uint8)(node->GetAttributeValueAsInt ("v"))); } else if (!strcmp ("word", value)) { cd->Set ((int16)(node->GetAttributeValueAsInt ("v"))); } else if (!strcmp ("uword", value)) { cd->Set ((uint16)(node->GetAttributeValueAsInt ("v"))); } else if (!strcmp ("long", value)) { cd->Set ((int32)(node->GetAttributeValueAsInt ("v"))); } else if (!strcmp ("ulong", value)) { cd->Set ((uint32)(node->GetAttributeValueAsInt ("v"))); } else if (!strcmp ("float", value)) { cd->Set (node->GetAttributeValueAsFloat ("v")); } else if (!strcmp ("string", value)) { cd->Set (node->GetAttributeValue ("v")); } else if (!strcmp ("color", value)) { csColor v; v.red = node->GetAttributeValueAsFloat ("r"); v.green = node->GetAttributeValueAsFloat ("g"); v.blue = node->GetAttributeValueAsFloat ("b"); cd->Set (v); } else if (!strcmp ("vector2", value)) { csVector2 v; v.x = node->GetAttributeValueAsFloat ("x"); v.y = node->GetAttributeValueAsFloat ("y"); cd->Set (v); } else if (!strcmp ("vector3", value)) { csVector3 v; v.x = node->GetAttributeValueAsFloat ("x"); v.y = node->GetAttributeValueAsFloat ("y"); v.z = node->GetAttributeValueAsFloat ("z"); cd->Set (v); } else if (!strcmp ("pc", value)) { iCelPropertyClass* pc; if (!Read (node, 0, pc)) return false; cd->Set (pc); } else if (!strcmp ("entity", value)) { iCelEntity* ent; if (!Read (node, ent)) return false; cd->Set (ent); } else return Report ("Found unknown data type '%s'!", value); return true; } bool celPersistXML::Read (iDocumentNode* node, csRef& db) { int32 ser = (int32)node->GetAttributeValueAsInt ("serial"); db = pl->CreateDataBuffer (ser); csRef it = node->GetNodes (); while (it->HasNext ()) { csRef child = it->Next (); if (child->GetType () != CS_NODE_ELEMENT) continue; if (!Read (child, db->AddData ())) return Report ("Error reading data entry %s!", child->GetValue ()); } return true; } bool celPersistXML::Read (iDocumentNode* pcnode, iCelEntity* entity, iCelPropertyClass*& pc) { csRef attr; attr = pcnode->GetAttribute ("null"); if (attr != 0) { pc = 0; return true; } attr = pcnode->GetAttribute ("extref"); if (attr != 0) { csRef db; if (!Read (pcnode, db)) return Report ("Error reading external property class reference!"); pc = set->FindExternalPC (db); // @@@ Check? return true; } attr = pcnode->GetAttribute ("locref"); // Either we have a local ref (attr != 0) or not. But in any case // we first want to try to find if the pc already exists. In that // case we reuse it even if it is not a ref. if (attr != 0) { // A reference. uint32 entid = (uint32)attr->GetValueAsInt (); entity = set->GetEntity (entid); } const char* pcname = pcnode->GetAttributeValue ("name"); const char* tagname = pcnode->GetAttributeValue ("tag"); if (tagname) pc = entity->GetPropertyClassList ()->FindByNameAndTag (pcname, tagname); else pc = entity->GetPropertyClassList ()->FindByName (pcname); bool loadpcdata = true; if (attr != 0) { // A reference. if (pc) return true; // We couldn't find the pc. So we create it here by falling through // to the next code. Later on that pc will be picked up. // We set loadpcdata to false because we don't want to load the // PC data here. We only want to create it so that it can be used // later. loadpcdata = false; } if (!pc) { iCelPropertyClassFactory* pf = pl->FindPropertyClassFactory (pcname); if (!pf) return Report ("Couldn't create property class '%s'!", pcname); csRef pcref = pf->CreatePropertyClass(); pc = pcref; if (tagname) pc->SetTag (tagname); pc->SetEntity (entity); printf ("Adding PC '%s' to Entity '%s'\n", pcname, entity->GetName()); entity->GetPropertyClassList ()->Add (pc); } if (loadpcdata) { csRef db; if (!Read (pcnode, db)) return Report ("Error loading property class '%s'!", pcname); if (!pc->Load (db)) return Report ("Error loading property class '%s'!", pcname); } return true; } bool celPersistXML::Read (iDocumentNode* entnode, iCelEntity*& entity) { csRef attr; attr = entnode->GetAttribute ("null"); if (attr != 0) { entity = 0; return true; } attr = entnode->GetAttribute ("extref"); if (attr != 0) { csRef db; if (!Read (entnode, db)) return Report ("Error reading external entity reference!"); entity = set->FindExternalEntity (db); return true; } attr = entnode->GetAttribute ("locref"); if (attr != 0) { // A reference. uint32 entid = (uint32)attr->GetValueAsInt (); entity = set->GetEntity (entid); return true; } // In this case we know the entity is already given to this // routine. const char* entname = entnode->GetAttributeValue ("name"); if (entname) entity->SetName (entname); csRef it = entnode->GetNodes (); while (it->HasNext ()) { csRef pcnode = it->Next (); if (pcnode->GetType () != CS_NODE_ELEMENT) continue; const char* value = pcnode->GetValue (); if (!strcmp ("pc", value)) { iCelPropertyClass* pc; if (!Read (pcnode, entity, pc)) { entity = 0; return false; } } else if (!strcmp ("behaviour", value)) { const char* layer = pcnode->GetAttributeValue ("layer"); const char* bname = pcnode->GetAttributeValue ("name"); iCelBlLayer* bl = pl->FindBehaviourLayer (layer); if (!bl) return Report ("Couldn't find behaviour layer '%s'!", layer); iCelBehaviour* bh = bl->CreateBehaviour (entity, bname); if (!bh) return Report ("Couldn't create behaviour '%s'!", bname); bh->DecRef (); } else return Report ("File not valid: expected 'pc' in entity '%s'!", entname); } return true; } bool celPersistXML::ReadFirstPass (iDocumentNode* entnode, iCelEntity* entity) { csRef it = entnode->GetNodes (); while (it->HasNext ()) { csRef pcnode = it->Next (); if (pcnode->GetType () != CS_NODE_ELEMENT) continue; const char* value = pcnode->GetValue (); if (!strcmp ("pc", value)) { const char* pcname = pcnode->GetAttributeValue ("name"); const char* tagname = pcnode->GetAttributeValue ("tag"); iCelPropertyClassFactory* pf = pl->FindPropertyClassFactory (pcname); if (!pf) return Report ("Couldn't create property class '%s'!", pcname); csRef pc = pf->CreatePropertyClass(); if (tagname) pc->SetTag (tagname); pc->SetEntity (entity); entity->GetPropertyClassList ()->Add (pc); csRef db; if (!Read (pcnode, db)) return Report ("Error loading property class '%s'!", pcname); if (!pc->LoadFirstPass (db)) return Report ("Error loading property class '%s'!", pcname); } else return Report ("File not valid: expected 'pc' in entity!"); } return true; } //------------------------------------------------------------------------ bool celPersistXML::Load (iCelLocalEntitySet* set, iFile* file) { csRef xml = CS_QUERY_REGISTRY (object_reg, iDocumentSystem); if (!xml) xml.AttachNew (new csTinyDocumentSystem ()); csRef doc = xml->CreateDocument (); const char* error = doc->Parse (file, true); if (error != 0) return Report ("Error parsing document: '%s'!", error); celPersistXML::set = set; csRef parent = doc->GetRoot ()->GetNode ("celentities"); if (!parent) return Report ("File doesn't seem to be a valid XML save file!"); csRef firstpass = parent->GetNode ("firstpass"); if (!firstpass) return Report ("File not valid: 'firstpass' missing!"); csRef secondpass = parent->GetNode ("secondpass"); if (!secondpass) return Report ("File not valid: 'secondpass' missing!"); size_t idx = 0; entities_map.DeleteAll (); // --- First pass ----------------------------------- csRef it = firstpass->GetNodes (); while (it->HasNext ()) { csRef entnode = it->Next (); if (entnode->GetType () != CS_NODE_ELEMENT) continue; const char* value = entnode->GetValue (); if (!strcmp ("entity", value)) { csRef ent = pl->CreateEntity (); entities_map.Put ((iCelEntity*)ent, idx); set->AddEntity (ent); idx++; if (!ReadFirstPass (entnode, ent)) return false; } else return Report ("File doesn't seem to be valid: expected 'entity'!"); } // --- Second pass ---------------------------------- // Loop again to actually load the entities. idx = 0; it = secondpass->GetNodes (); while (it->HasNext ()) { csRef entnode = it->Next (); if (entnode->GetType () != CS_NODE_ELEMENT) continue; // No need to check for entity anymore. Previous loop does that. iCelEntity* ent = set->GetEntity (idx); idx++; if (!Read (entnode, ent)) return false; } return true; } bool celPersistXML::Load (iCelLocalEntitySet* set, const char* name) { csRef rep (CS_QUERY_REGISTRY (object_reg, iReporter)); if (rep) rep->Report (CS_REPORTER_SEVERITY_NOTIFY, "cel.persist.xml", "Loading file '%s'.", name); else { csPrintf ("Loading file '%s'.\n", name); fflush (stdout); } csRef file = vfs->Open (name, VFS_FILE_READ); if (!file) return Report ("Failed to read file '%s'!", name); return Load (set, file); } bool celPersistXML::Save (iCelLocalEntitySet* set, iFile* file) { celPersistXML::set = set; csRef xml = CS_QUERY_REGISTRY (object_reg, iDocumentSystem); if (!xml) xml.AttachNew (new csTinyDocumentSystem ()); csRef doc = xml->CreateDocument (); csRef root = doc->CreateRoot (); csRef parent = root->CreateNodeBefore (CS_NODE_ELEMENT, 0); parent->SetValue ("celentities"); size_t i; entities_map.DeleteAll (); for (i = 0 ; i < set->GetEntityCount () ; i++) entities_map.Put (set->GetEntity (i), i); // --- First pass ----------------------------------- csRef firstpass = parent->CreateNodeBefore ( CS_NODE_ELEMENT, 0); firstpass->SetValue ("firstpass"); for (i = 0 ; i < set->GetEntityCount () ; i++) { iCelEntity* ent = set->GetEntity (i); csRef entnode = firstpass->CreateNodeBefore ( CS_NODE_ELEMENT, 0); entnode->SetValue ("entity"); if (!WriteFirstPass (entnode, ent)) return Report ("Error writing entity!"); } // --- Second pass ---------------------------------- csRef secondpass = parent->CreateNodeBefore ( CS_NODE_ELEMENT, 0); secondpass->SetValue ("secondpass"); for (i = 0 ; i < set->GetEntityCount () ; i++) { iCelEntity* ent = set->GetEntity (i); csRef entnode = secondpass->CreateNodeBefore ( CS_NODE_ELEMENT, 0); entnode->SetValue ("entity"); if (!Write (entnode, ent, true)) return Report ("Error writing entity!"); } if (doc->Write (file) != 0) return Report ("Error writing file!"); return true; } bool celPersistXML::Save (iCelLocalEntitySet* set, const char* name) { csRef rep (CS_QUERY_REGISTRY (object_reg, iReporter)); if (rep) rep->Report (CS_REPORTER_SEVERITY_NOTIFY, "cel.persist.xml", "Saving to file '%s'.", name); else { csPrintf ("Saving to file '%s'.\n", name); fflush (stdout); } csMemFile m; csRef mf = SCF_QUERY_INTERFACE (&m, iFile); if (Save (set, mf)) { vfs->WriteFile (name, m.GetData (), m.GetSize ()); return true; } else { return false; } } //------------------------------------------------------------------------