Merge pull request #6776 from pchote/better-lua-errors

Closes #5846
This commit is contained in:
Matthias Mailänder
2014-10-18 12:24:32 +02:00
13 changed files with 306 additions and 62 deletions

View File

@@ -11,6 +11,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Drawing; using System.Drawing;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@@ -118,6 +119,8 @@ namespace OpenRA.Scripting
{ {
runtime = new MemoryConstrainedLuaRuntime(); runtime = new MemoryConstrainedLuaRuntime();
Log.AddChannel("lua", "lua.log");
World = world; World = world;
WorldRenderer = worldRenderer; WorldRenderer = worldRenderer;
knownActorCommands = Game.modData.ObjectCreator knownActorCommands = Game.modData.ObjectCreator
@@ -141,7 +144,7 @@ namespace OpenRA.Scripting
using (var registerGlobal = (LuaFunction)runtime.Globals["RegisterSandboxedGlobal"]) using (var registerGlobal = (LuaFunction)runtime.Globals["RegisterSandboxedGlobal"])
{ {
using (var fn = runtime.CreateFunctionFromDelegate((Action<string>)Console.WriteLine)) using (var fn = runtime.CreateFunctionFromDelegate((Action<string>)LogDebugMessage))
registerGlobal.Call("print", fn).Dispose(); registerGlobal.Call("print", fn).Dispose();
// Register global tables // Register global tables
@@ -173,12 +176,30 @@ namespace OpenRA.Scripting
} }
} }
bool error; void LogDebugMessage(string message)
{
Console.WriteLine("Lua debug: {0}", message);
Log.Write("lua", message);
}
public bool FatalErrorOccurred { get; private set; }
public void FatalError(string message) public void FatalError(string message)
{ {
var stacktrace = new StackTrace().ToString();
Console.WriteLine("Fatal Lua Error: {0}", message); Console.WriteLine("Fatal Lua Error: {0}", message);
Game.AddChatLine(Color.White, "Fatal Lua Error", message); Console.WriteLine(stacktrace);
error = true;
Log.Write("lua", "Fatal Lua Error: {0}", message);
Log.Write("lua", stacktrace);
FatalErrorOccurred = true;
World.AddFrameEndTask(w =>
{
World.EndGame();
World.SetPauseState(true);
World.PauseStateLocked = true;
});
} }
public void RegisterMapActor(string name, Actor a) public void RegisterMapActor(string name, Actor a)
@@ -195,7 +216,7 @@ namespace OpenRA.Scripting
public void WorldLoaded() public void WorldLoaded()
{ {
if (error) if (FatalErrorOccurred)
return; return;
using (var worldLoaded = (LuaFunction)runtime.Globals["WorldLoaded"]) using (var worldLoaded = (LuaFunction)runtime.Globals["WorldLoaded"])
@@ -204,7 +225,7 @@ namespace OpenRA.Scripting
public void Tick(Actor self) public void Tick(Actor self)
{ {
if (error || disposed) if (FatalErrorOccurred || disposed)
return; return;
using (new PerfSample("tick_lua")) using (new PerfSample("tick_lua"))
@@ -215,6 +236,7 @@ namespace OpenRA.Scripting
{ {
if (disposed) if (disposed)
return; return;
disposed = true; disposed = true;
if (runtime != null) if (runtime != null)
runtime.Dispose(); runtime.Dispose();

View File

@@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA.Scripting
using (f) using (f)
f.Call(); f.Call();
} }
catch (LuaException e) catch (Exception e)
{ {
context.FatalError(e.Message); context.FatalError(e.Message);
} }
@@ -81,11 +81,18 @@ namespace OpenRA.Mods.RA.Scripting
var copy = (LuaFunction)func.CopyReference(); var copy = (LuaFunction)func.CopyReference();
Action<Actor> OnMemberKilled = m => Action<Actor> OnMemberKilled = m =>
{ {
group.Remove(m); try
if (!group.Any())
{ {
copy.Call(); group.Remove(m);
copy.Dispose(); if (!group.Any())
{
copy.Call();
copy.Dispose();
}
}
catch (Exception e)
{
context.FatalError(e.Message);
} }
}; };
@@ -101,14 +108,21 @@ namespace OpenRA.Mods.RA.Scripting
var copy = (LuaFunction)func.CopyReference(); var copy = (LuaFunction)func.CopyReference();
Action<Actor> OnMemberKilled = m => Action<Actor> OnMemberKilled = m =>
{ {
if (called) try
return; {
if (called)
return;
using (var killed = m.ToLuaValue(context)) using (var killed = m.ToLuaValue(context))
copy.Call(killed).Dispose(); copy.Call(killed).Dispose();
copy.Dispose(); copy.Dispose();
called = true; called = true;
}
catch (Exception e)
{
context.FatalError(e.Message);
}
}; };
foreach (var a in actors) foreach (var a in actors)
@@ -180,11 +194,18 @@ namespace OpenRA.Mods.RA.Scripting
var copy = (LuaFunction)func.CopyReference(); var copy = (LuaFunction)func.CopyReference();
Action<Actor> OnMemberRemoved = m => Action<Actor> OnMemberRemoved = m =>
{ {
group.Remove(m); try
if (!group.Any())
{ {
copy.Call().Dispose(); group.Remove(m);
copy.Dispose(); if (!group.Any())
{
copy.Call().Dispose();
copy.Dispose();
}
}
catch (Exception e)
{
context.FatalError(e.Message);
} }
}; };
@@ -208,9 +229,16 @@ namespace OpenRA.Mods.RA.Scripting
var onEntry = (LuaFunction)func.CopyReference(); var onEntry = (LuaFunction)func.CopyReference();
Action<Actor> invokeEntry = a => Action<Actor> invokeEntry = a =>
{ {
using (var luaActor = a.ToLuaValue(context)) try
using (var id = triggerId.ToLuaValue(context)) {
onEntry.Call(luaActor, id).Dispose(); using (var luaActor = a.ToLuaValue(context))
using (var id = triggerId.ToLuaValue(context))
onEntry.Call(luaActor, id).Dispose();
}
catch (Exception e)
{
context.FatalError(e.Message);
}
}; };
triggerId = context.World.ActorMap.AddCellTrigger(cells, invokeEntry, null); triggerId = context.World.ActorMap.AddCellTrigger(cells, invokeEntry, null);
@@ -227,9 +255,16 @@ namespace OpenRA.Mods.RA.Scripting
var onExit = (LuaFunction)func.CopyReference(); var onExit = (LuaFunction)func.CopyReference();
Action<Actor> invokeExit = a => Action<Actor> invokeExit = a =>
{ {
using (var luaActor = a.ToLuaValue(context)) try
using (var id = triggerId.ToLuaValue(context)) {
onExit.Call(luaActor, id).Dispose(); using (var luaActor = a.ToLuaValue(context))
using (var id = triggerId.ToLuaValue(context))
onExit.Call(luaActor, id).Dispose();
}
catch (Exception e)
{
context.FatalError(e.Message);
}
}; };
triggerId = context.World.ActorMap.AddCellTrigger(cells, null, invokeExit); triggerId = context.World.ActorMap.AddCellTrigger(cells, null, invokeExit);

View File

@@ -50,5 +50,7 @@ namespace OpenRA.Mods.RA.Scripting
if (context != null) if (context != null)
context.Dispose(); context.Dispose();
} }
public bool FatalErrorOccurred { get { return context.FatalErrorOccurred; } }
} }
} }

