Skip to content

Commit

Permalink
Added the ability to reference class objects contained within Decs.
Browse files Browse the repository at this point in the history
  • Loading branch information
zorbathut committed Nov 6, 2024
1 parent 3aed2af commit eb8d9b5
Show file tree
Hide file tree
Showing 16 changed files with 311 additions and 11 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file.
* Added SetupDependsOnAttribute, which allows you to define ConfigErrors/PostLoad dependencies between Dec classes. This is a prototype and will probably change in the future.
* Added a general-purpose stable dag evaluator, which is exposed mostly because it's often convenient.
* Added Recorder.InputContext, which gives contextual diagnostic information useful for reporting Recorder issues.
* Added the ability to reference class objects contained within Decs. Right now the path generation works only for members, lists, and arrays; this will probably be improved later.

### Breaking
* Dec doesn't guarantee what order Decs are initialized in, and it still doesn't . . . but it was *pretty consistent*, and boy, did the above change seriously scramble the order they tend to get initialized in! If you have load dependencies, even if you don't realize that you do, don't be surprised if stuff breaks. Future versions of Dec might include a Dev Mode that intentionally randomizes load order (within the bounds of dependencies) to help catch these issues.
Expand Down
6 changes: 6 additions & 0 deletions src/Database.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ public static class Database
private static Dec[] CachedList = null;

internal static Dictionary<object, Path> DecPathLookup = new Dictionary<object, Path>();
internal static Dictionary<string, object> DecPathLookupReverse = new Dictionary<string, object>();
internal static HashSet<string> DecPathLookupInvalid = new HashSet<string>();
internal static HashSet<string> DecPathLookupConflicts = new HashSet<string>();

/// <summary>
/// The total number of decs that exist.
Expand Down Expand Up @@ -184,6 +187,9 @@ public static void Clear()
CachedList = null;
Lookup.Clear();
DecPathLookup.Clear();
DecPathLookupReverse.Clear();
DecPathLookupInvalid.Clear();
DecPathLookupConflicts.Clear();

foreach (var db in Databases)
{
Expand Down
18 changes: 18 additions & 0 deletions src/ParserModular.cs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,24 @@ public void Finish()
}
}

// Invert the dec path lookup tables
foreach (var (decObject, decPath) in Database.DecPathLookup)
{
var pathSerialized = decPath.Serialize();

if (Database.DecPathLookupReverse.ContainsKey(pathSerialized))
{
Database.DecPathLookupConflicts.Add(pathSerialized); // this is currently considered OK because our path system is highly incomplete
}

if (!decPath.IsValidForWriting())
{
Database.DecPathLookupInvalid.Add(pathSerialized);
}

Database.DecPathLookupReverse[pathSerialized] = decObject;
}

if (s_Status != Status.Distributing)
{
Dbg.Err($"Finalizing while the world is in {s_Status} state; should be {Status.Distributing} state");
Expand Down
51 changes: 51 additions & 0 deletions src/Path.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ namespace Dec
public abstract class Path
{
public abstract string Serialize();

public abstract bool IsValidForWriting();
}

public class PathRoot : Path
Expand All @@ -19,6 +21,11 @@ public override string Serialize()
{
return rootType;
}

public override bool IsValidForWriting()
{
return true;
}
}

public class PathDec : Path
Expand All @@ -44,6 +51,11 @@ public override string Serialize()
{
return $"{decTypeName ?? decType.ComposeDecFormatted()}.{decName}";
}

public override bool IsValidForWriting()
{
return true;
}
}

public class PathRef : Path
Expand All @@ -60,6 +72,12 @@ public override string Serialize()
{
return $"REF.{refName}";
}

public override bool IsValidForWriting()
{
// how did this even happen?
return false;
}
}

public class PathMember : Path
Expand All @@ -77,6 +95,11 @@ public override string Serialize()
{
return $"{parent.Serialize()}.{memberName}";
}

public override bool IsValidForWriting()
{
return parent.IsValidForWriting();
}
}

public class PathIndex : Path
Expand All @@ -94,6 +117,11 @@ public override string Serialize()
{
return $"{parent.Serialize()}[{index}]";
}

public override bool IsValidForWriting()
{
return parent.IsValidForWriting();
}
}

public class PathIndexMultidim : Path
Expand All @@ -111,6 +139,11 @@ public override string Serialize()
{
return $"{parent.Serialize()}[{string.Join(",", indices)}]";
}

public override bool IsValidForWriting()
{
return parent.IsValidForWriting();
}
}

