Files
Mementos/Unity/Alternate Genre Jam/Assets/Ink/InkLibs/InkRuntime/Container.cs
2021-06-30 21:39:19 +10:00

367 lines
11 KiB
C#

using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
namespace Ink.Runtime
{
public class Container : Runtime.Object, INamedContent
{
public string name { get; set; }
public List<Runtime.Object> content {
get {
return _content;
}
set {
AddContent (value);
}
}
List<Runtime.Object> _content;
public Dictionary<string, INamedContent> namedContent { get; set; }
public Dictionary<string, Runtime.Object> namedOnlyContent {
get {
var namedOnlyContentDict = new Dictionary<string, Runtime.Object>();
foreach (var kvPair in namedContent) {
namedOnlyContentDict [kvPair.Key] = (Runtime.Object)kvPair.Value;
}
foreach (var c in content) {
var named = c as INamedContent;
if (named != null && named.hasValidName) {
namedOnlyContentDict.Remove (named.name);
}
}
if (namedOnlyContentDict.Count == 0)
namedOnlyContentDict = null;
return namedOnlyContentDict;
}
set {
var existingNamedOnly = namedOnlyContent;
if (existingNamedOnly != null) {
foreach (var kvPair in existingNamedOnly) {
namedContent.Remove (kvPair.Key);
}
}
if (value == null)
return;
foreach (var kvPair in value) {
var named = kvPair.Value as INamedContent;
if( named != null )
AddToNamedContentOnly (named);
}
}
}
public bool visitsShouldBeCounted { get; set; }
public bool turnIndexShouldBeCounted { get; set; }
public bool countingAtStartOnly { get; set; }
[Flags]
public enum CountFlags
{
Visits = 1,
Turns = 2,
CountStartOnly = 4
}
public int countFlags
{
get {
CountFlags flags = 0;
if (visitsShouldBeCounted) flags |= CountFlags.Visits;
if (turnIndexShouldBeCounted) flags |= CountFlags.Turns;
if (countingAtStartOnly) flags |= CountFlags.CountStartOnly;
// If we're only storing CountStartOnly, it serves no purpose,
// since it's dependent on the other two to be used at all.
// (e.g. for setting the fact that *if* a gather or choice's
// content is counted, then is should only be counter at the start)
// So this is just an optimisation for storage.
if (flags == CountFlags.CountStartOnly) {
flags = 0;
}
return (int)flags;
}
set {
var flag = (CountFlags)value;
if ((flag & CountFlags.Visits) > 0) visitsShouldBeCounted = true;
if ((flag & CountFlags.Turns) > 0) turnIndexShouldBeCounted = true;
if ((flag & CountFlags.CountStartOnly) > 0) countingAtStartOnly = true;
}
}
public bool hasValidName
{
get { return name != null && name.Length > 0; }
}
public Path pathToFirstLeafContent
{
get {
if( _pathToFirstLeafContent == null )
_pathToFirstLeafContent = path.PathByAppendingPath (internalPathToFirstLeafContent);
return _pathToFirstLeafContent;
}
}
Path _pathToFirstLeafContent;
Path internalPathToFirstLeafContent
{
get {
var components = new List<Path.Component>();
var container = this;
while (container != null) {
if (container.content.Count > 0) {
components.Add (new Path.Component (0));
container = container.content [0] as Container;
}
}
return new Path(components);
}
}
public Container ()
{
_content = new List<Runtime.Object> ();
namedContent = new Dictionary<string, INamedContent> ();
}
public void AddContent(Runtime.Object contentObj)
{
content.Add (contentObj);
if (contentObj.parent) {
throw new System.Exception ("content is already in " + contentObj.parent);
}
contentObj.parent = this;
TryAddNamedContent (contentObj);
}
public void AddContent(IList<Runtime.Object> contentList)
{
foreach (var c in contentList) {
AddContent (c);
}
}
public void InsertContent(Runtime.Object contentObj, int index)
{
content.Insert (index, contentObj);
if (contentObj.parent) {
throw new System.Exception ("content is already in " + contentObj.parent);
}
contentObj.parent = this;
TryAddNamedContent (contentObj);
}
public void TryAddNamedContent(Runtime.Object contentObj)
{
var namedContentObj = contentObj as INamedContent;
if (namedContentObj != null && namedContentObj.hasValidName) {
AddToNamedContentOnly (namedContentObj);
}
}
public void AddToNamedContentOnly(INamedContent namedContentObj)
{
Debug.Assert (namedContentObj is Runtime.Object, "Can only add Runtime.Objects to a Runtime.Container");
var runtimeObj = (Runtime.Object)namedContentObj;
runtimeObj.parent = this;
namedContent [namedContentObj.name] = namedContentObj;
}
public void AddContentsOfContainer(Container otherContainer)
{
content.AddRange (otherContainer.content);
foreach (var obj in otherContainer.content) {
obj.parent = this;
TryAddNamedContent (obj);
}
}
protected Runtime.Object ContentWithPathComponent(Path.Component component)
{
if (component.isIndex) {
if (component.index >= 0 && component.index < content.Count) {
return content [component.index];
}
// When path is out of range, quietly return nil
// (useful as we step/increment forwards through content)
else {
return null;
}
}
else if (component.isParent) {
return this.parent;
}
else {
INamedContent foundContent = null;
if (namedContent.TryGetValue (component.name, out foundContent)) {
return (Runtime.Object)foundContent;
} else {
return null;
}
}
}
public SearchResult ContentAtPath(Path path, int partialPathStart = 0, int partialPathLength = -1)
{
if (partialPathLength == -1)
partialPathLength = path.length;
var result = new SearchResult ();
result.approximate = false;
Container currentContainer = this;
Runtime.Object currentObj = this;
for (int i = partialPathStart; i < partialPathLength; ++i) {
var comp = path.GetComponent(i);
// Path component was wrong type
if (currentContainer == null) {
result.approximate = true;
break;
}
var foundObj = currentContainer.ContentWithPathComponent(comp);
// Couldn't resolve entire path?
if (foundObj == null) {
result.approximate = true;
break;
}
currentObj = foundObj;
currentContainer = foundObj as Container;
}
result.obj = currentObj;
return result;
}
public void BuildStringOfHierarchy(StringBuilder sb, int indentation, Runtime.Object pointedObj)
{
Action appendIndentation = () => {
const int spacesPerIndent = 4;
for(int i=0; i<spacesPerIndent*indentation;++i) {
sb.Append(" ");
}
};
appendIndentation ();
sb.Append("[");
if (this.hasValidName) {
sb.AppendFormat (" ({0})", this.name);
}
if (this == pointedObj) {
sb.Append (" <---");
}
sb.AppendLine ();
indentation++;
for (int i=0; i<content.Count; ++i) {
var obj = content [i];
if (obj is Container) {
var container = (Container)obj;
container.BuildStringOfHierarchy (sb, indentation, pointedObj);
} else {
appendIndentation ();
if (obj is StringValue) {
sb.Append ("\"");
sb.Append (obj.ToString ().Replace ("\n", "\\n"));
sb.Append ("\"");
} else {
sb.Append (obj.ToString ());
}
}
if (i != content.Count - 1) {
sb.Append (",");
}
if ( !(obj is Container) && obj == pointedObj ) {
sb.Append (" <---");
}
sb.AppendLine ();
}
var onlyNamed = new Dictionary<string, INamedContent> ();
foreach (var objKV in namedContent) {
if (content.Contains ((Runtime.Object)objKV.Value)) {
continue;
} else {
onlyNamed.Add (objKV.Key, objKV.Value);
}
}
if (onlyNamed.Count > 0) {
appendIndentation ();
sb.AppendLine ("-- named: --");
foreach (var objKV in onlyNamed) {
Debug.Assert (objKV.Value is Container, "Can only print out named Containers");
var container = (Container)objKV.Value;
container.BuildStringOfHierarchy (sb, indentation, pointedObj);
sb.AppendLine ();
}
}
indentation--;
appendIndentation ();
sb.Append ("]");
}
public virtual string BuildStringOfHierarchy()
{
var sb = new StringBuilder ();
BuildStringOfHierarchy (sb, 0, null);
return sb.ToString ();
}
}
}