View File

@@ -45,25 +45,55 @@ namespace OpenRA.Mods.RA.Scripting
public void TickIdle(Actor self) public void TickIdle(Actor self)
{ {
foreach (var f in Triggers[Trigger.OnIdle]) foreach (var f in Triggers[Trigger.OnIdle])
using (var a = self.ToLuaValue(f.Second)) {
f.First.Call(a).Dispose(); try
{
using (var a = self.ToLuaValue(f.Second))
f.First.Call(a).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void Damaged(Actor self, AttackInfo e) public void Damaged(Actor self, AttackInfo e)
{ {
foreach (var f in Triggers[Trigger.OnDamaged]) foreach (var f in Triggers[Trigger.OnDamaged])
using (var a = self.ToLuaValue(f.Second)) {
using (var b = e.Attacker.ToLuaValue(f.Second)) try
f.First.Call(a, b).Dispose(); {
using (var a = self.ToLuaValue(f.Second))
using (var b = e.Attacker.ToLuaValue(f.Second))
f.First.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void Killed(Actor self, AttackInfo e) public void Killed(Actor self, AttackInfo e)
{ {
// Run Lua callbacks // Run Lua callbacks
foreach (var f in Triggers[Trigger.OnKilled]) foreach (var f in Triggers[Trigger.OnKilled])
using (var a = self.ToLuaValue(f.Second)) {
using (var b = e.Attacker.ToLuaValue(f.Second)) try
f.First.Call(a, b).Dispose(); {
using (var a = self.ToLuaValue(f.Second))
using (var b = e.Attacker.ToLuaValue(f.Second))
f.First.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks // Run any internally bound callbacks
OnKilledInternal(self); OnKilledInternal(self);
@@ -72,72 +102,162 @@ namespace OpenRA.Mods.RA.Scripting
public void UnitProduced(Actor self, Actor other, CPos exit) public void UnitProduced(Actor self, Actor other, CPos exit)
{ {
foreach (var f in Triggers[Trigger.OnProduction]) foreach (var f in Triggers[Trigger.OnProduction])
using (var a = self.ToLuaValue(f.Second)) {
using (var b = other.ToLuaValue(f.Second)) try
f.First.Call(a, b).Dispose(); {
using (var a = self.ToLuaValue(f.Second))
using (var b = other.ToLuaValue(f.Second))
f.First.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void OnPlayerWon(Player player) public void OnPlayerWon(Player player)
{ {
foreach (var f in Triggers[Trigger.OnPlayerWon]) foreach (var f in Triggers[Trigger.OnPlayerWon])
using (var a = player.ToLuaValue(f.Second)) {
f.First.Call(a).Dispose(); try
{
using (var a = player.ToLuaValue(f.Second))
f.First.Call(a).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void OnPlayerLost(Player player) public void OnPlayerLost(Player player)
{ {
foreach (var f in Triggers[Trigger.OnPlayerLost]) foreach (var f in Triggers[Trigger.OnPlayerLost])
using (var a = player.ToLuaValue(f.Second)) {
f.First.Call(a).Dispose(); try
{
using (var a = player.ToLuaValue(f.Second))
f.First.Call(a).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void OnObjectiveAdded(Player player, int id) public void OnObjectiveAdded(Player player, int id)
{ {
foreach (var f in Triggers[Trigger.OnObjectiveAdded]) foreach (var f in Triggers[Trigger.OnObjectiveAdded])
using (var a = player.ToLuaValue(f.Second)) {
using (var b = id.ToLuaValue(f.Second)) try
f.First.Call(a, b).Dispose(); {
using (var a = player.ToLuaValue(f.Second))
using (var b = id.ToLuaValue(f.Second))
f.First.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void OnObjectiveCompleted(Player player, int id) public void OnObjectiveCompleted(Player player, int id)
{ {
foreach (var f in Triggers[Trigger.OnObjectiveCompleted]) foreach (var f in Triggers[Trigger.OnObjectiveCompleted])
using (var a = player.ToLuaValue(f.Second)) {
using (var b = id.ToLuaValue(f.Second)) try
f.First.Call(a, b).Dispose(); {
using (var a = player.ToLuaValue(f.Second))
using (var b = id.ToLuaValue(f.Second))
f.First.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void OnObjectiveFailed(Player player, int id) public void OnObjectiveFailed(Player player, int id)
{ {
foreach (var f in Triggers[Trigger.OnObjectiveFailed]) foreach (var f in Triggers[Trigger.OnObjectiveFailed])
using (var a = player.ToLuaValue(f.Second)) {
using (var b = id.ToLuaValue(f.Second)) try
f.First.Call(a, b).Dispose(); {
using (var a = player.ToLuaValue(f.Second))
using (var b = id.ToLuaValue(f.Second))
f.First.Call(a, b).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner) public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner)
{ {
foreach (var f in Triggers[Trigger.OnCapture]) foreach (var f in Triggers[Trigger.OnCapture])
using (var a = self.ToLuaValue(f.Second)) {
using (var b = captor.ToLuaValue(f.Second)) try
using (var c = oldOwner.ToLuaValue(f.Second)) {
using (var d = newOwner.ToLuaValue(f.Second)) using (var a = self.ToLuaValue(f.Second))
f.First.Call(a, b, c, d).Dispose(); using (var b = captor.ToLuaValue(f.Second))
using (var c = oldOwner.ToLuaValue(f.Second))
using (var d = newOwner.ToLuaValue(f.Second))
f.First.Call(a, b, c, d).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void AddedToWorld(Actor self) public void AddedToWorld(Actor self)
{ {
foreach (var f in Triggers[Trigger.OnAddedToWorld]) foreach (var f in Triggers[Trigger.OnAddedToWorld])
using (var a = self.ToLuaValue(f.Second)) {
f.First.Call(a).Dispose(); try
{
using (var a = self.ToLuaValue(f.Second))
f.First.Call(a).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
} }
public void RemovedFromWorld(Actor self) public void RemovedFromWorld(Actor self)
{ {
// Run Lua callbacks // Run Lua callbacks
foreach (var f in Triggers[Trigger.OnRemovedFromWorld]) foreach (var f in Triggers[Trigger.OnRemovedFromWorld])
using (var a = self.ToLuaValue(f.Second)) {
f.First.Call(a).Dispose(); try
{
using (var a = self.ToLuaValue(f.Second))
f.First.Call(a).Dispose();
}
catch (Exception ex)
{
f.Second.FatalError(ex.Message);
return;
}
}
// Run any internally bound callbacks // Run any internally bound callbacks
OnRemovedInternal(self); OnRemovedInternal(self);

View File

@@ -11,6 +11,7 @@
using System; using System;
using System.Drawing; using System.Drawing;
using System.Linq; using System.Linq;
using OpenRA.Mods.RA.Scripting;
using OpenRA.Network; using OpenRA.Network;
using OpenRA.Traits; using OpenRA.Traits;
using OpenRA.Widgets; using OpenRA.Widgets;
@@ -34,9 +35,11 @@ namespace OpenRA.Mods.RA.Widgets
var showStats = false; var showStats = false;
var scriptContext = world.WorldActor.TraitOrDefault<LuaScript>();
var iop = world.WorldActor.TraitsImplementing<IObjectivesPanel>().FirstOrDefault(); var iop = world.WorldActor.TraitsImplementing<IObjectivesPanel>().FirstOrDefault();
var isMultiplayer = !world.LobbyInfo.IsSinglePlayer && !world.IsReplay; var isMultiplayer = !world.LobbyInfo.IsSinglePlayer && !world.IsReplay;
var hasObjectives = iop != null && iop.PanelName != null && world.LocalPlayer != null; var hasError = scriptContext != null && scriptContext.FatalErrorOccurred;
var hasObjectives = hasError || (iop != null && iop.PanelName != null && world.LocalPlayer != null);
var showTabs = hasObjectives && isMultiplayer; var showTabs = hasObjectives && isMultiplayer;
currentTab = hasObjectives ? Tab.Objectives : Tab.Chat; currentTab = hasObjectives ? Tab.Objectives : Tab.Chat;
@@ -114,8 +117,9 @@ namespace OpenRA.Mods.RA.Widgets
if (hasObjectives) if (hasObjectives)
{ {
var panel = hasError ? "SCRIPT_ERROR_PANEL" : iop.PanelName;
var objectivesContainer = dialog.Get<ContainerWidget>("OBJECTIVES_PANEL"); var objectivesContainer = dialog.Get<ContainerWidget>("OBJECTIVES_PANEL");
Game.LoadWidget(world, iop.PanelName, objectivesContainer, new WidgetArgs()); Game.LoadWidget(world, panel, objectivesContainer, new WidgetArgs());
objectivesContainer.IsVisible = () => currentTab == Tab.Objectives; objectivesContainer.IsVisible = () => currentTab == Tab.Objectives;
} }

View File

@@ -0,0 +1,27 @@
Container@SCRIPT_ERROR_PANEL:
Height: PARENT_BOTTOM
Width: PARENT_RIGHT
Children:
Label@DESCA:
X: 15
Y: 15
Width: PARENT_RIGHT-30
Height: 20
Font: Bold
Align:Center
Text: The map script has encountered a fatal error
Label@DESCB:
X: 15
Y: 45
Width: PARENT_RIGHT-30
Height: 20
Font: Regular
WordWrap: true
Text: The details of the error have been saved to lua.log in the logs directory.
Label@DESCC:
X: 15
Y: 65
Width: PARENT_RIGHT-30
Height: 20
Font: Regular
Text: Please send this file to the map author so that they can fix this issue.

View File

@@ -116,6 +116,8 @@ Container@LEAVE_MAP_WIDGET:
Font: Bold Font: Bold
Text: Statistics Text: Statistics
Container@OBJECTIVES_PANEL: Container@OBJECTIVES_PANEL:
Width: PARENT_RIGHT
Height: 365
Container@DIALOG_CHAT_PANEL: Container@DIALOG_CHAT_PANEL:
X: 15 X: 15
Y: 15 Y: 15

View File

@@ -93,6 +93,7 @@ ChromeLayout:
./mods/cnc/chrome/ingame-debug.yaml ./mods/cnc/chrome/ingame-debug.yaml
./mods/cnc/chrome/ingame-info.yaml ./mods/cnc/chrome/ingame-info.yaml
./mods/cnc/chrome/ingame-infobriefing.yaml ./mods/cnc/chrome/ingame-infobriefing.yaml
./mods/cnc/chrome/ingame-infoscripterror.yaml
./mods/cnc/chrome/ingame-infoobjectives.yaml ./mods/cnc/chrome/ingame-infoobjectives.yaml
./mods/cnc/chrome/ingame-infostats.yaml ./mods/cnc/chrome/ingame-infostats.yaml
./mods/cnc/chrome/ingame-leavemap.yaml ./mods/cnc/chrome/ingame-leavemap.yaml

View File

@@ -64,6 +64,7 @@ ChromeLayout:
./mods/ra/chrome/ingame-fmvplayer.yaml ./mods/ra/chrome/ingame-fmvplayer.yaml
./mods/d2k/chrome/ingame-menu.yaml ./mods/d2k/chrome/ingame-menu.yaml
./mods/ra/chrome/ingame-info.yaml ./mods/ra/chrome/ingame-info.yaml
./mods/ra/chrome/ingame-infoscripterror.yaml
./mods/ra/chrome/ingame-infobriefing.yaml ./mods/ra/chrome/ingame-infobriefing.yaml
./mods/ra/chrome/ingame-infoobjectives.yaml ./mods/ra/chrome/ingame-infoobjectives.yaml
./mods/ra/chrome/ingame-infostats.yaml ./mods/ra/chrome/ingame-infostats.yaml

View File

@@ -0,0 +1,27 @@
Container@SCRIPT_ERROR_PANEL:
Height: PARENT_BOTTOM
Width: PARENT_RIGHT
Children:
Label@DESCA:
X: 15
Y: 15
Width: PARENT_RIGHT-30
Height: 20
Font: Bold
Align:Center
Text: The map script has encountered a fatal error
Label@DESCB:
X: 15
Y: 45
Width: PARENT_RIGHT-30
Height: 20
Font: Regular
WordWrap: true
Text: The details of the error have been saved to lua.log in the logs directory.
Label@DESCC:
X: 15
Y: 65
Width: PARENT_RIGHT-30
Height: 20
Font: Regular
Text: Please send this file to the map author so that they can fix this issue.

View File

@@ -126,6 +126,7 @@ Container@LEAVE_MAP_WIDGET:
Text: Leave Text: Leave
Container@OBJECTIVES_PANEL: Container@OBJECTIVES_PANEL:
Y: 65 Y: 65
Width: PARENT_RIGHT
Container@DIALOG_CHAT_PANEL: Container@DIALOG_CHAT_PANEL:
X: 20 X: 20
Y: 65 Y: 65

View File

@@ -76,6 +76,7 @@ ChromeLayout:
./mods/ra/chrome/ingame-diplomacy.yaml ./mods/ra/chrome/ingame-diplomacy.yaml
./mods/ra/chrome/ingame-fmvplayer.yaml ./mods/ra/chrome/ingame-fmvplayer.yaml
./mods/ra/chrome/ingame-info.yaml ./mods/ra/chrome/ingame-info.yaml
./mods/ra/chrome/ingame-infoscripterror.yaml
./mods/ra/chrome/ingame-infobriefing.yaml ./mods/ra/chrome/ingame-infobriefing.yaml
./mods/ra/chrome/ingame-infoobjectives.yaml ./mods/ra/chrome/ingame-infoobjectives.yaml
./mods/ra/chrome/ingame-infostats.yaml ./mods/ra/chrome/ingame-infostats.yaml

View File

@@ -106,6 +106,7 @@ ChromeLayout:
./mods/ra/chrome/ingame-fmvplayer.yaml ./mods/ra/chrome/ingame-fmvplayer.yaml
./mods/ra/chrome/ingame-menu.yaml ./mods/ra/chrome/ingame-menu.yaml
./mods/ra/chrome/ingame-info.yaml ./mods/ra/chrome/ingame-info.yaml
./mods/ra/chrome/ingame-infoscripterror.yaml
./mods/ra/chrome/ingame-infobriefing.yaml ./mods/ra/chrome/ingame-infobriefing.yaml
./mods/ra/chrome/ingame-infoobjectives.yaml ./mods/ra/chrome/ingame-infoobjectives.yaml
./mods/ra/chrome/ingame-infostats.yaml ./mods/ra/chrome/ingame-infostats.yaml