// the ones after this point are grossly incomplete
Expand All @@ -128,6 +161,12 @@ public override string Serialize()
{
return $"{parent.Serialize()}[KEY]";
}

public override bool IsValidForWriting()
{
// not yet identifiable; I'm not sure how this even can work, frankly
return false;
}
}

public class PathDictionaryValue : Path
Expand All @@ -143,6 +182,12 @@ public override string Serialize()
{
return $"{parent.Serialize()}[nyi]";
}

public override bool IsValidForWriting()
{
// if we have a usable key this can be done! but we're not right now
return false;
}
}

public class PathHashSetElement : Path
Expand All @@ -158,5 +203,11 @@ public override string Serialize()
{
return $"{parent.Serialize()}[KEY]";
}

public override bool IsValidForWriting()
{
// not yet identifiable; I'm not sure how this even can work, frankly
return false;
}
}
}
12 changes: 6 additions & 6 deletions src/ReaderXml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ public override void ParseList(IList list, Type referencedType, ReaderGlobals re
{
var recorderChildContext = recorderSettings.CreateChild();

int index = 0;
foreach (var fieldElement in xml.Elements())
{
if (fieldElement.Name.LocalName != "li")
Expand All @@ -98,7 +97,7 @@ public override void ParseList(IList list, Type referencedType, ReaderGlobals re
Dbg.Err($"{elementContext}: Tag should be <li>, is <{fieldElement.Name.LocalName}>");
}

list.Add(Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, new PathIndex(path, index++), UserSettings) }, referencedType, null, readerGlobals, recorderChildContext));
list.Add(Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, new PathIndex(path, list.Count), UserSettings) }, referencedType, null, readerGlobals, recorderChildContext));
}

