Merge pull request #9977 from RoosterDragon/dispose-lua-values

Ensure LuaValues are disposed
This commit is contained in:
Oliver Brakmann
2015-12-27 17:20:39 +01:00
11 changed files with 184 additions and 142 deletions

View File

@@ -64,7 +64,19 @@ namespace OpenRA.Scripting
} }
} }
// For global-level bindings /// <summary>
/// Provides global bindings in Lua code.
/// </summary>
/// <remarks>
/// Instance methods and properties declared in derived classes will be made available in Lua. Use
/// <see cref="ScriptGlobalAttribute"/> on your derived class to specify the name exposed in Lua. It is recommended
/// to apply <see cref="DescAttribute"/> against each method or property to provide a description of what it does.
///
/// Any parameters to your method that are <see cref="LuaValue"/>s will be disposed automatically when your method
/// completes. If you need to return any of these values, or need them to live longer than your method, you must
/// use <see cref="LuaValue.CopyReference"/> to get your own copy of the value. Any copied values you return will
/// be disposed automatically, but you assume responsibility for disposing any other copies.
/// </remarks>
public abstract class ScriptGlobal : ScriptObjectWrapper public abstract class ScriptGlobal : ScriptObjectWrapper
{ {
protected override string DuplicateKeyError(string memberName) { return "Table '{0}' defines multiple members '{1}'".F(Name, memberName); } protected override string DuplicateKeyError(string memberName) { return "Table '{0}' defines multiple members '{1}'".F(Name, memberName); }
@@ -78,11 +90,27 @@ namespace OpenRA.Scripting
var type = this.GetType(); var type = this.GetType();
var names = type.GetCustomAttributes<ScriptGlobalAttribute>(true); var names = type.GetCustomAttributes<ScriptGlobalAttribute>(true);
if (names.Length != 1) if (names.Length != 1)
throw new InvalidOperationException("[LuaGlobal] attribute not found for global table '{0}'".F(type)); throw new InvalidOperationException("[ScriptGlobal] attribute not found for global table '{0}'".F(type));
Name = names.First().Name; Name = names.First().Name;
Bind(new[] { this }); Bind(new[] { this });
} }
protected IEnumerable<T> FilteredObjects<T>(IEnumerable<T> objects, LuaFunction filter)
{
if (filter != null)
{
objects = objects.Where(a =>
{
using (var luaObject = a.ToLuaValue(Context))
using (var filterResult = filter.Call(luaObject))
using (var result = filterResult.First())
return result.ToBoolean();
});
}
return objects;
}
} }
public sealed class ScriptGlobalAttribute : Attribute public sealed class ScriptGlobalAttribute : Attribute

View File

