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
{
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 names = type.GetCustomAttributes<ScriptGlobalAttribute>(true);
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;
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

View File

@@ -43,6 +43,9 @@ namespace OpenRA.Scripting
}
LuaValue Invoke(LuaVararg args)
{
object[] clrArgs = null;
try
{
if (!IsMethod)
throw new LuaException("Trying to invoke a ScriptMemberWrapper that isn't a method!");
@@ -50,7 +53,8 @@ namespace OpenRA.Scripting
var mi = (MethodInfo)Member;
var pi = mi.GetParameters();
var clrArgs = new object[pi.Length];
clrArgs = new object[pi.Length];
var argCount = args.Count;
for (var i = 0; i < pi.Length; i++)
{
@@ -67,8 +71,27 @@ namespace OpenRA.Scripting
throw new LuaException("Unable to convert parameter {0} to {1}".F(i, pi[i].ParameterType.Name));
}
var ret = mi.Invoke(Target, clrArgs);
return ret.ToLuaValue(context);
return mi.Invoke(Target, clrArgs).ToLuaValue(context);
}
finally
{
// Clean up all the Lua arguments that were given to us.
foreach (var arg in args)
arg.Dispose();
args.Dispose();
// If we created any arrays of LuaValues to pass around, we need to dispose those too.
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)
@@ -77,10 +100,7 @@ namespace OpenRA.Scripting
return runtime.CreateFunctionFromDelegate((Func<LuaVararg, LuaValue>)Invoke);
if (IsGetProperty)
{
var pi = Member as PropertyInfo;
return pi.GetValue(Target, null).ToLuaValue(context);
}
return ((PropertyInfo)Member).GetValue(Target, null).ToLuaValue(context);
throw new LuaException("The property '{0}' is write-only".F(Member.Name));
}
@@ -89,7 +109,7 @@ namespace OpenRA.Scripting
{
if (IsSetProperty)
{
var pi = Member as PropertyInfo;
var pi = (PropertyInfo)Member;
object 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));

View File

@@ -106,15 +106,24 @@ namespace OpenRA.Scripting
var i = 0;
foreach (var kv in table)
{
using (kv.Key)
{
object element;
if (innerType == typeof(LuaValue))
element = kv.Value;
else if (!kv.Value.TryGetClrValue(innerType, out element))
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++);
}
}
clrObject = array;
return true;
@@ -159,11 +168,13 @@ namespace OpenRA.Scripting
if (obj is Array)
{
var array = obj as Array;
var array = (Array)obj;
var i = 1;
var table = context.CreateTable();
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;
}

View File

@@ -28,6 +28,9 @@ namespace OpenRA.Mods.Common.Scripting
// Convert table entries into ActorInits
foreach (var kv in initTable)
{
using (kv.Key)
using (kv.Value)
{
// Find the requested type
var typeName = kv.Key.ToString();
@@ -50,6 +53,7 @@ namespace OpenRA.Mods.Common.Scripting
var test = initType.GetConstructor(new[] { innerType }).Invoke(new[] { value });
initDict.Add(test);
}
}
// The actor must be added to the world at the end of the tick
var a = Context.World.CreateActor(false, type, initDict);

View File

