diff --git a/OpenRA.Game/Scripting/ScriptContext.cs b/OpenRA.Game/Scripting/ScriptContext.cs index 9e274d5423..e98c5b410b 100644 --- a/OpenRA.Game/Scripting/ScriptContext.cs +++ b/OpenRA.Game/Scripting/ScriptContext.cs @@ -64,7 +64,19 @@ namespace OpenRA.Scripting } } - // For global-level bindings + /// + /// Provides global bindings in Lua code. + /// + /// + /// Instance methods and properties declared in derived classes will be made available in Lua. Use + /// on your derived class to specify the name exposed in Lua. It is recommended + /// to apply against each method or property to provide a description of what it does. + /// + /// Any parameters to your method that are 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 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. + /// 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(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 FilteredObjects(IEnumerable 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 diff --git a/OpenRA.Game/Scripting/ScriptMemberWrapper.cs b/OpenRA.Game/Scripting/ScriptMemberWrapper.cs index 9b470762a1..3eaa4d7f2d 100644 --- a/OpenRA.Game/Scripting/ScriptMemberWrapper.cs +++ b/OpenRA.Game/Scripting/ScriptMemberWrapper.cs @@ -44,31 +44,54 @@ namespace OpenRA.Scripting LuaValue Invoke(LuaVararg args) { - if (!IsMethod) - throw new LuaException("Trying to invoke a ScriptMemberWrapper that isn't a method!"); - - 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++) + object[] clrArgs = null; + try { - if (i >= argCount) - { - if (!pi[i].IsOptional) - throw new LuaException("Argument '{0}' of '{1}' is not optional.".F(pi[i].LuaDocString(), Member.LuaDocString())); + if (!IsMethod) + throw new LuaException("Trying to invoke a ScriptMemberWrapper that isn't a method!"); - clrArgs[i] = pi[i].DefaultValue; - continue; + var mi = (MethodInfo)Member; + 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])) - throw new LuaException("Unable to convert parameter {0} to {1}".F(i, pi[i].ParameterType.Name)); + 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(); - var ret = mi.Invoke(Target, clrArgs); - return ret.ToLuaValue(context); + // 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)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)); diff --git a/OpenRA.Game/Scripting/ScriptTypes.cs b/OpenRA.Game/Scripting/ScriptTypes.cs index 14a792b7f6..d9011d2dac 100644 --- a/OpenRA.Game/Scripting/ScriptTypes.cs +++ b/OpenRA.Game/Scripting/ScriptTypes.cs @@ -107,13 +107,22 @@ namespace OpenRA.Scripting foreach (var kv in table) { - object element; - if (innerType == typeof(LuaValue)) - element = kv.Value; - else if (!kv.Value.TryGetClrValue(innerType, out element)) - throw new LuaException("Unable to convert table value of type {0} to type {1}".F(kv.Value.WrappedClrType(), innerType)); + using (kv.Key) + { + object element; + if (innerType == typeof(LuaValue)) + 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; @@ -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; } diff --git a/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs index 56da1f3bd1..4042f847e3 100644 --- a/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/ActorGlobal.cs @@ -29,26 +29,30 @@ namespace OpenRA.Mods.Common.Scripting // Convert table entries into ActorInits foreach (var kv in initTable) { - // Find the requested type - 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)); + using (kv.Key) + using (kv.Value) + { + // Find the requested type + 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 - var genericType = initType.GetInterfaces() - .First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IActorInit<>)); - var innerType = genericType.GetGenericArguments().First(); - var valueType = innerType.IsEnum ? typeof(int) : innerType; + // Cast it up to an IActorInit + var genericType = initType.GetInterfaces() + .First(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(IActorInit<>)); + var innerType = genericType.GetGenericArguments().First(); + var valueType = innerType.IsEnum ? typeof(int) : innerType; - // Try and coerce the table value to the required type - object value; - if (!kv.Value.TryGetClrValue(valueType, out value)) - throw new LuaException("Invalid data type for '{0}' (expected '{1}')".F(typeName, valueType.Name)); + // Try and coerce the table value to the required type + object value; + if (!kv.Value.TryGetClrValue(valueType, out value)) + throw new LuaException("Invalid data type for '{0}' (expected '{1}')".F(typeName, valueType.Name)); - // Construct the ActorInit. Phew! - var test = initType.GetConstructor(new[] { innerType }).Invoke(new[] { value }); - initDict.Add(test); + // Construct the ActorInit. Phew! + 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 diff --git a/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs index aa0f794968..01fda97235 100644 --- a/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/MapGlobal.cs @@ -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).")] diff --git a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs index f680774eea..7c4ef02d8b 100644 --- a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs @@ -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)); diff --git a/OpenRA.Mods.Common/Scripting/Global/PlayerGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/PlayerGlobal.cs index d570de1982..d871db5c53 100644 --- a/OpenRA.Mods.Common/Scripting/Global/PlayerGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/PlayerGlobal.cs @@ -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(); } } } diff --git a/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs index ec62bf1dd4..26210da7ca 100644 --- a/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/ReinforcementsGlobal.cs @@ -71,7 +71,7 @@ namespace OpenRA.Mods.Common.Scripting var actors = new List(); 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; } } diff --git a/OpenRA.Mods.Common/Scripting/Global/TriggerGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/TriggerGlobal.cs index ac2d4b55db..6b49bfd0d7 100644 --- a/OpenRA.Mods.Common/Scripting/Global/TriggerGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/TriggerGlobal.cs @@ -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 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 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 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 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 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 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 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 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 invokeExit = a => { try diff --git a/OpenRA.Mods.Common/Scripting/Global/UtilsGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/UtilsGlobal.cs index a7c4c55016..dcdfd71d61 100644 --- a/OpenRA.Mods.Common/Scripting/Global/UtilsGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/UtilsGlobal.cs @@ -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.")] diff --git a/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs b/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs index 634e0528ef..68c1a856c9 100644 --- a/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs +++ b/OpenRA.Mods.RA/Scripting/Properties/ChronosphereProperties.cs @@ -28,8 +28,13 @@ namespace OpenRA.Mods.RA.Scripting { Actor actor; CPos cell; - 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)); + 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(); if (cs != null && cs.CanChronoshiftTo(actor, cell))