@@ -44,31 +44,54 @@ namespace OpenRA.Scripting
LuaValue Invoke(LuaVararg args) LuaValue Invoke(LuaVararg args)
{ {
if (!IsMethod) object[] clrArgs = null;
throw new LuaException("Trying to invoke a ScriptMemberWrapper that isn't a method!"); try
var mi = (MethodInfo)Member;
var pi = mi.GetParameters();
var clrArgs = new object[pi.Length];
var argCount = args.Count;
for (var i = 0; i < pi.Length; i++)
{ {
if (i >= argCount) if (!IsMethod)
{ throw new LuaException("Trying to invoke a ScriptMemberWrapper that isn't a method!");
if (!pi[i].IsOptional)
throw new LuaException("Argument '{0}' of '{1}' is not optional.".F(pi[i].LuaDocString(), Member.LuaDocString()));
clrArgs[i] = pi[i].DefaultValue; var mi = (MethodInfo)Member;
continue; var pi = mi.GetParameters();
clrArgs = new object[pi.Length];
var argCount = args.Count;
for (var i = 0; i < pi.Length; i++)
{
if (i >= argCount)
{
if (!pi[i].IsOptional)
throw new LuaException("Argument '{0}' of '{1}' is not optional.".F(pi[i].LuaDocString(), Member.LuaDocString()));
clrArgs[i] = pi[i].DefaultValue;
continue;
}
if (!args[i].TryGetClrValue(pi[i].ParameterType, out clrArgs[i]))
throw new LuaException("Unable to convert parameter {0} to {1}".F(i, pi[i].ParameterType.Name));
} }
if (!args[i].TryGetClrValue(pi[i].ParameterType, out clrArgs[i])) return mi.Invoke(Target, clrArgs).ToLuaValue(context);
throw new LuaException("Unable to convert parameter {0} to {1}".F(i, pi[i].ParameterType.Name));
} }
finally
{
// Clean up all the Lua arguments that were given to us.
foreach (var arg in args)
arg.Dispose();
args.Dispose();
var ret = mi.Invoke(Target, clrArgs); // If we created any arrays of LuaValues to pass around, we need to dispose those too.
return ret.ToLuaValue(context); if (clrArgs != null)
{
foreach (var arg in clrArgs)
{
if (!(arg is LuaValue[]))
continue;
foreach (var value in (LuaValue[])arg)
value.Dispose();
}
}
}
} }
public LuaValue Get(LuaRuntime runtime) public LuaValue Get(LuaRuntime runtime)
@@ -77,10 +100,7 @@ namespace OpenRA.Scripting
return runtime.CreateFunctionFromDelegate((Func<LuaVararg, LuaValue>)Invoke); return runtime.CreateFunctionFromDelegate((Func<LuaVararg, LuaValue>)Invoke);
if (IsGetProperty) if (IsGetProperty)
{ return ((PropertyInfo)Member).GetValue(Target, null).ToLuaValue(context);
var pi = Member as PropertyInfo;
return pi.GetValue(Target, null).ToLuaValue(context);
}
throw new LuaException("The property '{0}' is write-only".F(Member.Name)); throw new LuaException("The property '{0}' is write-only".F(Member.Name));
} }
@@ -89,7 +109,7 @@ namespace OpenRA.Scripting
{ {
if (IsSetProperty) if (IsSetProperty)
{ {
var pi = Member as PropertyInfo; var pi = (PropertyInfo)Member;
object clrValue; object clrValue;
if (!value.TryGetClrValue(pi.PropertyType, out clrValue)) if (!value.TryGetClrValue(pi.PropertyType, out clrValue))
throw new LuaException("Unable to convert '{0}' to Clr type '{1}'".F(value.WrappedClrType().Name, pi.PropertyType)); throw new LuaException("Unable to convert '{0}' to Clr type '{1}'".F(value.WrappedClrType().Name, pi.PropertyType));

View File

@@ -107,13 +107,22 @@ namespace OpenRA.Scripting
foreach (var kv in table) foreach (var kv in table)
{ {
object element; using (kv.Key)
if (innerType == typeof(LuaValue)) {
element = kv.Value; object element;
else if (!kv.Value.TryGetClrValue(innerType, out element)) if (innerType == typeof(LuaValue))
throw new LuaException("Unable to convert table value of type {0} to type {1}".F(kv.Value.WrappedClrType(), innerType)); element = kv.Value;
else
{
var elementHasClrValue = kv.Value.TryGetClrValue(innerType, out element);
if (!elementHasClrValue || !(element is LuaValue))
kv.Value.Dispose();
if (!elementHasClrValue)
throw new LuaException("Unable to convert table value of type {0} to type {1}".F(kv.Value.WrappedClrType(), innerType));
}
array.SetValue(element, i++); array.SetValue(element, i++);
}
} }
clrObject = array; clrObject = array;
@@ -159,11 +168,13 @@ namespace OpenRA.Scripting
if (obj is Array) if (obj is Array)
{ {
var array = obj as Array; var array = (Array)obj;
var i = 1; var i = 1;
var table = context.CreateTable(); var table = context.CreateTable();
foreach (var x in array) foreach (var x in array)
table.Add(i++, x.ToLuaValue(context)); using (LuaValue key = i++, value = x.ToLuaValue(context))
table.Add(key, value);
return table; return table;
} }

View File

@@ -29,26 +29,30 @@ namespace OpenRA.Mods.Common.Scripting
// Convert table entries into ActorInits // Convert table entries into ActorInits
foreach (var kv in initTable) foreach (var kv in initTable)
{ {
// Find the requested type using (kv.Key)
var typeName = kv.Key.ToString(); using (kv.Value)
var initType = Game.ModData.ObjectCreator.FindType(typeName + "Init"); {
if (initType == null) // Find the requested type
throw new LuaException("Unknown initializer type '{0}'".F(typeName)); var typeName = kv.Key.ToString();
var initType = Game.ModData.ObjectCreator.FindType(typeName + "Init");
if (initType == null)
throw new LuaException("Unknown initializer type '{0}'".F(typeName));
// Cast it up to an IActorInit<T> // Cast it up to an IActorInit<T>
var genericType = initType.GetInterfaces() var genericType = initType.GetInterfaces()
.First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IActorInit<>)); .First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IActorInit<>));
var innerType = genericType.GetGenericArguments().First(); var innerType = genericType.GetGenericArguments().First();
var valueType = innerType.IsEnum ? typeof(int) : innerType; var valueType = innerType.IsEnum ? typeof(int) : innerType;
// Try and coerce the table value to the required type // Try and coerce the table value to the required type
object value; object value;
if (!kv.Value.TryGetClrValue(valueType, out value)) if (!kv.Value.TryGetClrValue(valueType, out value))
throw new LuaException("Invalid data type for '{0}' (expected '{1}')".F(typeName, valueType.Name)); throw new LuaException("Invalid data type for '{0}' (expected '{1}')".F(typeName, valueType.Name));
// Construct the ActorInit. Phew! // Construct the ActorInit. Phew!
var test = initType.GetConstructor(new[] { innerType }).Invoke(new[] { value }); var test = initType.GetConstructor(new[] { innerType }).Invoke(new[] { value });
initDict.Add(test); initDict.Add(test);
}
} }
// The actor must be added to the world at the end of the tick // The actor must be added to the world at the end of the tick

