1
0

LuaJson: Report serialization errors instead of crashing.

This commit is contained in:
Mattes D 2016-10-27 23:47:36 +02:00
parent 471ae7e47b
commit 9eff6f390d
3 changed files with 212 additions and 128 deletions

View File

@ -9476,7 +9476,7 @@ end
Type = "string",
},
},
Notes = "Serializes the input table into a Json string. The options table, if present, is used to adjust the formatting of the serialized string, see below for details.",
Notes = "Serializes the input table into a Json string. The options table, if present, is used to adjust the formatting of the serialized string, see below for details. <br/>Returns nil and error message if the table cannot be serialized (eg. contains both an array part and a dictionary part).",
},
},
AdditionalInfo =

View File

@ -15,7 +15,7 @@ function Initialize(a_Plugin)
cPluginManager.AddHook(cPluginManager.HOOK_TICK, OnTick1);
cPluginManager.AddHook(cPluginManager.HOOK_TICK, OnTick2);
--]]
local PM = cPluginManager;
PM:AddHook(cPluginManager.HOOK_PLAYER_USING_BLOCK, OnPlayerUsingBlock);
PM:AddHook(cPluginManager.HOOK_PLAYER_USING_ITEM, OnPlayerUsingItem);
@ -38,24 +38,24 @@ function Initialize(a_Plugin)
-- Load the InfoReg shared library:
dofile(cPluginManager:GetPluginsPath() .. "/InfoReg.lua")
-- Bind all the commands:
RegisterPluginInfoCommands();
-- Bind all the console commands:
RegisterPluginInfoConsoleCommands();
a_Plugin:AddWebTab("Debuggers", HandleRequest_Debuggers)
a_Plugin:AddWebTab("StressTest", HandleRequest_StressTest)
-- Enable the following line for BlockArea / Generator interface testing:
-- PluginManager:AddHook(Plugin, cPluginManager.HOOK_CHUNK_GENERATED);
-- TestBlockAreas()
-- TestSQLiteBindings()
-- TestExpatBindings()
TestPluginCalls()
TestBlockAreasString()
TestStringBase64()
-- TestUUIDFromName()
@ -63,7 +63,7 @@ function Initialize(a_Plugin)
TestFileExt()
-- TestFileLastMod()
TestPluginInterface()
local LastSelfMod = cFile:GetLastModificationTime(a_Plugin:GetLocalFolder() .. "/Debuggers.lua")
LOG("Debuggers.lua last modified on " .. os.date("%Y-%m-%dT%H:%M:%S", LastSelfMod))
@ -75,7 +75,7 @@ function Initialize(a_Plugin)
:SetMessageType(mtInfo)
)
--]]
-- Test the crash in #1889:
cPluginManager:AddHook(cPluginManager.HOOK_PLAYER_RIGHT_CLICKING_ENTITY,
function (a_CBPlayer, a_CBEntity)
@ -87,7 +87,7 @@ function Initialize(a_Plugin)
)
end
)
return true
end;
@ -105,7 +105,7 @@ function TestPluginInterface()
end
end
)
cPluginManager:ForEachPlugin(
function (a_CBPlugin)
LOG("Plugin in " .. a_CBPlugin:GetFolderName() .. " has an API name of " .. a_CBPlugin:GetName() .. " and status " .. a_CBPlugin:GetStatus())
@ -156,7 +156,7 @@ function TestPluginCalls()
-- Note the signature: function ReturnColorFromChar( Split, char ) ... return cChatColog.Gray ... end
-- The Split parameter should be a table, but it is not used in that function anyway,
-- so we can get away with passing nil to it.
LOG("Debuggers: Calling NoSuchPlugin.FnName()...")
cPluginManager:CallPlugin("NoSuchPlugin", "FnName", "SomeParam")
LOG("Debuggers: Calling Core.NoSuchFunction()...")
@ -177,7 +177,7 @@ end
function TestBlockAreas()
LOG("Testing block areas...");
-- Debug block area merging:
local BA1 = cBlockArea();
local BA2 = cBlockArea();
@ -192,7 +192,7 @@ function TestBlockAreas()
else
BA1:Create(16, 16, 16);
end
-- Debug block area cuboid filling:
BA1:FillRelCuboid(2, 9, 2, 8, 2, 8, cBlockArea.baTypes, E_BLOCK_GOLD_BLOCK);
BA1:RelLine(2, 2, 2, 9, 8, 8, cBlockArea.baTypes or cBlockArea.baMetas, E_BLOCK_SAPLING, E_META_SAPLING_BIRCH);
@ -204,18 +204,18 @@ function TestBlockAreas()
BA1:SaveToSchematicFile("schematics/lt_XY.schematic");
BA1:MirrorXYNoMeta();
BA1:SaveToSchematicFile("schematics/lt_XY2.schematic");
BA1:MirrorXZNoMeta();
BA1:SaveToSchematicFile("schematics/lt_XZ.schematic");
BA1:MirrorXZNoMeta();
BA1:SaveToSchematicFile("schematics/lt_XZ2.schematic");
BA1:MirrorYZNoMeta();
BA1:SaveToSchematicFile("schematics/lt_YZ.schematic");
BA1:MirrorYZNoMeta();
BA1:SaveToSchematicFile("schematics/lt_YZ2.schematic");
end
-- Debug block area rotation:
if (BA1:LoadFromSchematicFile("schematics/rot.schematic")) then
BA1:RotateCWNoMeta();
@ -246,23 +246,23 @@ function TestBlockAreas()
BA1:SaveToSchematicFile("schematics/ltm_XY.schematic");
BA1:MirrorXY();
BA1:SaveToSchematicFile("schematics/ltm_XY2.schematic");
BA1:MirrorXZ();
BA1:SaveToSchematicFile("schematics/ltm_XZ.schematic");
BA1:MirrorXZ();
BA1:SaveToSchematicFile("schematics/ltm_XZ2.schematic");
BA1:MirrorYZ();
BA1:SaveToSchematicFile("schematics/ltm_YZ.schematic");
BA1:MirrorYZ();
BA1:SaveToSchematicFile("schematics/ltm_YZ2.schematic");
end
LOG("Block areas test ended");
end
@ -281,7 +281,7 @@ function TestBlockAreasString()
local f = io.open("schematics/StringTest.schematic", "wb")
f:write(Data)
f:close()
-- Load a second area from that file:
local BA2 = cBlockArea()
if not(BA2:LoadFromSchematicFile("schematics/StringTest.schematic")) then
@ -289,7 +289,7 @@ function TestBlockAreasString()
return
end
BA2:Clear()
-- Load another area from a string in that file:
f = io.open("schematics/StringTest.schematic", "rb")
Data = f:read("*all")
@ -308,11 +308,11 @@ function TestStringBase64()
for i = 0, 255 do
s = s .. string.char(i)
end
-- Roundtrip through Base64:
local Base64 = Base64Encode(s)
local UnBase64 = Base64Decode(Base64)
assert(UnBase64 == s)
end
@ -322,7 +322,7 @@ end
function TestUUIDFromName()
LOG("Testing UUID-from-Name resolution...")
-- Test by querying a few existing names, along with a non-existent one:
local PlayerNames =
{
@ -332,7 +332,7 @@ function TestUUIDFromName()
}
-- WARNING: Blocking operation! DO NOT USE IN TICK THREAD!
local UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(PlayerNames)
-- Log the results:
for _, name in ipairs(PlayerNames) do
local UUID = UUIDs[name]
@ -342,7 +342,7 @@ function TestUUIDFromName()
LOG(" UUID(" .. name .. ") = \"" .. UUID .. "\"")
end
end
-- Test once more with the same players, valid-only. This should go directly from cache, so fast.
LOG("Testing again with the same valid players...")
local ValidPlayerNames =
@ -371,7 +371,7 @@ function TestUUIDFromName()
"notch", -- Valid player name, but not cached (most likely :)
}
UUIDs = cMojangAPI:GetUUIDsFromPlayerNames(PlayerNames3, true)
-- Log the results:
for _, name in ipairs(PlayerNames3) do
local UUID = UUIDs[name]
@ -383,7 +383,7 @@ function TestUUIDFromName()
end
LOG("UUID-from-Name resolution tests finished.")
LOG("Performing a Name-from-UUID test...")
-- local NameToTest = "aloe_vera"
local NameToTest = "xoft"
@ -410,7 +410,7 @@ end
function TestSQLiteBindings()
LOG("Testing SQLite bindings...");
-- Debug SQLite binding
local TestDB, ErrCode, ErrMsg = sqlite3.open("test.sqlite");
if (TestDB ~= nil) then
@ -439,7 +439,7 @@ function TestSQLiteBindings()
-- This happens if for example SQLite cannot open the file (eg. a folder with the same name exists)
LOG("SQLite3 failed to open DB! (" .. ErrCode .. ", " .. ErrMsg ..")");
end
LOG("SQLite bindings test ended");
end
@ -449,7 +449,7 @@ end
function TestExpatBindings()
LOG("Testing Expat bindings...");
-- Debug LuaExpat bindings:
local count = 0
callbacks = {
@ -472,7 +472,7 @@ function TestExpatBindings()
p:parse("\n");
p:parse(); -- finishes the document
p:close(); -- closes the parser
LOG("Expat bindings test ended");
end
@ -490,7 +490,7 @@ function OnUsingBlazeRod(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, Cur
local TempItem = cItem(Type, 1, Meta);
Player:SendMessage(cChatColor.LightGray .. "Block {" .. BlockX .. ", " .. BlockY .. ", " .. BlockZ .. "}: " .. ItemToFullString(TempItem) .. " (" .. Type .. ":" .. Meta .. ")");
end
local X, Y, Z = AddFaceDirection(BlockX, BlockY, BlockZ, BlockFace);
Valid, Type, Meta = Player:GetWorld():GetBlockTypeMeta(X, Y, Z);
if (Type == E_BLOCK_AIR) then
@ -517,27 +517,27 @@ function OnUsingDiamond(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, Curs
LOG("Size before cropping: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
Area:DumpToRawFile("crop0.dat");
Area:Crop(2, 3, 0, 0, 0, 0);
LOG("Size after cropping 1: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
Area:DumpToRawFile("crop1.dat");
Area:Crop(2, 3, 0, 0, 0, 0);
LOG("Size after cropping 2: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
Area:DumpToRawFile("crop2.dat");
Area:Expand(2, 3, 0, 0, 0, 0);
LOG("Size after expanding 1: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
Area:DumpToRawFile("expand1.dat");
Area:Expand(3, 2, 1, 1, 0, 0);
LOG("Size after expanding 2: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
Area:DumpToRawFile("expand2.dat");
Area:Crop(0, 0, 0, 0, 3, 2);
LOG("Size after cropping 3: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
Area:DumpToRawFile("crop3.dat");
Area:Crop(0, 0, 3, 2, 0, 0);
LOG("Size after cropping 4: " .. Area:GetSizeX() .. " x " .. Area:GetSizeY() .. " x " .. Area:GetSizeZ());
Area:DumpToRawFile("crop4.dat");
@ -571,7 +571,7 @@ end
function OnUsingEnderPearl(Player, BlockX, BlockY, BlockZ, BlockFace, CursorX, CursorY, CursorZ)
-- Rclk with an ender pearl saves a predefined area around the cursor into a .schematic file. Also tests area copying
local Area = cBlockArea();
if not(Area:Read(Player:GetWorld(),
if not(Area:Read(Player:GetWorld(),
BlockX - 8, BlockX + 8, BlockY - 8, BlockY + 8, BlockZ - 8, BlockZ + 8)
) then
LOG("LUA: Area couldn't be read");
@ -717,14 +717,14 @@ function OnTick()
table.remove(g_DropSpensersToActivate, i);
end
end
-- If GCOnTick > 0, do a garbage-collect and decrease by one
if (GCOnTick > 0) then
collectgarbage();
GCOnTick = GCOnTick - 1;
end
return false;
end
@ -741,8 +741,8 @@ function OnWorldTick(a_World, a_Dt)
a_World:ForEachPlayer(
function(a_Player)
a_Player:SendMessage(
tostring(Tick / 10) ..
" > FS: fl " .. a_Player:GetFoodLevel() ..
tostring(Tick / 10) ..
" > FS: fl " .. a_Player:GetFoodLevel() ..
"; sat " .. a_Player:GetFoodSaturationLevel() ..
"; exh " .. a_Player:GetFoodExhaustionLevel()
);
@ -782,7 +782,7 @@ end
function OnChunkGenerated(a_World, a_ChunkX, a_ChunkZ, a_ChunkDesc)
-- Get the topmost block coord:
local Height = a_ChunkDesc:GetHeight(0, 0);
-- Create a sign there:
a_ChunkDesc:SetBlockTypeMeta(0, Height + 1, 0, E_BLOCK_SIGN_POST, 0);
local BlockEntity = a_ChunkDesc:GetBlockEntity(0, Height + 1, 0);
@ -827,7 +827,7 @@ end
function HandleListEntitiesCmd(Split, Player)
local NumEntities = 0;
local ListEntity = function(Entity)
if (Entity:IsDestroyed()) then
-- The entity has already been destroyed, don't list it
@ -842,7 +842,7 @@ function HandleListEntitiesCmd(Split, Player)
end
NumEntities = NumEntities + 1;
end
Player:SendMessage("Listing all entities...");
Player:GetWorld():ForEachEntity(ListEntity);
Player:SendMessage("List finished, " .. NumEntities .. " entities listed");
@ -855,7 +855,7 @@ end
function HandleKillEntitiesCmd(Split, Player)
local NumEntities = 0;
local KillEntity = function(Entity)
-- kill everything except for players:
if (Entity:GetEntityType() ~= cEntity.etPlayer) then
@ -863,7 +863,7 @@ function HandleKillEntitiesCmd(Split, Player)
NumEntities = NumEntities + 1;
end;
end
Player:SendMessage("Killing all entities...");
Player:GetWorld():ForEachEntity(KillEntity);
Player:SendMessage("Killed " .. NumEntities .. " entities.");
@ -900,7 +900,7 @@ function HandleTestWndCmd(a_Split, a_Player)
a_Player:SendMessage("Usage: /testwnd [WindowType WindowSizeX WindowSizeY]");
return true;
end
-- Test out the OnClosing callback's ability to refuse to close the window
local attempt = 1;
local OnClosing = function(Window, Player, CanRefuse)
@ -908,12 +908,12 @@ function HandleTestWndCmd(a_Split, a_Player)
attempt = attempt + 1;
return CanRefuse and (attempt <= 3); -- refuse twice, then allow, unless CanRefuse is set to true
end
-- Log the slot changes
local OnSlotChanged = function(Window, SlotNum)
LOG("Window \"" .. Window:GetWindowTitle() .. "\" slot " .. SlotNum .. " changed.");
end
local Window = cLuaWindow(WindowType, WindowSizeX, WindowSizeY, "TestWnd");
local Item2 = cItem(E_ITEM_DIAMOND_SWORD, 1, 0, "1=1");
local Item3 = cItem(E_ITEM_DIAMOND_SHOVEL);
@ -929,13 +929,13 @@ function HandleTestWndCmd(a_Split, a_Player)
Window:SetSlot(a_Player, 4, Item5);
Window:SetOnClosing(OnClosing);
Window:SetOnSlotChanged(OnSlotChanged);
a_Player:OpenWindow(Window);
-- To make sure that the object has the correct life-management in Lua,
-- let's garbage-collect in the following few ticks
GCOnTick = 10;
return true;
end
@ -1024,12 +1024,12 @@ function HandleFoodLevelCmd(a_Split, a_Player)
a_Player:SendMessage("Missing an argument: the food level to set");
return true;
end
a_Player:SetFoodLevel(tonumber(a_Split[2]));
a_Player:SetFoodSaturationLevel(5);
a_Player:SetFoodExhaustionLevel(0);
a_Player:SendMessage(
"Food level set to " .. a_Player:GetFoodLevel() ..
"Food level set to " .. a_Player:GetFoodLevel() ..
", saturation reset to " .. a_Player:GetFoodSaturationLevel() ..
" and exhaustion reset to " .. a_Player:GetFoodExhaustionLevel()
);
@ -1053,17 +1053,17 @@ function HandleSpideyCmd(a_Split, a_Player)
World:SetBlock(a_BlockX, a_BlockY, a_BlockZ, E_BLOCK_COBWEB, 0);
end
};
local EyePos = a_Player:GetEyePosition();
local LookVector = a_Player:GetLookVector();
LookVector:Normalize();
-- Start cca 2 blocks away from the eyes
local Start = EyePos + LookVector + LookVector;
local End = EyePos + LookVector * 50;
cLineBlockTracer.Trace(World, Callbacks, Start.x, Start.y, Start.z, End.x, End.y, End.z);
return true;
end
@ -1099,7 +1099,7 @@ function HandleArrowCmd(a_Split, a_Player)
local Speed = a_Player:GetLookVector();
Speed:Normalize();
Pos = Pos + Speed;
World:CreateProjectile(Pos.x, Pos.y, Pos.z, cProjectileEntity.pkArrow, a_Player, Speed * 10);
return true;
end
@ -1114,7 +1114,7 @@ function HandleFireballCmd(a_Split, a_Player)
local Speed = a_Player:GetLookVector();
Speed:Normalize();
Pos = Pos + Speed * 2;
World:CreateProjectile(Pos.x, Pos.y, Pos.z, cProjectileEntity.pkGhastFireball, a_Player, Speed * 10);
return true;
end
@ -1134,7 +1134,7 @@ end
function HandleRemoveXp(a_Split, a_Player)
a_Player:SetCurrentExperience(0);
return true;
end
@ -1217,7 +1217,7 @@ end
function HandleSched(a_Split, a_Player)
local World = a_Player:GetWorld()
-- Schedule a broadcast of a countdown message:
for i = 1, 10 do
World:ScheduleTask(i * 20,
@ -1226,7 +1226,7 @@ function HandleSched(a_Split, a_Player)
end
)
end
-- Schedule a broadcast of the final message and a note to the originating player
-- Note that we CANNOT use the a_Player in the callback - what if the player disconnected?
-- Therefore we store the player's EntityID
@ -1246,7 +1246,7 @@ function HandleSched(a_Split, a_Player)
)
end
)
return true
end
@ -1362,7 +1362,7 @@ end
function OnPluginMessage(a_Client, a_Channel, a_Message)
LOGINFO("Received a plugin message from client " .. a_Client:GetUsername() .. ": channel '" .. a_Channel .. "', message '" .. a_Message .. "'");
if (a_Channel == "REGISTER") then
if (a_Message:find("WECUI")) then
-- The client has WorldEditCUI mod installed, test the comm by sending a few WECUI messages:
@ -1389,29 +1389,29 @@ function HandleChunkStay(a_Split, a_Player)
-- As an example of using ChunkStay, this call will load 3x3 chunks around the specified chunk coords,
-- then build an obsidian pillar in the middle of each one.
-- Once complete, the player will be teleported to the middle pillar
if (#a_Split ~= 3) then
a_Player:SendMessageInfo("Usage: /cs <ChunkX> <ChunkZ>")
return true
end
local ChunkX = tonumber(a_Split[2])
local ChunkZ = tonumber(a_Split[3])
if ((ChunkX == nil) or (ChunkZ == nil)) then
a_Player:SendMessageFailure("Invalid chunk coords.")
return true
end
local World = a_Player:GetWorld()
local PlayerID = a_Player:GetUniqueID()
a_Player:SendMessageInfo("Loading chunks, stand by...");
-- Set the wanted chunks:
local Chunks = {}
for z = -1, 1 do for x = -1, 1 do
table.insert(Chunks, {ChunkX + x, ChunkZ + z})
end end
-- The function that is called when all chunks are available
-- Will perform the actual action with all those chunks
-- Note that the player needs to be referenced using their EntityID - in case they disconnect before the chunks load
@ -1425,7 +1425,7 @@ function HandleChunkStay(a_Split, a_Player)
World:SetBlock(BlockX, y, BlockZ, E_BLOCK_OBSIDIAN, 0)
end
end end
-- Teleport the player there for visual inspection:
World:DoWithEntityByID(PlayerID,
function (a_CallbackPlayer)
@ -1434,7 +1434,7 @@ function HandleChunkStay(a_Split, a_Player)
end
)
end
-- This function will be called for each chunk that is made available
-- Note that the player needs to be referenced using their EntityID - in case they disconnect before the chunks load
local OnChunkAvailable = function(a_ChunkX, a_ChunkZ)
@ -1445,7 +1445,7 @@ function HandleChunkStay(a_Split, a_Player)
end
)
end
-- Process the ChunkStay:
World:ChunkStay(Chunks, OnChunkAvailable, OnAllChunksAvailable)
return true
@ -1463,13 +1463,13 @@ function HandleCompo(a_Split, a_Player)
msg:AddTextPart(" rules! ")
msg:AddRunCommandPart("Set morning", "/time set 0")
a_Player:SendMessage(msg)
-- Broadcast another one to the world:
local msg2 = cCompositeChat()
msg2:AddSuggestCommandPart(a_Player:GetName(), "/tell " .. a_Player:GetName() .. " ")
msg2:AddTextPart(" knows how to use cCompositeChat!");
a_Player:GetWorld():BroadcastChat(msg2)
return true
end
@ -1500,7 +1500,7 @@ function HandleSetBiome(a_Split, a_Player)
return true
end
end
local BlockX = math.floor(a_Player:GetPosX())
local BlockZ = math.floor(a_Player:GetPosZ())
a_Player:GetWorld():SetAreaBiome(BlockX - Size, BlockX + Size, BlockZ - Size, BlockZ + Size, Biome)
@ -1526,7 +1526,7 @@ function HandleWESel(a_Split, a_Player)
a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, the selection is not a cuboid"))
return true
end
-- Get the selection:
local SelCuboid = cCuboid()
local IsSuccess = cPluginManager:CallPlugin("WorldEdit", "GetPlayerCuboidSelection", a_Player, SelCuboid)
@ -1534,11 +1534,11 @@ function HandleWESel(a_Split, a_Player)
a_Player:SendMessage(cCompositeChat():SetMessageType(mtFailure):AddTextPart("Cannot adjust selection, WorldEdit reported failure while getting current selection"))
return true
end
-- Adjust the selection:
local NumBlocks = tonumber(a_Split[2] or "1") or 1
SelCuboid:Expand(NumBlocks, NumBlocks, 0, 0, NumBlocks, NumBlocks)
-- Set the selection:
IsSuccess = cPluginManager:CallPlugin("WorldEdit", "SetPlayerCuboidSelection", a_Player, SelCuboid)
if not(IsSuccess) then
@ -1571,7 +1571,7 @@ function OnProjectileHitBlock(a_Projectile, a_BlockX, a_BlockY, a_BlockZ, a_Bloc
-- Test projectile hooks by setting the blocks they hit on fire:
local BlockX, BlockY, BlockZ = AddFaceDirection(a_BlockX, a_BlockY, a_BlockZ, a_BlockFace)
local World = a_Projectile:GetWorld()
World:SetBlock(BlockX, BlockY, BlockZ, E_BLOCK_FIRE, 0)
end
@ -1734,7 +1734,7 @@ function HandleConsoleInh(a_Split, a_FullCmd)
if (world == nil) then
return true, "Cannot test inheritance, no default world"
end
-- Install the hook, if needed:
if not(isInhHookInstalled) then
cPluginManager:AddHook(cPluginManager.HOOK_SPAWNING_ENTITY,
@ -1748,7 +1748,7 @@ function HandleConsoleInh(a_Split, a_FullCmd)
)
isInhHookInstalled = true
end
-- Create the projectile:
LOG("Creating a " .. kindStr .. " projectile in world " .. world:GetName() .. "...")
local msg
@ -1762,7 +1762,7 @@ function HandleConsoleInh(a_Split, a_FullCmd)
return
end
LOG("Entity created, ID #" .. entityID)
-- Call a function on the newly created entity:
local hasExecutedCallback = false
world:DoWithEntityByID(
@ -1780,11 +1780,11 @@ function HandleConsoleInh(a_Split, a_FullCmd)
msg = "The callback failed to execute"
return
end
msg = "Inheritance test finished"
end
)
return true, msg
end
@ -1798,7 +1798,7 @@ function HandleConsoleLoadChunk(a_Split)
if (numParams ~= 3) and (numParams ~= 4) then
return true, "Usage: " .. a_Split[1] .. " <ChunkX> <ChunkZ> [<WorldName>]"
end
-- Get the chunk coords:
local chunkX = tonumber(a_Split[2])
if (chunkX == nil) then
@ -1819,7 +1819,7 @@ function HandleConsoleLoadChunk(a_Split)
return true, "There's no world named '" .. a_Split[4] .. "'."
end
end
-- Queue a ChunkStay for the chunk, log a message when the chunk is loaded:
world:ChunkStay({{chunkX, chunkZ}}, nil,
function()
@ -1839,7 +1839,7 @@ function HandleConsolePrepareChunk(a_Split)
if (numParams ~= 3) and (numParams ~= 4) then
return true, "Usage: " .. a_Split[1] .. " <ChunkX> <ChunkZ> [<WorldName>]"
end
-- Get the chunk coords:
local chunkX = tonumber(a_Split[2])
if (chunkX == nil) then
@ -1860,7 +1860,7 @@ function HandleConsolePrepareChunk(a_Split)
return true, "There's no world named '" .. a_Split[4] .. "'."
end
end
-- Queue the chunk for preparing, log a message when prepared:
world:PrepareChunk(chunkX, chunkZ,
function(a_CBChunkX, a_CBChunkZ)
@ -1942,7 +1942,7 @@ function HandleConsoleTestBbox(a_Split, a_EntireCmd)
LOG(" {" .. intersection:GetMinX() .. ", " .. intersection:GetMinY() .. ", " .. intersection:GetMinZ() .. "}")
LOG(" {" .. intersection:GetMaxX() .. ", " .. intersection:GetMaxY() .. ", " .. intersection:GetMaxZ() .. "}")
end
-- Test line intersection:
local lines =
{
@ -1959,7 +1959,7 @@ function HandleConsoleTestBbox(a_Split, a_EntireCmd)
assert(coeff == coeff2)
assert(face == face2)
end
return true
end
@ -1970,7 +1970,7 @@ end
function HandleConsoleTestCall(a_Split, a_EntireCmd)
LOG("Testing inter-plugin calls")
LOG("Note: These will fail if the Core plugin is not enabled")
-- Test calling the HandleConsoleWeather handler:
local pm = cPluginManager
LOG("Calling Core's HandleConsoleWeather")
@ -1985,7 +1985,7 @@ function HandleConsoleTestCall(a_Split, a_EntireCmd)
else
LOG("FAILED")
end
-- Test injecting some code:
LOG("Injecting code into the Core plugin")
isSuccess = pm:CallPlugin("Core", "dofile", pm:GetCurrentPlugin():GetLocalFolder() .. "/Inject.lua")
@ -1994,7 +1994,7 @@ function HandleConsoleTestCall(a_Split, a_EntireCmd)
else
LOG("FAILED")
end
-- Test the full capabilities of the table-passing API, using the injected function:
LOG("Calling injected code")
isSuccess = pm:CallPlugin("Core", "injectedPrintParams",
@ -2039,14 +2039,19 @@ function HandleConsoleTestJson(a_Split, a_EntireCmd)
assert(t2 == nil)
assert(type(msg) == "string")
LOG("Json parsing an invalid string: Error message returned: " .. msg)
LOG("Json parsing test succeeded")
LOG("Testing Json serializing...")
local s1 = cJson:Serialize({a = 1, b = "2", c = {3, "4", 5}, d = true}, {indentation = " "})
LOG("Serialization result: " .. (s1 or "<nil>"))
local s1, msg1 = cJson:Serialize({a = 1, b = "2", c = {3, "4", 5}, d = true}, {indentation = " "})
LOG("Serialization result: " .. (s1 or ("ERROR: " .. (msg1 or "<no message>"))))
local s2, msg2 = cJson:Serialize({valueA = 1, valueB = {3, badValue = "4", 5}, d = true}, {indentation = " "})
assert(not(s2), "Serialization should have failed")
LOG("Serialization correctly failed with message: " .. (msg2 or "<no message>"))
LOG("Json serializing test succeeded")
return true
end
@ -2067,7 +2072,7 @@ function HandleConsoleTestTracer(a_Split, a_EntireCmd)
end
Coords[i] = v
end
-- Get the world in which to test:
local World
if (a_Split[8]) then
@ -2078,7 +2083,7 @@ function HandleConsoleTestTracer(a_Split, a_EntireCmd)
if not(World) then
return true, "No such world"
end
-- Define the callbacks to use for tracing:
local Callbacks =
{
@ -2120,7 +2125,7 @@ function HandleConsoleTestTracer(a_Split, a_EntireCmd)
end
end
end
-- Load the chunks and do the trace once loaded:
World:ChunkStay(Chunks,
nil,
@ -2211,7 +2216,7 @@ function HandleConsoleUuid(a_Split, a_EntireCmd)
if not(playerName) then
return true, "Usage: uuid <PlayerName>"
end
-- Query with cache:
LOG("Player " .. playerName .. ":")
local cachedUuid = cMojangAPI:GetUUIDFromPlayerName(playerName, true)
@ -2220,7 +2225,7 @@ function HandleConsoleUuid(a_Split, a_EntireCmd)
else
LOG(" - in the cache: \"" .. cachedUuid .. "\"")
end
-- Query online:
local onlineUuid = cMojangAPI:GetUUIDFromPlayerName(playerName, false)
if not(onlineUuid) then
@ -2228,7 +2233,7 @@ function HandleConsoleUuid(a_Split, a_EntireCmd)
else
LOG(" - online: \"" .. onlineUuid .. "\"")
end
return true
end
@ -2241,13 +2246,13 @@ function HandleConsoleBBox(a_Split)
local v1 = Vector3d(1, 1, 1)
local v2 = Vector3d(5, 5, 5)
local v3 = Vector3d(11, 11, 11)
if (bbox:IsInside(v1)) then
LOG("v1 is inside bbox")
else
LOG("v1 is not inside bbox")
end
if (bbox:IsInside(v2)) then
LOG("v2 is inside bbox")
else
@ -2265,25 +2270,25 @@ function HandleConsoleBBox(a_Split)
else
LOG("v1*v2 is not inside bbox")
end
if (bbox:IsInside(v2, v1)) then
LOG("v2*v1 is inside bbox")
else
LOG("v2*v1 is not inside bbox")
end
if (bbox:IsInside(v1, v3)) then
LOG("v1*v3 is inside bbox")
else
LOG("v1*v3 is not inside bbox")
end
if (bbox:IsInside(v2, v3)) then
LOG("v2*v3 is inside bbox")
else
LOG("v2*v3 is not inside bbox")
end
return true
end
@ -2298,7 +2303,7 @@ function HandleConsoleDownload(a_Split)
if (not(url) or not(fnam)) then
return true, "Missing parameters. Usage: download <url> <filename>"
end
local callbacks =
{
OnStatusLine = function (self, a_HttpVersion, a_Status, a_Rest)
@ -2306,7 +2311,7 @@ function HandleConsoleDownload(a_Split)
LOG("Cannot download " .. url .. ", HTTP error code " .. a_Status)
return
end
local f, err = io.open(fnam, "wb")
if not(f) then
LOG("Cannot download " .. url .. ", error opening the file " .. fnam .. ": " .. (err or "<no message>"))
@ -2320,7 +2325,7 @@ function HandleConsoleDownload(a_Split)
self.m_File:write(a_Data)
end
end,
OnBodyFinished = function (self)
if (self.m_File) then
self.m_File:close()
@ -2328,7 +2333,7 @@ function HandleConsoleDownload(a_Split)
end
end,
}
local isSuccess, msg = cUrlClient:Get(url, callbacks)
if not(isSuccess) then
LOG("Cannot start an URL download: " .. (msg or "<no message>"))
@ -2353,15 +2358,15 @@ function HandleBlkCmd(a_Split, a_Player)
end
end
};
local EyePos = a_Player:GetEyePosition();
local LookVector = a_Player:GetLookVector();
LookVector:Normalize();
local End = EyePos + LookVector * 50;
cLineBlockTracer.Trace(World, Callbacks, EyePos.x, EyePos.y, EyePos.z, End.x, End.y, End.z);
return true;
end

View File

@ -22,6 +22,53 @@ static Json::Value JsonSerializeValue(cLuaState & a_LuaState);
/** Exception thrown when the input cannot be serialized.
Keeps track of the error message and the problematic value's path in the table.
*/
class CannotSerializeException:
public std::runtime_error
{
typedef std::runtime_error Super;
public:
/** Constructs a new instance of the exception based on the provided values directly. */
explicit CannotSerializeException(const AString & a_ValueName, const char * a_ErrorMsg):
Super(a_ErrorMsg),
m_ValueName(a_ValueName)
{
}
/** Constructs a new instance of the exception based on the provided values directly. */
explicit CannotSerializeException(int a_ValueIndex, const char * a_ErrorMsg):
Super(a_ErrorMsg),
m_ValueName(Printf("%d", a_ValueIndex))
{
}
/** Constructs a new instance of the exception that takes the error message and value name from the parent, and prefix the value name with the specified prefix.
Used for prefixing the value name's path along the call stack that lead to the main exception. */
explicit CannotSerializeException(const CannotSerializeException & a_Parent, const AString & a_ValueNamePrefix):
Super(a_Parent.what()),
m_ValueName(a_ValueNamePrefix + "." + a_Parent.m_ValueName)
{
}
/** Constructs a new instance of the exception that takes the error message and value name from the parent, and prefix the value name with the specified prefix.
Used for prefixing the value name's path along the call stack that lead to the main exception. */
explicit CannotSerializeException(const CannotSerializeException & a_Parent, int a_ValueNamePrefixIndex):
Super(a_Parent.what()),
m_ValueName(Printf("%d", a_ValueNamePrefixIndex) + "." + a_Parent.m_ValueName)
{
}
const AString & GetValueName() const { return m_ValueName; }
protected:
AString m_ValueName;
};
/** Pushes the specified Json array as a table on top of the specified Lua state.
Assumes that a_Value is an array. */
@ -136,14 +183,36 @@ static Json::Value JsonSerializeTable(cLuaState & a_LuaState)
{
int idx;
a_LuaState.GetStackValue(-2, idx);
res[idx - 1] = JsonSerializeValue(a_LuaState);
try
{
res[idx - 1] = JsonSerializeValue(a_LuaState);
}
catch (const CannotSerializeException & exc)
{
throw CannotSerializeException(exc, idx);
}
catch (const std::exception & exc) // Cannot catch Json::Exception, because it's not properly defined
{
throw CannotSerializeException(idx, exc.what());
}
}
else
{
AString name;
if (a_LuaState.GetStackValue(-2, name))
{
res[name] = JsonSerializeValue(a_LuaState);
try
{
res[name] = JsonSerializeValue(a_LuaState);
}
catch (const CannotSerializeException & exc)
{
throw CannotSerializeException(exc, name);
}
catch (const std::exception & exc) // Cannot catch Json::Exception, because it's not properly defined
{
throw CannotSerializeException(name, exc.what());
}
}
}
lua_pop(a_LuaState, 1);
@ -259,7 +328,17 @@ static int tolua_cJson_Serialize(lua_State * a_LuaState)
// Push the table to the top of the Lua stack, and call the serializing function:
lua_pushvalue(L, 2);
Json::Value root = JsonSerializeValue(L);
Json::Value root;
try
{
root = JsonSerializeValue(L);
}
catch (const CannotSerializeException & exc)
{
lua_pushnil(L);
L.Push(Printf("Cannot serialize into Json, value \"%s\" caused an error \"%s\"", exc.GetValueName().c_str(), exc.what()));
return 2;
}
lua_pop(L, 1);
// Create the writer, with all properties (optional param 3) applied to it: