Add ISingleInstanceInit interface.

Inits that are logically singletons (e.g. actor
location or owner) should implement this interface
to avoid runtime inconsistencies.

Duplicate instances are rejected at init-time,
allowing simpler queries when they are used.
This commit is contained in:
Paul Chote
2020-05-30 16:48:35 +01:00
committed by reaperrr
parent 86305879cb
commit b856613194
45 changed files with 169 additions and 95 deletions

View File

@@ -11,6 +11,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Eluant;
using Eluant.ObjectBinding;
@@ -116,13 +117,19 @@ namespace OpenRA
internal Actor(World world, string name, TypeDictionary initDict)
{
var duplicateInit = initDict.WithInterface<ISingleInstanceInit>().GroupBy(i => i.GetType())
.FirstOrDefault(i => i.Count() > 1);
if (duplicateInit != null)
throw new InvalidDataException("Duplicate initializer '{0}'".F(duplicateInit.Key.Name));
var init = new ActorInitializer(this, initDict);
readOnlyConditionCache = new ReadOnlyDictionary<string, int>(conditionCache);
World = world;
ActorID = world.NextAID();
var ownerInit = init.GetOrDefault<OwnerInit>(null);
var ownerInit = init.GetOrDefault<OwnerInit>();
if (ownerInit != null)
Owner = ownerInit.Value(world);

View File

@@ -26,6 +26,12 @@ namespace OpenRA
U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>;
U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>;
bool Contains<T>(TraitInfo info) where T : ActorInit;
T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit;
T Get<T>() where T : ActorInit, ISingleInstanceInit;
U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit;
U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit;
bool Contains<T>() where T : ActorInit, ISingleInstanceInit;
}
public class ActorInitializer : IActorInitializer
@@ -77,6 +83,29 @@ namespace OpenRA
}
public bool Contains<T>(TraitInfo info) where T : ActorInit { return GetOrDefault<T>(info) != null; }
public T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit
{
return Dict.GetOrDefault<T>();
}
public T Get<T>() where T : ActorInit, ISingleInstanceInit
{
return Dict.Get<T>();
}
public U GetValue<T, U>() where T : ValueActorInit<U>, ISingleInstanceInit
{
return Get<T>().Value;
}
public U GetValue<T, U>(U fallback) where T : ValueActorInit<U>, ISingleInstanceInit
{
var init = GetOrDefault<T>();
return init != null ? init.Value : fallback;
}
public bool Contains<T>() where T : ActorInit, ISingleInstanceInit { return GetOrDefault<T>() != null; }
}
/*
@@ -88,6 +117,9 @@ namespace OpenRA
* finds with an argument type that matches the given LuaValue.
* - Most ActorInits will want to inherit either ValueActorInit<T> or CompositeActorInit which hide the low-level plumbing.
* - Inits that reference actors should use ActorInitActorReference which allows actors to be referenced by name in map.yaml
* - Inits that should only have a single instance defined on an actor should implement ISingleInstanceInit to allow
* direct queries and runtime enforcement.
* - Inits that aren't ISingleInstanceInit should expose a ctor that accepts a TraitInfo to allow per-trait targeting.
*/
public abstract class ActorInit
{
@@ -104,6 +136,8 @@ namespace OpenRA
public abstract MiniYaml Save();
}
public interface ISingleInstanceInit { }
public abstract class ValueActorInit<T> : ActorInit
{
protected readonly T value;
@@ -181,16 +215,13 @@ namespace OpenRA
}
}
public class LocationInit : ValueActorInit<CPos>
public class LocationInit : ValueActorInit<CPos>, ISingleInstanceInit
{
public LocationInit(TraitInfo info, CPos value)
: base(info, value) { }
public LocationInit(CPos value)
: base(value) { }
}
public class OwnerInit : ActorInit
public class OwnerInit : ActorInit, ISingleInstanceInit
{
public readonly string InternalName;
protected readonly Player value;

View File

@@ -40,7 +40,14 @@ namespace OpenRA
{
var dict = new TypeDictionary();
foreach (var i in inits)
dict.Add(LoadInit(i.Key, i.Value));
{
var init = LoadInit(i.Key, i.Value);
if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
dict.Add(init);
}
return dict;
});
}
@@ -88,7 +95,14 @@ namespace OpenRA
}
// for initialization syntax
public void Add(object o) { InitDict.Add(o); }
public void Add(ActorInit init)
{
if (init is ISingleInstanceInit && InitDict.Contains(init.GetType()))
throw new InvalidDataException("Duplicate initializer '{0}'".F(init.GetType().Name));
InitDict.Add(init);
}
public IEnumerator GetEnumerator() { return InitDict.GetEnumerator(); }
public ActorReference Clone()

View File

@@ -41,6 +41,11 @@ namespace OpenRA.Primitives
return data.ContainsKey(typeof(T));
}
public bool Contains(Type t)
{
return data.ContainsKey(t);
}
public T Get<T>()
{
return (T)Get(typeof(T), true);