View File

@@ -8,7 +8,6 @@
*/ */
#endregion #endregion
using System;
using System.Linq; using System.Linq;
using Eluant; using Eluant;
using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits;
@@ -34,34 +33,14 @@ namespace OpenRA.Mods.Common.Scripting
public Actor[] ActorsInCircle(WPos location, WDist radius, LuaFunction filter = null) public Actor[] ActorsInCircle(WPos location, WDist radius, LuaFunction filter = null)
{ {
var actors = Context.World.FindActorsInCircle(location, radius); var actors = Context.World.FindActorsInCircle(location, radius);
return FilteredObjects(actors, filter).ToArray();
if (filter != null)
{
actors = actors.Where(a =>
{
using (var f = filter.Call(a.ToLuaValue(Context)))
return f.First().ToBoolean();
});
}
return actors.ToArray();
} }
[Desc("Returns a table of all actors within the requested rectangle, filtered using the specified function.")] [Desc("Returns a table of all actors within the requested rectangle, filtered using the specified function.")]
public Actor[] ActorsInBox(WPos topLeft, WPos bottomRight, LuaFunction filter = null) public Actor[] ActorsInBox(WPos topLeft, WPos bottomRight, LuaFunction filter = null)
{ {
var actors = Context.World.ActorMap.ActorsInBox(topLeft, bottomRight); var actors = Context.World.ActorMap.ActorsInBox(topLeft, bottomRight);
return FilteredObjects(actors, filter).ToArray();
if (filter != null)
{
actors = actors.Where(a =>
{
using (var f = filter.Call(a.ToLuaValue(Context)))
return f.First().ToBoolean();
});
}
return actors.ToArray();
} }
[Desc("Returns the location of the top-left corner of the map (assuming zero terrain height).")] [Desc("Returns the location of the top-left corner of the map (assuming zero terrain height).")]

View File

