Files
OpenRA/OpenRA.Game/Map/ActorReference.cs
RoosterDragon ac0969d688 Use nameof instead of hardcoded strings in reflection calls.
This helps improve the safety of code the uses reflection when methods may get renamed, and helps navigating code as the nameof will show up when searching for references to members.
2022-04-14 19:58:15 +02:00

207 lines
5.8 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2021 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA
{
public interface ISuppressInitExport { }
public class ActorReference : IEnumerable
{
public string Type;
readonly Lazy<TypeDictionary> initDict;
internal TypeDictionary InitDict => initDict.Value;
public ActorReference(string type)
: this(type, new Dictionary<string, MiniYaml>()) { }
public ActorReference(string type, Dictionary<string, MiniYaml> inits)
{
Type = type;
initDict = Exts.Lazy(() =>
{
var dict = new TypeDictionary();
foreach (var i in inits)
{
var init = LoadInit(i.Key, i.Value);
if (init is ISingleInstanceInit && dict.Contains(init.GetType()))
throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'");
dict.Add(init);
}
return dict;
});
}
public ActorReference(string type, TypeDictionary inits)
{
Type = type;
initDict = new Lazy<TypeDictionary>(() =>
{
var dict = new TypeDictionary();
foreach (var i in inits)
dict.Add(i);
return dict;
});
}
static ActorInit LoadInit(string initName, MiniYaml initYaml)
{
var initInstance = initName.Split(ActorInfo.TraitInstanceSeparator);
var type = Game.ModData.ObjectCreator.FindType(initInstance[0] + "Init");
if (type == null)
throw new InvalidDataException($"Unknown initializer type '{initInstance[0]}Init'");
var init = (ActorInit)FormatterServices.GetUninitializedObject(type);
if (initInstance.Length > 1)
type.GetField(nameof(ActorInit.InstanceName)).SetValue(init, initInstance[1]);
var loader = type.GetMethod("Initialize", new[] { typeof(MiniYaml) });
if (loader == null)
throw new InvalidDataException($"{initInstance[0]}Init does not define a yaml-assignable type.");
loader.Invoke(init, new[] { initYaml });
return init;
}
public MiniYaml Save(Func<ActorInit, bool> initFilter = null)
{
var ret = new MiniYaml(Type);
foreach (var o in initDict.Value)
{
var init = o as ActorInit;
if (init == null || o is ISuppressInitExport)
continue;
if (initFilter != null && !initFilter(init))
continue;
var initTypeName = init.GetType().Name;
var initName = initTypeName.Substring(0, initTypeName.Length - 4);
if (!string.IsNullOrEmpty(init.InstanceName))
initName += ActorInfo.TraitInstanceSeparator + init.InstanceName;
ret.Nodes.Add(new MiniYamlNode(initName, init.Save()));
}
return ret;
}
public IEnumerator GetEnumerator() { return initDict.Value.GetEnumerator(); }
public ActorReference Clone()
{
var clone = new ActorReference(Type);
foreach (var init in initDict.Value)
clone.initDict.Value.Add(init);
return clone;
}
public void Add(ActorInit init)
{
if (init is ISingleInstanceInit && InitDict.Contains(init.GetType()))
throw new InvalidDataException($"Duplicate initializer '{init.GetType().Name}'");
InitDict.Add(init);
}
public void Remove(ActorInit o) { initDict.Value.Remove(o); }
public int RemoveAll<T>() where T : ActorInit
{
var removed = 0;
foreach (var o in initDict.Value.WithInterface<T>().ToList())
{
removed++;
initDict.Value.Remove(o);
}
return removed;
}
public IEnumerable<T> GetAll<T>() where T : ActorInit
{
return initDict.Value.WithInterface<T>();
}
public T GetOrDefault<T>(TraitInfo info) where T : ActorInit
{
var inits = initDict.Value.WithInterface<T>();
// Traits tagged with an instance name prefer inits with the same name.
// If a more specific init is not available, fall back to an unnamed init.
// If duplicate inits are defined, take the last to match standard yaml override expectations
if (info != null && !string.IsNullOrEmpty(info.InstanceName))
return inits.LastOrDefault(i => i.InstanceName == info.InstanceName) ??
inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
// Untagged traits will only use untagged inits
return inits.LastOrDefault(i => string.IsNullOrEmpty(i.InstanceName));
}
public T Get<T>(TraitInfo info) where T : ActorInit
{
var init = GetOrDefault<T>(info);
if (init == null)
throw new InvalidOperationException($"TypeDictionary does not contain instance of type `{typeof(T)}`");
return init;
}
public U GetValue<T, U>(TraitInfo info) where T : ValueActorInit<U>
{
return Get<T>(info).Value;
}
public U GetValue<T, U>(TraitInfo info, U fallback) where T : ValueActorInit<U>
{
var init = GetOrDefault<T>(info);
return init != null ? init.Value : fallback;
}
public bool Contains<T>(TraitInfo info) where T : ActorInit { return GetOrDefault<T>(info) != null; }
public T GetOrDefault<T>() where T : ActorInit, ISingleInstanceInit
{
return initDict.Value.GetOrDefault<T>();
}
public T Get<T>() where T : ActorInit, ISingleInstanceInit
{
return initDict.Value.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; }
}
}