// A concept should be an object, not just a string. sclass Scenario { Map concepts = synchroTreeMap(); new HashMap perClassData; S programID; long idCounter; volatile long changes = 1, changesWritten; volatile java.util.Timer autoSaver; volatile bool savingConcepts; *() { } *(S *programID) {} long newID() { do { ++idCounter; } while (hasConcept(idCounter)); ret idCounter; } synchronized void loadConceptsFrom(S programID) { clearConcepts(); DynamicObject_loading = true; try { readLocally2(this, programID, "concepts"); assignConceptsToUs(); readLocally2(this, programID, "idCounter"); } finally { DynamicObject_loading = false; } } void assignConceptsToUs() { for (Concept c : values(concepts)) { c.scenario = this; callOpt(c, "_doneLoading2"); } } synchronized void loadConcepts() { loadConceptsFrom(progID()); } S progID() { ret programID == null ? programID() : programID; } Concept getConcept(S id) { ret empty(id) ? null : getConcept(parseLong(id)); } Concept getConcept(long id) { ret (Concept) concepts.get((long) id); } Concept getConcept(PassRef ref) { ret ref == null ? null : getConcept(ref.longID()); } bool hasConcept(long id) { ret concepts.containsKey((long) id); } void deleteConcept(long id) { Concept c = getConcept(id); if (c == null) print("Concept " + id + " not found"); else c.delete(); } // call in only one thread void saveConceptsIfDirty() { savingConcepts = true; try { S s; synchronized(main.class) { if (changes == changesWritten) ret; changesWritten = changes; saveLocally2(this, "idCounter"); s = structure(cloneMap(concepts)); } print("Saving " + l(s) + " chars (" + changesWritten + ")"); saveTextFile(getProgramFile("concepts.structure"), s); copyFile(getProgramFile("concepts.structure"), getProgramFile("concepts.structure.backup" + ymd() + "-" + formatInt(hours(), 2))); } finally { savingConcepts = false; } } void saveConcepts() { saveConceptsIfDirty(); } void clearConcepts() { concepts.clear(); change(); } synchronized void change() { ++changes; } // auto-save every second if dirty synchronized void autoSaveConcepts() { if (autoSaver == null) autoSaver = doEvery(1000, r { saveConcepts(); }); } void cleanMeUp() { if (autoSaver != null) { autoSaver.cancel(); autoSaver = null; } while (savingConcepts) sleep(10); saveConceptsIfDirty(); } Map getIDsAndNames() { new Map map; Map cloned = cloneMap(concepts); for (long id : keys(cloned)) map.put(id, cloned.get(id).className); ret map; } void deleteConcepts(L ids) { concepts.keySet().removeAll(ids); } A findBackRef(Concept c, Class type) { ret findBackRefOfType(c, type); } A findBackRefOfType(Concept c, Class type) { for (Concept.Ref r : c.backRefs) if (instanceOf(r.concept(), type)) ret (A) r.concept(); null; } L findBackRefs(Concept c, Class type) { new L l; for (Concept.Ref r : c.backRefs) if (instanceOf(r.concept(), type)) l.add((A) r.concept()); ret l; } A conceptOfType(Class type) { ret firstOfType(allConcepts(), type); } L conceptsOfType(Class type) { ret filterByType(allConcepts(), type); } L listConcepts(Class type) { ret conceptsOfType(type); } L list(Class type) { ret conceptsOfType(type); } L conceptsOfType(S type) { ret filterByDynamicType(allConcepts(), "main$" + type); } bool hasConceptOfType(Class type) { ret hasType(allConcepts(), type); } void persistConcepts() { loadConcepts(); autoSaveConcepts(); } // We love synonyms void conceptPersistence() { persistConcepts(); } // Runs r if there is no concept of that type A ensureHas(Class c, Runnable r) { A a = conceptOfType(c); if (a == null) { r.run(); a = conceptOfType(c); if (a == null) fail("Concept not made by " + r + ": " + shortClassName(c)); } ret a; } // Ensures that every concept of type c1 is ref'd by a concept of // type c2. // Type of func: voidfunc(concept) void ensureHas(Class c1, Class c2, O func) { for (Concept a : conceptsOfType(c1)) { Concept b = findBackRef(a, c2); if (b == null) { callF(func, a); b = findBackRef(a, c2); if (b == null) fail("Concept not made by " + func + ": " + shortClassName(c2)); } } } // Type of func: voidfunc(concept) void forEvery(Class type, O func) { for (Concept c : conceptsOfType(type)) callF(func, c); } int deleteAll(Class type) { L l = (L) conceptsOfType(type); for (Concept c : l) c.delete(); ret l(l); } Collection allConcepts() { synchronized(concepts) { ret new L(values(concepts)); } } Concept cnew(S name, O... values) { Class cc = findClass(name); Concept c = cc != null ? nuObject(cc) : new Concept(name); csetAll(c, values); ret c; } void csetAll(Concept c, O... values) { for (int i = 0; i+1 < l(values); i += 2) cset(c, (S) values[i], values[i+1]); } void cset(Concept c, S field, O value) ctex { Field f = setOpt_findField(c.getClass(), field); print("cset: " + c.id + " " + field + " " + struct(value) + " " + f); if (value instanceof PassRef) value = getConcept((PassRef) value); if (f == null) c.fieldValues.put(field, value instanceof Concept ? c.new Ref((Concept) value) : value); else if (isSubtypeOf(f.getType(), Concept.Ref.class)) ((Concept.Ref) f.get(c)).set((Concept) value); else f.set(c, value); change(); } O cget(Concept c, S field) { O o = getOpt(c, field); if (o instanceof Concept.Ref) ret ((Concept.Ref) o).get(); ret o; } PassRef toPassRef(Concept c) { ret new PassRef(c); } // inter-process methods PassRef xnew(S name, O... values) { ret new PassRef(cnew(name, values)); } void xset(long id, S field, O value) { xset(new PassRef(id), field, value); } void xset(PassRef c, S field, O value) { if (value instanceof PassRef) value = getConcept((PassRef) value); cset(getConcept(c), field, value); } O xget(long id, S field) { ret xget(new PassRef(id), field); } O xget(PassRef c, S field) { ret cget(getConcept(c), field); } void xdelete(long id) { xdelete(new PassRef(id)); } void xdelete(PassRef c) { getConcept(c).delete(); } L xlist() { ret map("toPassRef", allConcepts()); } L xlist(S className) { ret map("toPassRef", conceptsOfType(className)); } } static volatile new Scenario mainScenario; // Where we create new concepts sclass Concept extends DynamicObject { transient Scenario scenario; // Where we belong long id; O madeBy; //double energy; //bool defunct; long created; // used only internally (cnew) *(S className) { super(className); _created(); } *() { if (!DynamicObject_loading) { className = shortClassName(this); print("New concept of type " + className); _created(); } } new L refs; new L backRefs; void _created() { scenario = mainScenario; id = scenario.newID(); created = now(); scenario.concepts.put((long) id, this); scenario.change(); } void put(S field, O value) { fieldValues.put(field, value); scenario.change(); } O get(S field) { ret fieldValues.get(field); } class Ref { A value; *() { if (!DynamicObject_loading) refs.add(this); } *(A *value) { refs.add(this); index(); } // get owning concept (source) Concept concept() { ret Concept.this; } // get target A get() { ret value; } void set(A a) { if (a == value) ret; unindex(); value = a; index(); } void set(Ref ref) { set(ref.get()); } void index() { if (value != null) value.backRefs.add(this); scenario.change(); } void unindex() { if (value != null) value.backRefs.remove(this); } } class RefL extends AbstractList { new L> l; public A set(int i, A o) { A prev = l.get(i).get(); l.get(i).set(o); ret prev; } public void add(int i, A o) { l.add(i, new Ref(o)); } public A get(int i) { ret l.get(i).get(); } public A remove(int i) { ret l.remove(i).get(); } public int size() { ret l.size(); } } void delete() { scenario.concepts.remove((long) id); id = 0; //name = "[defunct " + name + "]"; //defunct = true; //energy = 0; for (Ref r : refs) r.unindex(); refs.clear(); scenario.change(); } BaseXRef export() { ret new BaseXRef(scenario.programID, id); } // notice system of a change in this object void change() { scenario.change(); } } // class Concept // for inter-process communication // prepared for string ids if we do them later sclass PassRef { S id; *() {} // make serialisation happy *(long id) { this.id = str(id); } *(Concept c) { this(c.id); } long longID() { ret parseLong(id); } public S toString() { ret id; } } sclass Event extends Concept {} // Reference to a concept in another program sclass BaseXRef { S programID; long id; *() {} *(S *programID, long *id) {} public bool equals(O o) { if (!(o instanceof BaseXRef)) false; BaseXRef r = cast o; ret eq(programID, r.programID) && eq(id, r.id); } public int hashCode() { ret programID.hashCode() + (int) id; } } // BaseXRef as a concept sclass XRef extends Concept { BaseXRef ref; *() {} *(BaseXRef *ref) { _doneLoading2(); } // after we have been added to scenario void _doneLoading2() { getIndex().put(ref, this); } HashMap getIndex() { ret getXRefIndex(scenario); } } static synchronized HashMap getXRefIndex(Scenario scenario) { HashMap cache = (HashMap) scenario.perClassData.get(XRef.class); if (cache == null) scenario.perClassData.put(XRef.class, cache = new HashMap); ret cache; } // uses mainScenario static XRef lookupOrCreateXRef(BaseXRef ref) { XRef xref = getXRefIndex(mainScenario).get(ref); if (xref == null) xref = new XRef(ref); ret xref; } svoid loadAndAutoSaveConcepts { mainScenario.loadConcepts(); mainScenario.autoSaveConcepts(); }