@@ -53,7 +53,6 @@ namespace OpenRA.Mods.Common.Scripting
Game.Sound.Play(file); Game.Sound.Play(file);
} }
Action onComplete;
[Desc("Play track defined in music.yaml or map.yaml, or keep track empty for playing a random song.")] [Desc("Play track defined in music.yaml or map.yaml, or keep track empty for playing a random song.")]
public void PlayMusic(string track = null, LuaFunction func = null) public void PlayMusic(string track = null, LuaFunction func = null)
{ {
@@ -65,8 +64,8 @@ namespace OpenRA.Mods.Common.Scripting
if (func != null) if (func != null)
{ {
var f = func.CopyReference() as LuaFunction; var f = (LuaFunction)func.CopyReference();
onComplete = () => Action onComplete = () =>
{ {
try try
{ {
@@ -113,13 +112,13 @@ namespace OpenRA.Mods.Common.Scripting
playlist.Stop(); playlist.Stop();
} }
Action onCompleteFullscreen;
[Desc("Play a VQA video fullscreen. File name has to include the file extension.")] [Desc("Play a VQA video fullscreen. File name has to include the file extension.")]
public void PlayMovieFullscreen(string movie, LuaFunction func = null) public void PlayMovieFullscreen(string movie, LuaFunction func = null)
{ {
Action onCompleteFullscreen;
if (func != null) if (func != null)
{ {
var f = func.CopyReference() as LuaFunction; var f = (LuaFunction)func.CopyReference();
onCompleteFullscreen = () => onCompleteFullscreen = () =>
{ {
try try
@@ -139,15 +138,14 @@ namespace OpenRA.Mods.Common.Scripting
Media.PlayFMVFullscreen(world, movie, onCompleteFullscreen); Media.PlayFMVFullscreen(world, movie, onCompleteFullscreen);
} }
Action onLoadComplete;
Action onCompleteRadar;
[Desc("Play a VQA video in the radar window. File name has to include the file extension. " + [Desc("Play a VQA video in the radar window. File name has to include the file extension. " +
"Returns true on success, if the movie wasn't found the function returns false and the callback is executed.")] "Returns true on success, if the movie wasn't found the function returns false and the callback is executed.")]
public bool PlayMovieInRadar(string movie, LuaFunction playComplete = null) public bool PlayMovieInRadar(string movie, LuaFunction playComplete = null)
{ {
Action onCompleteRadar;
if (playComplete != null) if (playComplete != null)
{ {
var f = playComplete.CopyReference() as LuaFunction; var f = (LuaFunction)playComplete.CopyReference();
onCompleteRadar = () => onCompleteRadar = () =>
{ {
try try
@@ -178,7 +176,7 @@ namespace OpenRA.Mods.Common.Scripting
AsyncLoader l = new AsyncLoader(Media.LoadVqa); AsyncLoader l = new AsyncLoader(Media.LoadVqa);
IAsyncResult ar = l.BeginInvoke(s, null, null); IAsyncResult ar = l.BeginInvoke(s, null, null);
onLoadComplete = () => Action onLoadComplete = () =>
{ {
Media.StopFMVInRadar(); Media.StopFMVInRadar();
world.AddFrameEndTask(_ => Media.PlayFMVInRadar(world, l.EndInvoke(ar), onCompleteRadar)); world.AddFrameEndTask(_ => Media.PlayFMVInRadar(world, l.EndInvoke(ar), onCompleteRadar));

View File

@@ -28,12 +28,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Returns a table of players filtered by the specified function.")] [Desc("Returns a table of players filtered by the specified function.")]
public Player[] GetPlayers(LuaFunction filter) public Player[] GetPlayers(LuaFunction filter)
{ {
return Context.World.Players return FilteredObjects(Context.World.Players, filter).ToArray();
.Where(p =>
{
using (var f = filter.Call(p.ToLuaValue(Context)))
return f.First().ToBoolean();
}).ToArray();
} }
} }
} }

View File

@@ -71,7 +71,7 @@ namespace OpenRA.Mods.Common.Scripting
var actors = new List<Actor>(); var actors = new List<Actor>();
for (var i = 0; i < actorTypes.Length; i++) for (var i = 0; i < actorTypes.Length; i++)
{ {
var af = actionFunc != null ? actionFunc.CopyReference() as LuaFunction : null; var af = actionFunc != null ? (LuaFunction)actionFunc.CopyReference() : null;
var actor = CreateActor(owner, actorTypes[i], false, entryPath[0], entryPath.Length > 1 ? entryPath[1] : (CPos?)null); var actor = CreateActor(owner, actorTypes[i], false, entryPath[0], entryPath.Length > 1 ? entryPath[1] : (CPos?)null);
actors.Add(actor); actors.Add(actor);
@@ -86,8 +86,9 @@ namespace OpenRA.Mods.Common.Scripting
{ {
actor.QueueActivity(new CallFunc(() => actor.QueueActivity(new CallFunc(() =>
{ {
af.Call(actor.ToLuaValue(Context)); using (af)
af.Dispose(); using (var a = actor.ToLuaValue(Context))
af.Call(a);
})); }));
} }
}; };
@@ -128,11 +129,12 @@ namespace OpenRA.Mods.Common.Scripting
if (actionFunc != null) if (actionFunc != null)
{ {
var af = actionFunc.CopyReference() as LuaFunction; var af = (LuaFunction)actionFunc.CopyReference();
transport.QueueActivity(new CallFunc(() => transport.QueueActivity(new CallFunc(() =>
{ {
af.Call(transport.ToLuaValue(Context), passengers.ToArray().ToLuaValue(Context)); using (af)
af.Dispose(); using (LuaValue t = transport.ToLuaValue(Context), p = passengers.ToArray().ToLuaValue(Context))
af.Call(t, p);
})); }));
} }
else else
@@ -164,11 +166,12 @@ namespace OpenRA.Mods.Common.Scripting
if (exitFunc != null) if (exitFunc != null)
{ {
var ef = exitFunc.CopyReference() as LuaFunction; var ef = (LuaFunction)exitFunc.CopyReference();
transport.QueueActivity(new CallFunc(() => transport.QueueActivity(new CallFunc(() =>
{ {
ef.Call(transport.ToLuaValue(Context)); using (ef)
ef.Dispose(); using (var t = transport.ToLuaValue(Context))
ef.Call(t);
})); }));
} }
else if (exitPath != null) else if (exitPath != null)
@@ -180,8 +183,16 @@ namespace OpenRA.Mods.Common.Scripting
} }
var ret = Context.CreateTable(); var ret = Context.CreateTable();
ret.Add(1, transport.ToLuaValue(Context)); using (LuaValue
ret.Add(2, passengers.ToArray().ToLuaValue(Context)); tKey = 1,
tValue = transport.ToLuaValue(Context),
pKey = 2,
pValue = passengers.ToArray().ToLuaValue(Context))
{
ret.Add(tKey, tValue);
ret.Add(pKey, pValue);
}
return ret; return ret;
} }
} }

View File

@@ -34,8 +34,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Call a function after a specified delay. The callback function will be called as func().")] [Desc("Call a function after a specified delay. The callback function will be called as func().")]
public void AfterDelay(int delay, LuaFunction func) public void AfterDelay(int delay, LuaFunction func)
{ {
var f = func.CopyReference() as LuaFunction; var f = (LuaFunction)func.CopyReference();
Action doCall = () => Action doCall = () =>
{ {
try try
@@ -78,17 +77,15 @@ namespace OpenRA.Mods.Common.Scripting
public void OnAllKilled(Actor[] actors, LuaFunction func) public void OnAllKilled(Actor[] actors, LuaFunction func)
{ {
var group = actors.ToList(); var group = actors.ToList();
var copy = (LuaFunction)func.CopyReference(); var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberKilled = m => Action<Actor> onMemberKilled = m =>
{ {
try try
{ {
group.Remove(m); group.Remove(m);
if (!group.Any()) if (!group.Any())
{ using (f)
copy.Call(); f.Call();
copy.Dispose();
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -105,7 +102,7 @@ namespace OpenRA.Mods.Common.Scripting
public void OnAnyKilled(Actor[] actors, LuaFunction func) public void OnAnyKilled(Actor[] actors, LuaFunction func)
{ {
var called = false; var called = false;
var copy = (LuaFunction)func.CopyReference(); var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberKilled = m => Action<Actor> onMemberKilled = m =>
{ {
try try
@@ -113,10 +110,10 @@ namespace OpenRA.Mods.Common.Scripting
if (called) if (called)
return; return;
using (f)
using (var killed = m.ToLuaValue(Context)) using (var killed = m.ToLuaValue(Context))
copy.Call(killed).Dispose(); f.Call(killed).Dispose();
copy.Dispose();
called = true; called = true;
} }
catch (Exception e) catch (Exception e)
@@ -191,7 +188,7 @@ namespace OpenRA.Mods.Common.Scripting
{ {
var group = actors.ToList(); var group = actors.ToList();
var copy = (LuaFunction)func.CopyReference(); var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberRemoved = m => Action<Actor> onMemberRemoved = m =>
{ {
try try
@@ -200,10 +197,8 @@ namespace OpenRA.Mods.Common.Scripting
return; return;
if (!group.Any()) if (!group.Any())
{ using (f)
copy.Call().Dispose(); f.Call().Dispose();
copy.Dispose();
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -228,7 +223,7 @@ namespace OpenRA.Mods.Common.Scripting
{ {
var called = false; var called = false;
var copy = (LuaFunction)func.CopyReference(); var f = (LuaFunction)func.CopyReference();
Action<Actor> onKilledOrCaptured = m => Action<Actor> onKilledOrCaptured = m =>
{ {
try try
@@ -236,8 +231,9 @@ namespace OpenRA.Mods.Common.Scripting
if (called) if (called)
return; return;
copy.Call().Dispose(); using (f)
copy.Dispose(); f.Call().Dispose();
called = true; called = true;
} }
catch (Exception e) catch (Exception e)
@@ -256,7 +252,7 @@ namespace OpenRA.Mods.Common.Scripting
{ {
var group = actors.ToList(); var group = actors.ToList();
var copy = (LuaFunction)func.CopyReference(); var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberKilledOrCaptured = m => Action<Actor> onMemberKilledOrCaptured = m =>
{ {
try try
@@ -265,10 +261,8 @@ namespace OpenRA.Mods.Common.Scripting
return; return;
if (!group.Any()) if (!group.Any())
{ using (f)
copy.Call().Dispose(); f.Call().Dispose();
copy.Dispose();
}
} }
catch (Exception e) catch (Exception e)
{ {
@@ -288,8 +282,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")] "The callback function will be called as func(Actor a, int id).")]
public int OnEnteredFootprint(CPos[] cells, LuaFunction func) public int OnEnteredFootprint(CPos[] cells, LuaFunction func)
{ {
var triggerId = 0; // We can't easily dispose onEntry, so we'll have to rely on finalization for it.
var onEntry = (LuaFunction)func.CopyReference(); var onEntry = (LuaFunction)func.CopyReference();
var triggerId = 0;
Action<Actor> invokeEntry = a => Action<Actor> invokeEntry = a =>
{ {
try try
@@ -314,8 +309,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")] "The callback function will be called as func(Actor a, int id).")]
public int OnExitedFootprint(CPos[] cells, LuaFunction func) public int OnExitedFootprint(CPos[] cells, LuaFunction func)
{ {
var triggerId = 0; // We can't easily dispose onExit, so we'll have to rely on finalization for it.
var onExit = (LuaFunction)func.CopyReference(); var onExit = (LuaFunction)func.CopyReference();
var triggerId = 0;
Action<Actor> invokeExit = a => Action<Actor> invokeExit = a =>
{ {
try try
@@ -346,8 +342,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")] "The callback function will be called as func(Actor a, int id).")]
public int OnEnteredProximityTrigger(WPos pos, WDist range, LuaFunction func) public int OnEnteredProximityTrigger(WPos pos, WDist range, LuaFunction func)
{ {
var triggerId = 0; // We can't easily dispose onEntry, so we'll have to rely on finalization for it.
var onEntry = (LuaFunction)func.CopyReference(); var onEntry = (LuaFunction)func.CopyReference();
var triggerId = 0;
Action<Actor> invokeEntry = a => Action<Actor> invokeEntry = a =>
{ {
try try
@@ -372,8 +369,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")] "The callback function will be called as func(Actor a, int id).")]
public int OnExitedProximityTrigger(WPos pos, WDist range, LuaFunction func) public int OnExitedProximityTrigger(WPos pos, WDist range, LuaFunction func)
{ {
var triggerId = 0; // We can't easily dispose onExit, so we'll have to rely on finalization for it.
var onExit = (LuaFunction)func.CopyReference(); var onExit = (LuaFunction)func.CopyReference();
var triggerId = 0;
Action<Actor> invokeExit = a => Action<Actor> invokeExit = a =>
{ {
try try

View File

@@ -33,14 +33,10 @@ namespace OpenRA.Mods.Common.Scripting
public bool Any(LuaValue[] collection, LuaFunction func) public bool Any(LuaValue[] collection, LuaFunction func)
{ {
foreach (var c in collection) foreach (var c in collection)
{
using (var ret = func.Call(c)) using (var ret = func.Call(c))
{ using (var result = ret.FirstOrDefault())
var result = ret.FirstOrDefault();
if (result != null && result.ToBoolean()) if (result != null && result.ToBoolean())
return true; return true;
}
}
return false; return false;
} }
@@ -49,14 +45,10 @@ namespace OpenRA.Mods.Common.Scripting
public bool All(LuaValue[] collection, LuaFunction func) public bool All(LuaValue[] collection, LuaFunction func)
{ {
foreach (var c in collection) foreach (var c in collection)
{
using (var ret = func.Call(c)) using (var ret = func.Call(c))
{ using (var result = ret.FirstOrDefault())
var result = ret.FirstOrDefault();
if (result == null || !result.ToBoolean()) if (result == null || !result.ToBoolean())
return false; return false;
}
}
return true; return true;
} }
@@ -64,7 +56,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Returns the first n values from a collection.")] [Desc("Returns the first n values from a collection.")]
public LuaValue[] Take(int n, LuaValue[] source) public LuaValue[] Take(int n, LuaValue[] source)
{ {
return source.Take(n).ToArray(); return source.Take(n).Select(v => v.CopyReference()).ToArray();
} }
[Desc("Skips over the first numElements members of a table and return the rest.")] [Desc("Skips over the first numElements members of a table and return the rest.")]
@@ -73,7 +65,8 @@ namespace OpenRA.Mods.Common.Scripting
var t = Context.CreateTable(); var t = Context.CreateTable();
for (var i = numElements; i <= table.Count; i++) for (var i = numElements; i <= table.Count; i++)
t.Add(t.Count + 1, table[i]); using (LuaValue key = t.Count + 1, value = table[i])
t.Add(key, value);
return t; return t;
} }
@@ -81,7 +74,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Returns a random value from a collection.")] [Desc("Returns a random value from a collection.")]
public LuaValue Random(LuaValue[] collection) public LuaValue Random(LuaValue[] collection)
{ {
return collection.Random(Context.World.SharedRandom); return collection.Random(Context.World.SharedRandom).CopyReference();
} }
[Desc("Expands the given footprint one step along the coordinate axes, and (if requested) diagonals.")] [Desc("Expands the given footprint one step along the coordinate axes, and (if requested) diagonals.")]

View File

@@ -28,8 +28,13 @@ namespace OpenRA.Mods.RA.Scripting
{ {
Actor actor; Actor actor;
CPos cell; CPos cell;
if (!kv.Key.TryGetClrValue<Actor>(out actor) || !kv.Value.TryGetClrValue<CPos>(out cell)) using (kv.Key)
throw new LuaException("Chronoshift requires a table of Actor,CPos pairs. Received {0},{1}".F(kv.Key.WrappedClrType().Name, kv.Value.WrappedClrType().Name)); using (kv.Value)
{
if (!kv.Key.TryGetClrValue(out actor) || !kv.Value.TryGetClrValue(out cell))
throw new LuaException("Chronoshift requires a table of Actor,CPos pairs. Received {0},{1}".F(
kv.Key.WrappedClrType().Name, kv.Value.WrappedClrType().Name));
}
var cs = actor.TraitOrDefault<Chronoshiftable>(); var cs = actor.TraitOrDefault<Chronoshiftable>();
if (cs != null && cs.CanChronoshiftTo(actor, cell)) if (cs != null && cs.CanChronoshiftTo(actor, cell))