1 /*
2  * OpenClonk, http://www.openclonk.org
3  *
4  * Copyright (c) 2009-2016, The OpenClonk Team and contributors
5  *
6  * Distributed under the terms of the ISC license; see accompanying file
7  * "COPYING" for details.
8  *
9  * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10  * See accompanying file "TRADEMARK" for details.
11  *
12  * To redistribute this file separately, substitute the full license texts
13  * for the above references.
14  */
15 
16 /* Property lists */
17 
18 #include "script/C4Value.h"
19 #include "script/C4StringTable.h"
20 
21 #ifndef C4PROPLIST_H
22 #define C4PROPLIST_H
23 
24 /* C4PropList have a somewhat complicated reference counting scheme. You can:
25  - Store a C4Proplist* in a C4Value. This is the preferred and often only way.
26  - Store a C4Object* from GetObject in a C4Object* or C4PropList* if there is a ClearPointer function for it
27    Use a C4ObjectPtr for help with storing the Object in Savegames.
28  - Store a C4Def* from GetDef() in a C4Def* or C4PropList*
29 
30 All PropLists can be destroyed while there are still C4Values referencing them, though
31 Definitions do not get destroyed during the game. So always check for nullpointers.
32 
33 The linked list formed by C4PropList::FirstRef and C4Value::NextRef is used to
34 change all C4Values referencing the destroyed Proplist to contain nil instead.
35 Objects are also cleaned up via various ClearPointer functions.
36 The list is also used as a reference count to remove unused Proplists.
37 The exception are C4PropListNumbered and C4Def, which have implicit references
38 from C4GameObjects, C4Object and C4DefList. They have to be destroyed when loosing that reference.*/
39 
40 class C4Property
41 {
42 public:
43 	C4Property() = default;
C4Property(C4String * Key,const C4Value & Value)44 	C4Property(C4String *Key, const C4Value &Value) : Key(Key), Value(Value)
45 	{ assert(Key); Key->IncRef(); }
C4Property(const C4Property & o)46 	C4Property(const C4Property &o) : Key(o.Key), Value(o.Value) { if (Key) Key->IncRef(); }
47 	C4Property & operator = (const C4Property &o)
48 	{ if(o.Key) o.Key->IncRef(); if (Key) Key->DecRef(); Key = o.Key; Value = o.Value; return *this; }
C4Property(C4Property && o)49 	C4Property(C4Property && o) : Key(o.Key), Value(std::move(o.Value)) { o.Key = nullptr; }
50 	C4Property & operator = (C4Property && o)
51 	{ if (Key) Key->DecRef(); Key = o.Key; o.Key = nullptr; Value = std::move(o.Value); return *this; }
~C4Property()52 	~C4Property() { if (Key) Key->DecRef(); }
53 	void CompileFunc(StdCompiler *pComp, C4ValueNumbers *);
54 	C4String * Key{nullptr};
55 	C4Value Value;
56 	operator const void * () const { return Key; }
57 	C4Property & operator = (void * p)
58 	{ assert(!p); if (Key) Key->DecRef(); Key = nullptr; Value.Set0(); return *this; }
59 	bool operator < (const C4Property &cmp) const { return strcmp(GetSafeKey(), cmp.GetSafeKey())<0; }
GetSafeKey()60 	const char *GetSafeKey() const { if (Key && Key->GetCStr()) return Key->GetCStr(); return ""; } // get key as C string; return "" if undefined. never return nullptr
61 };
62 class C4PropListNumbered;
63 class C4PropList
64 {
65 public:
Clear()66 	void Clear() { constant = false; Properties.Clear(); prototype.Set0(); }
67 	virtual const char *GetName() const;
68 	virtual void SetName (const char *NewName = nullptr);
SetOnFire(bool OnFire)69 	virtual void SetOnFire(bool OnFire) { }
70 
71 	// These functions return this or a prototype.
72 	virtual C4Def const * GetDef() const;
73 	virtual C4Def * GetDef();
74 	virtual C4Object * GetObject();
75 	virtual C4Object const * GetObject() const;
76 	virtual C4Effect * GetEffect();
77 	virtual C4PropListNumbered * GetPropListNumbered();
78 	virtual class C4MapScriptLayer * GetMapScriptLayer();
79 	virtual class C4MapScriptMap * GetMapScriptMap();
80 
GetPrototype()81 	C4PropList * GetPrototype() const { return prototype._getPropList(); }
82 	void RemoveCyclicPrototypes();
83 
84 	// saved as a reference to a global constant?
IsStatic()85 	virtual class C4PropListStatic * IsStatic() { return nullptr; }
IsStatic()86 	const class C4PropListStatic * IsStatic() const { return const_cast<C4PropList*>(this)->IsStatic(); }
87 	// saved as a reference to separately saved objects?
IsNumbered()88 	virtual bool IsNumbered() const { return false; }
89 	// some proplists have references that are not reference-counted
Delete()90 	virtual bool Delete() { return false; }
91 
92 	// These four operate on properties as seen by script, which can be dynamic
93 	// or reflect C++ variables
94 	virtual bool GetPropertyByS(const C4String *k, C4Value *pResult) const;
95 	virtual C4ValueArray * GetProperties() const;
96 	// not allowed on frozen proplists
97 	virtual void SetPropertyByS(C4String * k, const C4Value & to);
98 	virtual void ResetProperty(C4String * k);
99 
100 	// helper functions to get dynamic properties from other parts of the engine
GetProperty(C4PropertyName k,C4Value * pResult)101 	bool GetProperty(C4PropertyName k, C4Value *pResult) const
102 	{ return GetPropertyByS(&Strings.P[k], pResult); }
103 	C4String * GetPropertyStr(C4PropertyName k) const;
104 	C4ValueArray * GetPropertyArray(C4PropertyName n) const;
GetFunc(C4PropertyName k)105 	C4AulFunc * GetFunc(C4PropertyName k) const
106 	{ return GetFunc(&Strings.P[k]); }
107 	C4AulFunc * GetFunc(C4String * k) const;
108 	C4AulFunc * GetFunc(const char * k) const;
109 	C4String * EnumerateOwnFuncs(C4String * prev = nullptr) const;
110 	C4Value Call(C4PropertyName k, C4AulParSet *pPars=nullptr, bool fPassErrors=false)
111 	{ return Call(&Strings.P[k], pPars, fPassErrors); }
112 	C4Value Call(C4String * k, C4AulParSet *pPars=nullptr, bool fPassErrors=false);
113 	C4Value Call(const char * k, C4AulParSet *pPars=nullptr, bool fPassErrors=false);
114 	C4PropertyName GetPropertyP(C4PropertyName k) const;
115 	int32_t GetPropertyBool(C4PropertyName n, bool default_val = false) const;
116 	int32_t GetPropertyInt(C4PropertyName k, int32_t default_val = 0) const;
117 	C4PropList *GetPropertyPropList(C4PropertyName k) const;
HasProperty(C4String * k)118 	bool HasProperty(C4String * k) const { return Properties.Has(k); }
119 	// not allowed on frozen proplists
SetProperty(C4PropertyName k,const C4Value & to)120 	void SetProperty(C4PropertyName k, const C4Value & to)
121 	{ SetPropertyByS(&Strings.P[k], to); }
122 
123 	static C4PropList * New(C4PropList * prototype = nullptr);
124 	static C4PropListStatic * NewStatic(C4PropList * prototype, const C4PropListStatic * parent, C4String * key);
125 
126 	// only freeze proplists which are not going to be modified
127 	// FIXME: Only C4PropListStatic get frozen. Optimize accordingly.
Freeze()128 	void Freeze() { constant = true; }
Thaw()129 	void Thaw() { constant = false; }
130 	void ThawRecursively();
IsFrozen()131 	bool IsFrozen() const { return constant; }
132 
133 	// Freeze this and all proplist in properties and ensure they are static proplists
134 	// If a proplist is not static, replace it with a static proplist and replace all instances
135 	// Place references to all proplists made static in the given value array
136 	C4PropListStatic *FreezeAndMakeStaticRecursively(std::vector<C4Value>* prop_lists, const C4PropListStatic *parent = nullptr, C4String * key = nullptr);
137 
138 	virtual void Denumerate(C4ValueNumbers *);
139 	virtual ~C4PropList();
140 
141 	void CompileFunc(StdCompiler *pComp, C4ValueNumbers *);
142 	void AppendDataString(StdStrBuf * out, const char * delim, int depth = 3, bool ignore_reference_parent = false) const;
143 	StdStrBuf ToJSON(int depth = 10, bool ignore_reference_parent = false) const;
144 	std::vector< C4String * > GetSortedLocalProperties(bool add_prototype=true) const;
145 	std::vector< C4String * > GetSortedLocalProperties(const char *prefix, const C4PropList *ignore_overridden) const;
146 	std::vector< C4String * > GetUnsortedProperties(const char *prefix, C4PropList *ignore_parent = nullptr) const;
147 	std::vector< C4String * > GetSortedProperties(const char *prefix, C4PropList *ignore_parent = nullptr) const;
148 
149 	bool operator==(const C4PropList &b) const;
150 #ifdef _DEBUG
151 	static C4Set<C4PropList *> PropLists;
152 #endif
153 
154 protected:
155 	C4PropList(C4PropList * prototype = nullptr);
ClearRefs()156 	void ClearRefs() { while (FirstRef) FirstRef->Set0(); }
157 
158 private:
159 	void AddRef(C4Value *pRef);
160 	void DelRef(const C4Value *pRef, C4Value * pNextRef);
161 	C4Value *FirstRef{nullptr}; // No-Save
162 	C4Set<C4Property> Properties;
163 	C4Value prototype;
164 	bool constant{false}; // if true, this proplist is not changeable
165 	friend class C4Value;
166 	friend class C4ScriptHost;
167 public:
168 	int32_t Status{1};
169 
170 	class Iterator
171 	{
172 	private:
173 		std::shared_ptr<std::vector<const C4Property*> > properties;
174 		std::vector<const C4Property*>::iterator iter;
175 		// needed when constructing the iterator
176 		// adds a property or overwrites existing property with same name
177 		void AddProperty(const C4Property * prop);
178 		void Reserve(size_t additionalAmount);
179 		// Initializes internal iterator. Needs to be called before actually using the iterator.
180 		void Init();
181 	public:
Iterator()182 		Iterator() : properties(nullptr) { }
183 
184 		const C4Property * operator*() const { return *iter; }
185 		const C4Property * operator->() const { return *iter; }
186 		void operator++() { ++iter; };
187 		void operator++(int) { operator++(); }
188 
189 		bool operator==(const Iterator & other) const
190 		{
191 			if ((properties == nullptr || iter == properties->end()) && (other.properties == nullptr || other.iter == other.properties->end()))
192 				return true;
193 			return properties == other.properties && iter == other.iter;
194 		}
195 
196 		bool operator!=(const Iterator & other) const
197 		{
198 			return !(*this == other);
199 		}
200 
201 		friend class C4PropList;
202 	};
203 
204 	// do not modify the proplist while iterating over it!
205 	Iterator begin();
end()206 	Iterator end() { return Iterator(); }
207 };
208 
209 void CompileNewFunc(C4PropList *&pStruct, StdCompiler *pComp, C4ValueNumbers *rPar);
210 
211 // Proplists that are created during a game and get saved in a savegame
212 // Examples: Objects, Effects
213 class C4PropListNumbered: public C4PropList
214 {
215 public:
216 	int32_t Number{-1};
217 	~C4PropListNumbered() override;
218 	void CompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers);
219 	C4PropListNumbered * GetPropListNumbered() override;
IsNumbered()220 	bool IsNumbered() const override { return true; }
221 
222 	static C4PropList * GetByNumber(int32_t iNumber); // pointer by number
223 	static bool CheckPropList(C4PropList *); // sanity check: true when the proplist is in the list and not a stale pointer
224 	static void SetEnumerationIndex(int32_t iMaxObjectNumber);
GetEnumerationIndex()225 	static int32_t GetEnumerationIndex() { return EnumerationIndex; }
226 	static void ResetEnumerationIndex();
227 	static void ShelveNumberedPropLists(); // unnumber all proplists and put them on the shelve. To be used on remaining objects before a savegame load.
228 	static void UnshelveNumberedPropLists(); // re-insert shelved proplists into main list
229 	static void ClearShelve();
230 	static void ClearNumberedPropLists(); // empty all properties in numbered prop lists. Used on game clear to ensure prop lists with circular references get cleared.
231 protected:
232 	C4PropListNumbered(C4PropList * prototype = nullptr);
233 	void AcquireNumber(); // acquire a number and add to internal list
234 	void ClearNumber(); // clear number and remove from internal list
235 
236 	static C4Set<C4PropListNumbered *> PropLists;
237 	static std::vector<C4PropListNumbered *> ShelvedPropLists; // temporary storage for existing proplists while a new section loaded
238 	static int32_t EnumerationIndex;
239 	friend class C4Game;
240 	friend class C4GameObjects;
241 };
242 
243 // Proplists created by script at runtime
244 class C4PropListScript: public C4PropList
245 {
246 public:
C4PropList(prototype)247 	C4PropListScript(C4PropList * prototype = nullptr) : C4PropList(prototype) { PropLists.Add(this);  }
~C4PropListScript()248 	~C4PropListScript() override { PropLists.Remove(this); }
Delete()249 	bool Delete() override { return true; }
250 
251 	static void ClearScriptPropLists(); // empty all properties in script-created prop lists. Used on game clear to ensure prop lists with circular references get cleared.
252 
253 protected:
254 	static C4Set<C4PropListScript *> PropLists;
255 };
256 
257 // PropLists declared in the game data
258 // examples: Definitions, local variable initializers
259 class C4PropListStatic: public C4PropList
260 {
261 public:
C4PropListStatic(C4PropList * prototype,const C4PropListStatic * parent,C4String * key)262 	C4PropListStatic(C4PropList * prototype, const C4PropListStatic * parent, C4String * key):
263 		C4PropList(prototype), Parent(parent), ParentKeyName(key) { }
264 	~C4PropListStatic() override = default;
Delete()265 	bool Delete() override { return true; }
IsStatic()266 	C4PropListStatic * IsStatic() override { return this; }
267 	void RefCompileFunc(StdCompiler *pComp, C4ValueNumbers * numbers) const;
268 	StdStrBuf GetDataString() const;
269 	const char *GetName() const override;
GetParent()270 	const C4PropListStatic * GetParent() const { return Parent; }
GetParentKeyName()271 	C4String * GetParentKeyName() { return ParentKeyName; }
272 protected:
273 	const C4PropListStatic * Parent;
274 	C4RefCntPointer<C4String> ParentKeyName; // property in parent this proplist was created in
275 };
276 
277 // static PropList of which another class owns the pointer
278 class C4PropListStaticMember : public C4PropListStatic
279 {
280 public:
C4PropListStaticMember(C4PropList * prototype,const C4PropListStatic * parent,C4String * key)281 	C4PropListStaticMember(C4PropList * prototype, const C4PropListStatic * parent, C4String * key):
282 	  C4PropListStatic(prototype, parent, key) {}
Delete()283 	bool Delete() override { return false; }
284 };
285 
286 #endif // C4PROPLIST_H
287