Allow to use a generator as MapGenerator
This way it's clear what's the input and what's the output of the computation. All map generation scripts should reman working. They are adopted in a future commit. `Engine.SetProgress` and `Engine.ExportMap` can be removed in a future commit. Comments by: @marder, @sera, @Stan Differential Revision: https://code.wildfiregames.com/D5220 This was SVN commit r28093.
This commit is contained in:
parent
585e821274
commit
6ce2fc53ea
@ -479,14 +479,14 @@ RandomMap.prototype.exportTerrainTextures = function()
|
||||
};
|
||||
};
|
||||
|
||||
RandomMap.prototype.ExportMap = function()
|
||||
RandomMap.prototype.MakeExportable = function()
|
||||
{
|
||||
if (g_Environment.Water.WaterBody.Height === undefined)
|
||||
g_Environment.Water.WaterBody.Height = SEA_LEVEL - 0.1;
|
||||
|
||||
this.logger.close();
|
||||
|
||||
Engine.ExportMap({
|
||||
return {
|
||||
"entities": this.exportEntityList(),
|
||||
"height": this.exportHeightData(),
|
||||
"seaLevel": SEA_LEVEL,
|
||||
@ -495,5 +495,10 @@ RandomMap.prototype.ExportMap = function()
|
||||
"tileData": this.exportTerrainTextures(),
|
||||
"Camera": g_Camera,
|
||||
"Environment": g_Environment
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
RandomMap.prototype.ExportMap = function()
|
||||
{
|
||||
Engine.ExportMap(this.MakeExportable());
|
||||
};
|
||||
|
@ -0,0 +1,24 @@
|
||||
Engine.GetTemplate = path => (
|
||||
{
|
||||
"Identity": {
|
||||
"GenericName": null,
|
||||
"Icon": null,
|
||||
"History": null
|
||||
}
|
||||
});
|
||||
|
||||
Engine.LoadLibrary("rmgen");
|
||||
|
||||
RandomMapLogger.prototype.printDirectly = function(string)
|
||||
{
|
||||
log(string);
|
||||
// print(string);
|
||||
};
|
||||
|
||||
function* GenerateMap(mapSettings)
|
||||
{
|
||||
TS_ASSERT_DIFFER(mapSettings.Seed, undefined);
|
||||
// Phew... that assertion took a while. ;) Let's update the progress bar.
|
||||
yield 50;
|
||||
return new RandomMap(0, "blackness");
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
function* GenerateMap()
|
||||
{
|
||||
try
|
||||
{
|
||||
yield;
|
||||
}
|
||||
catch (error)
|
||||
{
|
||||
TS_ASSERT(error instanceof Error);
|
||||
TS_ASSERT_EQUALS(error.message, "Failed to convert the yielded value to an integer.");
|
||||
yield 50;
|
||||
return;
|
||||
}
|
||||
|
||||
TS_FAIL("The yield statement didn't throw.");
|
||||
}
|
@ -32,6 +32,12 @@ global.TS_ASSERT_EQUALS = function(x, y)
|
||||
fail("Expected equal, got " + uneval(x) + " !== " + uneval(y));
|
||||
};
|
||||
|
||||
global.TS_ASSERT_DIFFER = function(x, y)
|
||||
{
|
||||
if (x === y)
|
||||
fail("Expected differ, got " + uneval(x) + " === " + uneval(y));
|
||||
};
|
||||
|
||||
global.TS_ASSERT_EQUALS_APPROX = function(x, y, maxDifference)
|
||||
{
|
||||
TS_ASSERT_NUMBER(maxDifference);
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2023 Wildfire Games.
|
||||
/* Copyright (C) 2024 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -47,6 +47,8 @@ extern bool IsQuitRequested();
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr const char* GENERATOR_NAME{"GenerateMap"};
|
||||
|
||||
bool MapGenerationInterruptCallback(JSContext* UNUSED(cx))
|
||||
{
|
||||
// This may not use SDL_IsQuitRequested(), because it runs in a thread separate to SDL, see SDL_PumpEvents
|
||||
@ -423,5 +425,49 @@ Script::StructuredClone RunMapGenerationScript(std::atomic<int>& progress, Scrip
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return mapData;
|
||||
LOGMESSAGE("Run RMS generator");
|
||||
bool hasGenerator;
|
||||
JS::RootedObject globalAsObject{rq.cx, &JS::HandleValue{global}.toObject()};
|
||||
if (!JS_HasProperty(rq.cx, globalAsObject, GENERATOR_NAME, &hasGenerator))
|
||||
{
|
||||
LOGERROR("RunMapGenerationScript: failed to search `%s`.", GENERATOR_NAME);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (mapData != nullptr)
|
||||
{
|
||||
LOGWARNING("The map generation script called `Engine.ExportMap` that's deprecated. The "
|
||||
"generator based interface should be used.");
|
||||
if (hasGenerator)
|
||||
LOGWARNING("The map generation script contains a `%s` but `Engine.ExportMap` was already "
|
||||
"called. `%s` isn't called, preserving the old behavior.", GENERATOR_NAME,
|
||||
GENERATOR_NAME);
|
||||
return mapData;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
JS::RootedValue map{rq.cx, ScriptFunction::RunGenerator(rq, global, GENERATOR_NAME, settingsVal,
|
||||
[&](const JS::HandleValue value)
|
||||
{
|
||||
int tempProgress;
|
||||
if (!Script::FromJSVal(rq, value, tempProgress))
|
||||
throw std::runtime_error{"Failed to convert the yielded value to an "
|
||||
"integer."};
|
||||
progress.store(tempProgress);
|
||||
})};
|
||||
|
||||
JS::RootedValue exportedMap{rq.cx};
|
||||
const bool exportSuccess{ScriptFunction::Call(rq, map, "MakeExportable", &exportedMap)};
|
||||
return Script::WriteStructuredClone(rq, exportSuccess ? exportedMap : map);
|
||||
}
|
||||
catch(const std::exception& e)
|
||||
{
|
||||
LOGERROR("%s", e.what());
|
||||
return nullptr;
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2023 Wildfire Games.
|
||||
/* Copyright (C) 2024 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -51,19 +51,31 @@ public:
|
||||
|
||||
for (const VfsPath& path : paths)
|
||||
{
|
||||
TestLogger logger;
|
||||
ScriptInterface scriptInterface("Engine", "MapGenerator", g_ScriptContext);
|
||||
ScriptTestSetup(scriptInterface);
|
||||
|
||||
// It's never read in the test so it doesn't matter to what value it's initialized. For
|
||||
// good practice it's initialized to 1.
|
||||
std::atomic<int> progress{1};
|
||||
|
||||
const Script::StructuredClone result{RunMapGenerationScript(progress, scriptInterface,
|
||||
path, "{\"Seed\": 0}", JSPROP_ENUMERATE | JSPROP_PERMANENT)};
|
||||
|
||||
// The test scripts don't call `ExportMap` so `RunMapGenerationScript` allways returns
|
||||
// `nullptr`.
|
||||
TS_ASSERT_EQUALS(result, nullptr);
|
||||
if (path == "maps/random/tests/test_Generator.js" ||
|
||||
path == "maps/random/tests/test_RecoverableError.js")
|
||||
{
|
||||
TS_ASSERT_EQUALS(progress.load(), 50);
|
||||
TS_ASSERT_DIFFERS(result, nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The test scripts don't call `ExportMap` so `RunMapGenerationScript` allways
|
||||
// returns `nullptr`.
|
||||
TS_ASSERT_EQUALS(result, nullptr);
|
||||
// Because the test scripts don't call `ExportMap`, `GenerateMap` is searched, which
|
||||
// doesn't exist.
|
||||
TS_ASSERT_STR_CONTAINS(logger.GetOutput(),
|
||||
"Failed to call the generator `GenerateMap`.");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1,4 +1,4 @@
|
||||
/* Copyright (C) 2023 Wildfire Games.
|
||||
/* Copyright (C) 2024 Wildfire Games.
|
||||
* This file is part of 0 A.D.
|
||||
*
|
||||
* 0 A.D. is free software: you can redistribute it and/or modify
|
||||
@ -22,8 +22,10 @@
|
||||
#include "ScriptExceptions.h"
|
||||
#include "ScriptRequest.h"
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
class ScriptInterface;
|
||||
@ -80,6 +82,17 @@ private:
|
||||
template<typename C, typename R, typename ...Types>
|
||||
struct args_info<R(C::*)(Types ...) const> : public args_info<R(C::*)(Types ...)> {};
|
||||
|
||||
struct IteratorResultError : std::runtime_error
|
||||
{
|
||||
IteratorResultError(const std::string& property) :
|
||||
IteratorResultError{property.c_str()}
|
||||
{}
|
||||
IteratorResultError(const char* property) :
|
||||
std::runtime_error{fmt::format("Failed to get `{}` from an `IteratorResult`.", property)}
|
||||
{}
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@ -346,6 +359,59 @@ public:
|
||||
return Call(rq, val, name, IgnoreResult, std::forward<const Args>(args)...);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a JS function @a name, property of object @a val, with the argument @a args. Repeatetly
|
||||
* invokes @a yieldCallback with the yielded value.
|
||||
* @return the final value of the generator.
|
||||
*/
|
||||
template<typename Callback>
|
||||
static JS::Value RunGenerator(const ScriptRequest& rq, JS::HandleValue val, const char* name,
|
||||
JS::HandleValue arg, Callback yieldCallback)
|
||||
{
|
||||
JS::RootedValue generator{rq.cx};
|
||||
if (!ScriptFunction::Call(rq, val, name, &generator, arg))
|
||||
throw std::runtime_error{fmt::format("Failed to call the generator `{}`.", name)};
|
||||
|
||||
const auto continueGenerator = [&](const char* property, auto... args) -> JS::Value
|
||||
{
|
||||
JS::RootedValue iteratorResult{rq.cx};
|
||||
if (!ScriptFunction::Call(rq, generator, property, &iteratorResult, args...))
|
||||
throw std::runtime_error{fmt::format("Failed to call `{}`.", name)};
|
||||
return iteratorResult;
|
||||
};
|
||||
|
||||
JS::PersistentRootedValue error{rq.cx, JS::UndefinedValue()};
|
||||
while (true)
|
||||
{
|
||||
JS::RootedValue iteratorResult{rq.cx, error.isUndefined() ? continueGenerator("next") :
|
||||
continueGenerator("throw", std::exchange(error, JS::UndefinedValue()))};
|
||||
|
||||
try
|
||||
{
|
||||
JS::RootedObject iteratorResultObject{rq.cx, &iteratorResult.toObject()};
|
||||
|
||||
bool done;
|
||||
if (!Script::FromJSProperty(rq, iteratorResult, "done", done, true))
|
||||
throw IteratorResultError{"done"};
|
||||
|
||||
JS::RootedValue value{rq.cx};
|
||||
if (!JS_GetProperty(rq.cx, iteratorResultObject, "value", &value))
|
||||
throw IteratorResultError{"value"};
|
||||
|
||||
if (done)
|
||||
return value;
|
||||
|
||||
yieldCallback(value);
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
JS::RootedValue global{rq.cx, rq.globalValue()};
|
||||
if (!ScriptFunction::Call(rq, global, "Error", &error, e.what()))
|
||||
throw std::runtime_error{"Failed to construct `Error`."};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a function spec from a C++ function.
|
||||
*/
|
||||
|
Loading…
Reference in New Issue
Block a user