@@ -8,7 +8,6 @@
*/
#endregion
using System;
using System.Linq;
using Eluant;
using OpenRA.Mods.Common.Traits;
@@ -34,34 +33,14 @@ namespace OpenRA.Mods.Common.Scripting
public Actor[] ActorsInCircle(WPos location, WDist radius, LuaFunction filter = null)
{
var actors = Context.World.FindActorsInCircle(location, radius);
if (filter != null)
{
actors = actors.Where(a =>
{
using (var f = filter.Call(a.ToLuaValue(Context)))
return f.First().ToBoolean();
});
}
return actors.ToArray();
return FilteredObjects(actors, filter).ToArray();
}
[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)
{
var actors = Context.World.ActorMap.ActorsInBox(topLeft, bottomRight);
if (filter != null)
{
actors = actors.Where(a =>
{
using (var f = filter.Call(a.ToLuaValue(Context)))
return f.First().ToBoolean();
});
}
return actors.ToArray();
return FilteredObjects(actors, filter).ToArray();
}
[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);
}
Action onComplete;
[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)
{
@@ -65,8 +64,8 @@ namespace OpenRA.Mods.Common.Scripting
if (func != null)
{
var f = func.CopyReference() as LuaFunction;
onComplete = () =>
var f = (LuaFunction)func.CopyReference();
Action onComplete = () =>
{
try
{
@@ -113,13 +112,13 @@ namespace OpenRA.Mods.Common.Scripting
playlist.Stop();
}
Action onCompleteFullscreen;
[Desc("Play a VQA video fullscreen. File name has to include the file extension.")]
public void PlayMovieFullscreen(string movie, LuaFunction func = null)
{
Action onCompleteFullscreen;
if (func != null)
{
var f = func.CopyReference() as LuaFunction;
var f = (LuaFunction)func.CopyReference();
onCompleteFullscreen = () =>
{
try
@@ -139,15 +138,14 @@ namespace OpenRA.Mods.Common.Scripting
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. " +
"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)
{
Action onCompleteRadar;
if (playComplete != null)
{
var f = playComplete.CopyReference() as LuaFunction;
var f = (LuaFunction)playComplete.CopyReference();
onCompleteRadar = () =>
{
try
@@ -178,7 +176,7 @@ namespace OpenRA.Mods.Common.Scripting
AsyncLoader l = new AsyncLoader(Media.LoadVqa);
IAsyncResult ar = l.BeginInvoke(s, null, null);
onLoadComplete = () =>
Action onLoadComplete = () =>
{
Media.StopFMVInRadar();
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.")]
public Player[] GetPlayers(LuaFunction filter)
{
return Context.World.Players
.Where(p =>
{
using (var f = filter.Call(p.ToLuaValue(Context)))
return f.First().ToBoolean();
}).ToArray();
return FilteredObjects(Context.World.Players, filter).ToArray();
}
}
}

View File

@@ -71,7 +71,7 @@ namespace OpenRA.Mods.Common.Scripting
var actors = new List<Actor>();
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);
actors.Add(actor);
@@ -86,8 +86,9 @@ namespace OpenRA.Mods.Common.Scripting
{
actor.QueueActivity(new CallFunc(() =>
{
af.Call(actor.ToLuaValue(Context));
af.Dispose();
using (af)
using (var a = actor.ToLuaValue(Context))
af.Call(a);
}));
}
};
@@ -128,11 +129,12 @@ namespace OpenRA.Mods.Common.Scripting
if (actionFunc != null)
{
var af = actionFunc.CopyReference() as LuaFunction;
var af = (LuaFunction)actionFunc.CopyReference();
transport.QueueActivity(new CallFunc(() =>
{
af.Call(transport.ToLuaValue(Context), passengers.ToArray().ToLuaValue(Context));
af.Dispose();
using (af)
using (LuaValue t = transport.ToLuaValue(Context), p = passengers.ToArray().ToLuaValue(Context))
af.Call(t, p);
}));
}
else
@@ -164,11 +166,12 @@ namespace OpenRA.Mods.Common.Scripting
if (exitFunc != null)
{
var ef = exitFunc.CopyReference() as LuaFunction;
var ef = (LuaFunction)exitFunc.CopyReference();
transport.QueueActivity(new CallFunc(() =>
{
ef.Call(transport.ToLuaValue(Context));
ef.Dispose();
using (ef)
using (var t = transport.ToLuaValue(Context))
ef.Call(t);
}));
}
else if (exitPath != null)
@@ -180,8 +183,16 @@ namespace OpenRA.Mods.Common.Scripting
}
var ret = Context.CreateTable();
ret.Add(1, transport.ToLuaValue(Context));
ret.Add(2, passengers.ToArray().ToLuaValue(Context));
using (LuaValue
tKey = 1,
tValue = transport.ToLuaValue(Context),
pKey = 2,
pValue = passengers.ToArray().ToLuaValue(Context))
{
ret.Add(tKey, tValue);
ret.Add(pKey, pValue);
}
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().")]
public void AfterDelay(int delay, LuaFunction func)
{
var f = func.CopyReference() as LuaFunction;
var f = (LuaFunction)func.CopyReference();
Action doCall = () =>
{
try
@@ -78,17 +77,15 @@ namespace OpenRA.Mods.Common.Scripting
public void OnAllKilled(Actor[] actors, LuaFunction func)
{
var group = actors.ToList();
var copy = (LuaFunction)func.CopyReference();
var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberKilled = m =>
{
try
{
group.Remove(m);
if (!group.Any())
{
copy.Call();
copy.Dispose();
}
using (f)
f.Call();
}
catch (Exception e)
{
@@ -105,7 +102,7 @@ namespace OpenRA.Mods.Common.Scripting
public void OnAnyKilled(Actor[] actors, LuaFunction func)
{
var called = false;
var copy = (LuaFunction)func.CopyReference();
var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberKilled = m =>
{
try
@@ -113,10 +110,10 @@ namespace OpenRA.Mods.Common.Scripting
if (called)
return;
using (f)
using (var killed = m.ToLuaValue(Context))
copy.Call(killed).Dispose();
f.Call(killed).Dispose();
copy.Dispose();
called = true;
}
catch (Exception e)
@@ -191,7 +188,7 @@ namespace OpenRA.Mods.Common.Scripting
{
var group = actors.ToList();
var copy = (LuaFunction)func.CopyReference();
var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberRemoved = m =>
{
try
@@ -200,10 +197,8 @@ namespace OpenRA.Mods.Common.Scripting
return;
if (!group.Any())
{
copy.Call().Dispose();
copy.Dispose();
}
using (f)
f.Call().Dispose();
}
catch (Exception e)
{
@@ -228,7 +223,7 @@ namespace OpenRA.Mods.Common.Scripting
{
var called = false;
var copy = (LuaFunction)func.CopyReference();
var f = (LuaFunction)func.CopyReference();
Action<Actor> onKilledOrCaptured = m =>
{
try
@@ -236,8 +231,9 @@ namespace OpenRA.Mods.Common.Scripting
if (called)
return;
copy.Call().Dispose();
copy.Dispose();
using (f)
f.Call().Dispose();
called = true;
}
catch (Exception e)
@@ -256,7 +252,7 @@ namespace OpenRA.Mods.Common.Scripting
{
var group = actors.ToList();
var copy = (LuaFunction)func.CopyReference();
var f = (LuaFunction)func.CopyReference();
Action<Actor> onMemberKilledOrCaptured = m =>
{
try
@@ -265,10 +261,8 @@ namespace OpenRA.Mods.Common.Scripting
return;
if (!group.Any())
{
copy.Call().Dispose();
copy.Dispose();
}
using (f)
f.Call().Dispose();
}
catch (Exception e)
{
@@ -288,8 +282,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")]
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 triggerId = 0;
Action<Actor> invokeEntry = a =>
{
try
@@ -314,8 +309,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")]
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 triggerId = 0;
Action<Actor> invokeExit = a =>
{
try
@@ -346,8 +342,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")]
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 triggerId = 0;
Action<Actor> invokeEntry = a =>
{
try
@@ -372,8 +369,9 @@ namespace OpenRA.Mods.Common.Scripting
"The callback function will be called as func(Actor a, int id).")]
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 triggerId = 0;
Action<Actor> invokeExit = a =>
{
try

View File

@@ -33,14 +33,10 @@ namespace OpenRA.Mods.Common.Scripting
public bool Any(LuaValue[] collection, LuaFunction func)
{
foreach (var c in collection)
{
using (var ret = func.Call(c))
{
var result = ret.FirstOrDefault();
using (var result = ret.FirstOrDefault())
if (result != null && result.ToBoolean())
return true;
}
}
return false;
}
@@ -49,14 +45,10 @@ namespace OpenRA.Mods.Common.Scripting
public bool All(LuaValue[] collection, LuaFunction func)
{
foreach (var c in collection)
{
using (var ret = func.Call(c))
{
var result = ret.FirstOrDefault();
using (var result = ret.FirstOrDefault())
if (result == null || !result.ToBoolean())
return false;
}
}
return true;
}
@@ -64,7 +56,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Returns the first n values from a collection.")]
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.")]
@@ -73,7 +65,8 @@ namespace OpenRA.Mods.Common.Scripting
var t = Context.CreateTable();
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;
}
@@ -81,7 +74,7 @@ namespace OpenRA.Mods.Common.Scripting
[Desc("Returns a random value from a 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.")]

View File

@@ -28,8 +28,13 @@ namespace OpenRA.Mods.RA.Scripting
{
Actor actor;
CPos cell;
if (!kv.Key.TryGetClrValue<Actor>(out actor) || !kv.Value.TryGetClrValue<CPos>(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));
using (kv.Key)
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>();
if (cs != null && cs.CanChronoshiftTo(actor, cell))