/* * OpenClonk, http://www.openclonk.org * * Copyright (c) 2004, Peter Wortmann * Copyright (c) 2007, Günther Brammer * Copyright (c) 2009-2013, The OpenClonk Team and contributors * * Distributed under the terms of the ISC license; see accompanying file * "COPYING" for details. * * "Clonk" is a registered trademark of Matthes Bender, used with permission. * See accompanying file "TRADEMARK" for details. * * To redistribute this file separately, substitute the full license texts * for the above references. */ #include #include #include #include #include #include void C4PropList::AddRef(C4Value *pRef) { #ifdef _DEBUG C4Value * pVal = FirstRef; while (pVal) { assert(pVal != pRef); pVal = pVal->NextRef; } #endif pRef->NextRef = FirstRef; FirstRef = pRef; } void C4PropList::DelRef(const C4Value * pRef, C4Value * pNextRef) { assert(FirstRef); // References to objects never have HasBaseArray set if (pRef == FirstRef) { FirstRef = pNextRef; if (pNextRef) return; } else { C4Value *pPrev = FirstRef; while (pPrev->NextRef != pRef) { pPrev = pPrev->NextRef; assert(pPrev); } pPrev->NextRef = pNextRef; return; } // Only pure script proplists are garbage collected here, host proplists // like definitions and effects have their own memory management. if (Delete()) delete this; } C4PropList * C4PropList::New(C4PropList * prototype) { C4PropList * r = new C4PropListScript(prototype); return r; } C4PropListStatic * C4PropList::NewStatic(C4PropList * prototype, const C4PropListStatic * parent, C4String * key) { return new C4PropListStatic(prototype, parent, key); } C4PropList *C4PropListNumbered::GetByNumber(int32_t iNumber) { return PropLists.Get(iNumber); } bool C4PropListNumbered::CheckPropList(C4PropList *pObj) { if (!pObj) return false; C4PropListNumbered * const * p = PropLists.First(); while (p) { if (*p == pObj) return true; p = PropLists.Next(p); } return false; } void C4PropListNumbered::SetEnumerationIndex(int32_t iMaxObjectNumber) { // update object enumeration index now, because calls like OnSynchronized might create objects EnumerationIndex = Max(EnumerationIndex, iMaxObjectNumber); } void C4PropListNumbered::ResetEnumerationIndex() { assert(!PropLists.GetSize()); EnumerationIndex = 0; } void C4PropListNumbered::ShelveNumberedPropLists() { // unnumber all proplists and put them on the shelve. To be used on remaining objects before a savegame load. assert(ShelvedPropLists.empty()); ShelvedPropLists.reserve(PropLists.GetSize()); C4PropListNumbered *const* p_next = PropLists.First(), *const* p; while ((p = p_next)) { p_next = PropLists.Next(p); C4PropListNumbered *pl = *p; if (pl->Number != -1) { pl->ClearNumber(); ShelvedPropLists.push_back(pl); } } } void C4PropListNumbered::UnshelveNumberedPropLists() { // re-insert shelved proplists into main list and give them a number for (std::vector::iterator i=ShelvedPropLists.begin(); i!=ShelvedPropLists.end(); ++i) (*i)->AcquireNumber(); ShelvedPropLists.clear(); } void C4PropListNumbered::ClearShelve() { // cleanup shelve - used in game clear, un unsuccessful section load, etc. ShelvedPropLists.clear(); } void C4PropListNumbered::ClearNumberedPropLists() { // empty all proplists to ensure safe deletion of proplists with circular references // note that this the call to Clear() might delete some prop lists. So it is assumed that // PropLists does not shrink its table as the number of values goes down C4PropListNumbered *const* p_next = PropLists.First(), *const* p; while ((p = p_next)) { p_next = PropLists.Next(p); // check *p since it might have been deleted by clearing the previous prop list if (*p) (*p)->Clear(); } } C4PropListNumbered::C4PropListNumbered(C4PropList * prototype): C4PropList(prototype), Number(-1) { } void C4PropListNumbered::AcquireNumber() { // Enumerate object do Number = ++EnumerationIndex; while (PropLists.Get(Number)); // Add to list PropLists.Add(this); } void C4PropListNumbered::ClearNumber() { // Make proplist invisible during denumeration process if (Number != -1) { PropLists.Remove(this); Number = -1; } } C4PropListNumbered* C4PropListNumbered::GetPropListNumbered() { return this; } void C4PropListNumbered::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) { int32_t n = Number; pComp->Value(n); pComp->Separator(StdCompiler::SEP_SEP2); C4PropList::CompileFunc(pComp, numbers); if (pComp->isCompiler()) { if (PropLists.Get(n)) { pComp->excCorrupt("multiple PropLists with Number %d", n); return; } // Once this proplist has a Number, it has to be in PropLists. See the destructor below. Number = n; PropLists.Add(this); } } C4PropListNumbered::~C4PropListNumbered() { if (Number != -1) PropLists.Remove(this); else Log("removing numbered proplist without number"); } void C4PropListScript::ClearScriptPropLists() { // empty all proplists to ensure safe deletion of proplists with circular references // note that this the call to Clear() might delete some prop lists. So it is assumed that // PropLists does not shrink its table as the number of values goes down // However, some values may be skipped due to table consolidation. Just fix it by iterating over the table until it's empty. C4PropListScript *const* p_next, *const* p; while ((p_next = PropLists.First())) { size_t prev_size = PropLists.GetSize(); while ((p = p_next)) { p_next = PropLists.Next(p); // check *p since it might have been deleted by clearing the previous prop list if (*p) { C4Value ref(C4VPropList(*p)); // keep a reference because prop list might delete itself within clearing method otherwise (*p)->Clear(); } } if (PropLists.GetSize() >= prev_size) { // Looks like there's a rogue C4Value pointer somewhere. // Could just delete the prop list and let ref counting do the job // However, it might be better to keep the dead pointer to find the leak in debug mode #ifdef _DEBUG assert(0); #endif break; } } } void C4PropListStatic::RefCompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) const { assert(!pComp->isCompiler()); if (Parent) { Parent->RefCompileFunc(pComp, numbers); pComp->Separator(StdCompiler::SEP_PART); } if (!ParentKeyName) pComp->excCorrupt("C4PropListStatic::RefCompileFunc without ParentKeyName"); pComp->Value(mkParAdapt(ParentKeyName->GetData(), StdCompiler::RCT_ID)); } StdStrBuf C4PropListStatic::GetDataString() const { StdStrBuf r; if (Parent) { r.Take(Parent->GetDataString()); r.AppendChar('.'); } assert(ParentKeyName); if (ParentKeyName) r.Append(ParentKeyName->GetData()); return r; } C4PropList::C4PropList(C4PropList * prototype): FirstRef(NULL), prototype(prototype), constant(false), Status(1) { #ifdef _DEBUG PropLists.Add(this); #endif } void C4PropList::Denumerate(C4ValueNumbers * numbers) { const C4Property * p = Properties.First(); while (p) { const_cast(p->Value).Denumerate(numbers); p = Properties.Next(p); } prototype.Denumerate(numbers); RemoveCyclicPrototypes(); } C4PropList::~C4PropList() { while (FirstRef) { // Manually kill references so DelRef doesn't destroy us again FirstRef->Data = 0; FirstRef->Type = C4V_Nil; C4Value *ref = FirstRef; FirstRef = FirstRef->NextRef; ref->NextRef = NULL; } #ifdef _DEBUG assert(PropLists.Has(this)); PropLists.Remove(this); #endif assert(!C4PropListNumbered::CheckPropList(this)); } bool C4PropList::operator==(const C4PropList &b) const { // every numbered proplist has a unique number and is only identical to itself if (this == &b) return true; if (IsNumbered() || b.IsNumbered()) return false; if (Properties.GetSize() != b.Properties.GetSize()) return false; if (GetDef() != b.GetDef()) return false; const C4Property * p = Properties.First(); while (p) { const C4Property & bp = b.Properties.Get(p->Key); if (!bp) return false; if (p->Value != bp.Value) return false; p = Properties.Next(p); } return true; } void C4PropList::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) { bool oldFormat = false; // constant proplists are not serialized to savegames, but recreated from the game data instead assert(!constant); if (pComp->isCompiler() && pComp->hasNaming()) { // backwards compat to savegames and scenarios before 5.5 try { pComp->Value(constant); oldFormat = true; } catch (StdCompiler::NotFoundException *pEx) { delete pEx; pComp->Value(mkParAdapt(prototype, numbers)); } } else pComp->Value(mkParAdapt(prototype, numbers)); pComp->Separator(StdCompiler::SEP_SEP2); pComp->Value(mkParAdapt(Properties, numbers)); if (oldFormat) { if (Properties.Has(&::Strings.P[P_Prototype])) { prototype = Properties.Get(&::Strings.P[P_Prototype]).Value; Properties.Remove(&::Strings.P[P_Prototype]); } } } void C4PropList::RemoveCyclicPrototypes() { // clear any cyclic prototype chains // Use prototype.getPropList() instead of GetPrototype() because denumeration might not be completed yet for(C4PropList * it = prototype.getPropList(); it; it = it->prototype.getPropList()) if(it == this) { prototype.Set0(); } } void CompileNewFunc(C4PropList *&pStruct, StdCompiler *pComp, C4ValueNumbers * const & rPar) { std::unique_ptr temp(C4PropList::New()); // exception-safety pComp->Value(mkParAdapt(*temp, rPar)); pStruct = temp.release(); } template void C4Set::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) { bool fNaming = pComp->hasNaming(); if (pComp->isCompiler()) { // Compiling: Empty previous Clear(); // Read size (binary only) uint32_t iSize; if (!pComp->hasNaming()) pComp->Value(iSize); // Read new do { // No entries left to read? if (!fNaming && !iSize--) break; // Read entries try { T e; // This could use the same technique StdArrayAdapt uses // instead of hardcoding mkParAdapt here pComp->Value(mkParAdapt(e, numbers)); Add(e); } catch (StdCompiler::NotFoundException *pEx) { // No value found: Stop reading loop delete pEx; break; } } while (pComp->Separator(StdCompiler::SEP_SEP)); } else { // Write size (binary only) if (!fNaming) { int32_t iSize = GetSize(); pComp->Value(iSize); } // Write all entries const T * p = First(); while (p) { pComp->Value(mkParAdapt(*const_cast(p), numbers)); p = Next(p); if (p) pComp->Separator(StdCompiler::SEP_SEP); } } } void C4Property::CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) { StdStrBuf s; if (!pComp->isCompiler()) s = Key->GetData(); pComp->Value(s); if (pComp->isCompiler()) { if (Key) Key->DecRef(); Key = ::Strings.RegString(s); Key->IncRef(); } pComp->Separator(StdCompiler::SEP_SET); pComp->Value(mkParAdapt(Value, numbers)); } void C4PropList::AppendDataString(StdStrBuf * out, const char * delim, int depth) const { StdStrBuf & DataString = *out; if (depth <= 0 && Properties.GetSize()) { DataString.Append("..."); return; } std::list sorted_props = Properties.GetSortedListOfElementPointers(); for (std::list::const_iterator p = sorted_props.begin(); p != sorted_props.end(); ++p) { if (p != sorted_props.begin()) DataString.Append(delim); DataString.Append((*p)->Key->GetData()); DataString.Append(" = "); DataString.Append((*p)->Value.GetDataString(depth - 1)); } } const char * C4PropList::GetName() const { C4String * s = GetPropertyStr(P_Name); if (!s) return ""; return s->GetCStr(); } void C4PropList::SetName(const char* NewName) { if (!NewName) ResetProperty(&Strings.P[P_Name]); else { SetProperty(P_Name, C4VString(NewName)); } } C4Object * C4PropList::GetObject() { if (GetPrototype()) return GetPrototype()->GetObject(); return 0; } C4Def * C4PropList::GetDef() { if (GetPrototype()) return GetPrototype()->GetDef(); return 0; } C4Def const * C4PropList::GetDef() const { if (GetPrototype()) return GetPrototype()->GetDef(); return 0; } class C4MapScriptLayer * C4PropList::GetMapScriptLayer() { if (GetPrototype()) return GetPrototype()->GetMapScriptLayer(); return NULL; } class C4MapScriptMap * C4PropList::GetMapScriptMap() { if (GetPrototype()) return GetPrototype()->GetMapScriptMap(); return NULL; } C4PropListNumbered * C4PropList::GetPropListNumbered() { if (GetPrototype()) return GetPrototype()->GetPropListNumbered(); return 0; } C4Effect * C4PropList::GetEffect() { if (GetPrototype()) return GetPrototype()->GetEffect(); return 0; } template<> template<> unsigned int C4Set::Hash(C4String * const & e) { assert(e); unsigned int hash = 4, tmp; hash += ((uintptr_t)e) >> 16; tmp = ((((uintptr_t)e) & 0xffff) << 11) ^ hash; hash = (hash << 16) ^ tmp; hash += hash >> 11; hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } template<> template<> bool C4Set::Equals(C4Property const & a, C4String * const & b) { return a.Key == b; } template<> template<> unsigned int C4Set::Hash(C4Property const & p) { return C4Set::Hash(p.Key); } bool C4PropList::GetPropertyByS(C4String * k, C4Value *pResult) const { if (Properties.Has(k)) { *pResult = Properties.Get(k).Value; return true; } else if (k == &Strings.P[P_Prototype]) { *pResult = prototype; return true; } else if(GetPrototype()) return GetPrototype()->GetPropertyByS(k, pResult); else return false; } C4String * C4PropList::GetPropertyStr(C4PropertyName n) const { C4String * k = &Strings.P[n]; if (Properties.Has(k)) { return Properties.Get(k).Value.getStr(); } if (GetPrototype()) { return GetPrototype()->GetPropertyStr(n); } return 0; } C4ValueArray * C4PropList::GetPropertyArray(C4PropertyName n) const { C4String * k = &Strings.P[n]; if (Properties.Has(k)) { return Properties.Get(k).Value.getArray(); } if (GetPrototype()) { return GetPrototype()->GetPropertyArray(n); } return 0; } C4AulFunc * C4PropList::GetFunc(C4String * k) const { assert(k); if (Properties.Has(k)) { return Properties.Get(k).Value.getFunction(); } if (GetPrototype()) { return GetPrototype()->GetFunc(k); } return 0; } C4AulFunc * C4PropList::GetFunc(const char * s) const { assert(s); if (s[0] == '~') ++s; C4String * k = Strings.FindString(s); // this string is entirely unused if (!k) return 0; return GetFunc(k); } C4Value C4PropList::Call(C4String * k, C4AulParSet *Pars, bool fPassErrors) { if (!Status) return C4Value(); C4AulFunc *pFn = GetFunc(k); if (!pFn) return C4Value(); return pFn->Exec(this, Pars, fPassErrors); } C4Value C4PropList::Call(const char * s, C4AulParSet *Pars, bool fPassErrors) { if (!Status) return C4Value(); assert(s && s[0]); C4AulFunc *pFn = GetFunc(s); if (!pFn) return C4Value(); return pFn->Exec(this, Pars, fPassErrors); } C4PropertyName C4PropList::GetPropertyP(C4PropertyName n) const { C4String * k = &Strings.P[n]; if (Properties.Has(k)) { C4String * v = Properties.Get(k).Value.getStr(); if (v >= &Strings.P[0] && v < &Strings.P[P_LAST]) return C4PropertyName(v - &Strings.P[0]); return P_LAST; } if (GetPrototype()) { return GetPrototype()->GetPropertyP(n); } return P_LAST; } int32_t C4PropList::GetPropertyInt(C4PropertyName n, int32_t default_val) const { C4String * k = &Strings.P[n]; if (Properties.Has(k)) { return Properties.Get(k).Value.getInt(); } if (GetPrototype()) { return GetPrototype()->GetPropertyInt(n, default_val); } return default_val; } bool C4PropList::GetPropertyBoolByS(C4String * k, bool default_val) const { if (Properties.Has(k)) { return Properties.Get(k).Value.getBool(); } if (GetPrototype()) { return GetPrototype()->GetPropertyBoolByS(k, default_val); } return default_val; } C4PropList *C4PropList::GetPropertyPropList(C4PropertyName n) const { C4String * k = &Strings.P[n]; if (Properties.Has(k)) { return Properties.Get(k).Value.getPropList(); } if (GetPrototype()) { return GetPrototype()->GetPropertyPropList(n); } return NULL; } C4ValueArray * C4PropList::GetProperties() const { C4ValueArray * a; int i; if (GetPrototype()) { a = GetPrototype()->GetProperties(); i = a->GetSize(); a->SetSize(i + Properties.GetSize()); } else { a = new C4ValueArray(Properties.GetSize()); i = 0; } const C4Property * p = Properties.First(); while (p) { assert(p->Key != nullptr && "Proplist key is nullpointer"); (*a)[i++] = C4VString(p->Key); assert(((*a)[i - 1].GetType() == C4V_String) && "Proplist key is non-string"); p = Properties.Next(p); } return a; } C4String * C4PropList::EnumerateOwnFuncs(C4String * prev) const { const C4Property * p = prev ? Properties.Next(&Properties.Get(prev)) : Properties.First(); while (p) { if (p->Value.getFunction()) return p->Key; p = Properties.Next(p); } return 0; } void C4PropList::SetPropertyByS(C4String * k, const C4Value & to) { assert(!constant); if (k == &Strings.P[P_Prototype]) { C4PropList * newpt = to.getPropList(); for(C4PropList * it = newpt; it; it = it->GetPrototype()) if(it == this) throw new C4AulExecError("Trying to create cyclic prototype structure"); prototype.SetPropList(newpt); } else if (Properties.Has(k)) { Properties.Get(k).Value = to; } else { Properties.Add(C4Property(k, to)); } } void C4PropList::ResetProperty(C4String * k) { if (k == &Strings.P[P_Prototype]) prototype.Set0(); else Properties.Remove(k); } void C4PropList::Iterator::Init() { iter = properties->begin(); } void C4PropList::Iterator::Reserve(size_t additionalAmount) { properties->reserve(properties->size() + additionalAmount); } void C4PropList::Iterator::AddProperty(const C4Property * prop) { std::vector::size_type i = 0, len = properties->size(); for(;i < len; ++i) { const C4Property *oldProperty = (*properties)[i]; if (oldProperty->Key == prop->Key) { (*properties)[i] = prop; return; } } // not already in vector? properties->push_back(prop); } C4PropList::Iterator C4PropList::begin() { C4PropList::Iterator iter; if (GetPrototype()) { iter = GetPrototype()->begin(); } else { iter.properties = std::make_shared >(); } iter.Reserve(Properties.GetSize()); const C4Property * p = Properties.First(); while (p) { iter.AddProperty(p); p = Properties.Next(p); } iter.Init(); return iter; } template<> template<> unsigned int C4Set::Hash(int const & e) { unsigned int hash = 4, tmp; hash += e >> 16; tmp = ((e & 0xffff) << 11) ^ hash; hash = (hash << 16) ^ tmp; hash += hash >> 11; hash ^= hash << 3; hash += hash >> 5; hash ^= hash << 4; hash += hash >> 17; hash ^= hash << 25; hash += hash >> 6; return hash; } template<> template<> unsigned int C4Set::Hash(C4PropList * const & e) { return Hash(e->GetPropListNumbered()->Number); } template<> template<> unsigned int C4Set::Hash(C4PropListNumbered * const & e) { return Hash(e->Number); } template<> template<> bool C4Set::Equals(C4PropListNumbered * const & a, int const & b) { return a->Number == b; } template<> template<> bool C4Set::Equals(C4PropListNumbered * const & a, C4PropList * const & b) { return a == b; } template<> template<> unsigned int C4Set::Hash(C4PropList * const & e) { return C4Set::Hash(static_cast(reinterpret_cast(e))); } template<> template<> unsigned int C4Set::Hash(C4PropListScript * const & e) { // since script prop lists are only put in the set for reference keeping, just hash by pointer // but use only some of the more significant bits because uintptr_t hash = reinterpret_cast(e); return (unsigned int)(hash / 63); }