list.GetType().GetField("_version", BindingFlags.Instance | BindingFlags.NonPublic).SetValue(list, Util.CollectionDeserializationVersion);
Expand Down Expand Up @@ -160,7 +159,7 @@ public override void ParseArray(Array array, Type referencedType, ReaderGlobals
int index = 0;
foreach (var fieldElement in xml.Elements())
{
var newPath = new PathIndex(path, index);
var newPath = new PathIndex(path, startOffset + index);

if (fieldElement.Name.LocalName != "li")
{
Expand Down Expand Up @@ -286,7 +285,8 @@ public override void ParseDictionary(IDictionary dict, Type referencedKeyType, T

writtenFields?.Add(key);

dict[key] = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, null, UserSettings) }, valueType, originalValue, readerGlobals, recorderChildContext);
var valuePath = new PathDictionaryValue(path);
dict[key] = Serialization.ParseElement(new List<ReaderNodeParseable>() { new ReaderNodeXml(fieldElement, fileIdentifier, valuePath, UserSettings) }, valueType, originalValue, readerGlobals, recorderChildContext);
}
}
}
Expand Down Expand Up @@ -373,7 +373,7 @@ public override void ParseStack(object stack, Type referencedType, ReaderGlobals

var recorderChildContext = recorderSettings.CreateChild();

int index = 0;
int index = (int)stack.GetType().GetProperty("Count").GetValue(stack);
foreach (var fieldElement in xml.Elements())
{
var newPath = new PathIndex(path, index);
Expand All @@ -395,7 +395,7 @@ public override void ParseQueue(object queue, Type referencedType, ReaderGlobals

var recorderChildContext = recorderSettings.CreateChild();

int index = 0;
int index = (int)queue.GetType().GetProperty("Count").GetValue(queue);
foreach (var fieldElement in xml.Elements())
{
var newPath = new PathIndex(path, index);
Expand Down
55 changes: 50 additions & 5 deletions src/Serialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Linq;
using System.Reflection;

Expand Down Expand Up @@ -580,11 +581,18 @@ internal static object ParseElement(List<ReaderNodeParseable> nodes, Type type,
// these paths *should* all match up, so we're just choosing one
var newPath = nodes[0].GetContext().path;

// right now we strictly overwrite previous instances of the result
// this is not a great solution because it's very error-prone
// the problem is that the inheritance/patch system has a tendency to spam this function repeatedly with the same object
// I'm currently not sure how to deal with this, so . . . I'm not! I'm just doing it the bad way.
globals.decPathLookup[result] = newPath;
if (newPath == null)
{
Dbg.Err("Internal error; missing path somehow? Please report this, thanks!");
}
else
{
// right now we strictly overwrite previous instances of the result
// this is not a great solution because it's very error-prone
// the problem is that the inheritance/patch system has a tendency to spam this function repeatedly with the same object
// I'm currently not sure how to deal with this, so . . . I'm not! I'm just doing it the bad way.
globals.decPathLookup[result] = newPath;
}
}
}

Expand Down Expand Up @@ -772,6 +780,30 @@ internal static object ParseElement_Worker(List<ReaderNodeParseable> nodes, Type
{
// Ref is the highest priority, largely because I think it's cool

// First we check if this is a valid Dec path ref; those don't require .Shared()
if (Database.DecPathLookupReverse.TryGetValue(refKey, out var defPathRef))
{
// check types
if (!type.IsAssignableFrom(defPathRef.GetType()))
{
Dbg.Err($"{refKeyNode.GetContext()}: Dec path reference object [{refKey}] is of type {defPathRef.GetType()}, which cannot be converted to expected type {type}");
return result;
}

// if it's a conflict, be unhappy
if (Database.DecPathLookupInvalid.Contains(refKey))
{
Dbg.Err($"{refKeyNode.GetContext()}: Deserializes improper Dec path reference [{refKey}]; this should probably not have been serialized in the first place, doing our best though");
}
else if (Database.DecPathLookupConflicts.Contains(refKey))
{
Dbg.Err($"{refKeyNode.GetContext()}: Multiple objects claiming Dec path reference [{refKey}]; this should probably not have been serialized in the first place, doing our best though");
}

// toot
return defPathRef;
}

if (recSettings.shared == Recorder.Settings.Shared.Deny)
{
Dbg.Err($"{refKeyNode.GetContext()}: Found a reference in a non-.Shared() context; this should happen only if you've removed the .Shared() tag since the file was generated, or if you hand-wrote a file that is questionably valid. Using the reference anyway but this might produce unexpected results");
Expand Down Expand Up @@ -1760,6 +1792,19 @@ internal static void ComposeElement(WriterNode node, object value, Type fieldTyp
return;
}


if (node.AllowDecPath)
{
// Try to snag a Dec path
var decPath = Database.DecPathLookup.TryGetValue(value);

if (decPath != null)
{
node.WriteDecPathRef(value);
return;
}
}

var valType = value.GetType();

// This is our value's type, but we may need a little bit of tinkering to make it useful.
Expand Down
14 changes: 14 additions & 0 deletions src/UtilCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,19 @@ internal static int FirstIndexOf<T>(this IEnumerable<T> enumerable, Func<T, bool

return -1;
}

internal static IEnumerable<T> FindDuplicates<T>(this IEnumerable<T> source)
{
var seen = new HashSet<T>();
var duplicates = new HashSet<T>();

foreach (var item in source)
{
if (!seen.Add(item))
duplicates.Add(item);
}

return duplicates;
}
}
}
2 changes: 2 additions & 0 deletions src/Writer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public WriterNode(Recorder.Settings settings, Path path)

public Recorder.Settings RecorderSettings { get => settings; }
public abstract bool AllowReflection { get; }
public abstract bool AllowDecPath { get; }
public virtual bool AllowAsThis { get => true; }
public virtual bool AllowCloning { get => false; }
public abstract Recorder.IUserSettings UserSettings { get; }
Expand All @@ -36,6 +37,7 @@ public void MakeRecorderContextChild()
public abstract void WriteString(string value);
public abstract void WriteType(Type value);
public abstract void WriteDec(Dec value);
public abstract void WriteDecPathRef(object value);
public abstract void WriteExplicitNull();
public abstract bool WriteReference(object value, Path path);
public abstract void WriteArray(Array value);
Expand Down
6 changes: 6 additions & 0 deletions src/WriterClone.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ internal class WriterNodeClone : WriterNode
private Dictionary<string, WriterNodeClone> recorderChildren;

public override bool AllowReflection { get => writer.AllowReflection; }
public override bool AllowDecPath { get => true; }
public override bool AllowAsThis { get => false; }
public override bool AllowCloning { get => true; }
public override Recorder.IUserSettings UserSettings { get => writer.UserSettings; }
Expand Down Expand Up @@ -601,6 +602,11 @@ public override void WriteDec(Dec value)
SetValuelikeOriginalAndResult(value);
}

public override void WriteDecPathRef(object value)
{
SetValuelikeOriginalAndResult(value);
}

public override void TagClass(Type type)
{
// we don't actually care about this, the clone system finds the difference between fields-as-set and fields-as-expected to be pointless
Expand Down
Loading

0 comments on commit eb8d9b5

Please sign in to comment.