From aa9d72111f823d452ae3905a6b0e2b9c87ae061a Mon Sep 17 00:00:00 2001 From: riebel Date: Mon, 21 Sep 2026 21:11:50 +0200 Subject: [PATCH 1/2] docs: update pixi-tiledmap entry in framework support list (#4619) --- docs/reference/support-for-tmx-maps.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/reference/support-for-tmx-maps.rst b/docs/reference/support-for-tmx-maps.rst index 87dd482de6..96f125b7bf 100644 --- a/docs/reference/support-for-tmx-maps.rst +++ b/docs/reference/support-for-tmx-maps.rst @@ -327,10 +327,11 @@ HTML5 (multiple engines) multi-canvas based game rendering library - `melonJS `__ A lightweight HTML5 game engine - `Panda 2 `__, a HTML5 Game Development Platform for Mac, Windows and Linux. Has `a plugin for rendering Tiled `__ maps, both orthogonal and isometric. -- `pixi-tiledmap `__ A - loader and renderer for Tiled Maps in `Pixi.JS `__ - v8+ written in TypeScript. JSON/XML support with no external deps, - full layer-type support, typed API and ESM/CJS dual output. +- `pixi-tiledmap `__ A Tiled map + runtime for `PixiJS `__ v8+, written in TypeScript. + TMX/TMJ parsing with no external dependencies, all layer types and + orientations, batched tile rendering, runtime tile editing, procedural + map generation, and export back to Tiled JSON. - `Phaser `__ A fast, free and fun open source framework supporting both JavaScript and TypeScript (`Tiled tutorial `__) From ed0b433141a11d6c4a728de95ab50a3c90b39154 Mon Sep 17 00:00:00 2001 From: Shubham Padkonde Date: Tue, 22 Sep 2026 02:00:03 +0530 Subject: [PATCH 2/2] Fix world scripting APIs without the editor (#4612) Scripts can load worlds through WorldManager, but this didn't work for evaluation from the command-line because it unnecessarily required a DocumentManager. The same GUI guard prevented worldsChanged from being forwarded, and loadWorld discarded loading errors. Read worlds and connect their change notification independently of the GUI, propagate load failures as script errors, and document the error behavior. Add a scripting test target that runs the public JavaScript API without creating a DocumentManager, covering load/unload visibility, change notifications, and a missing world file. --- NEWS.md | 1 + docs/scripting-doc/index.d.ts | 1 + src/tiled/scriptmodule.cpp | 12 ++- tests/scriptmodule/scriptmodule.qbs | 10 +++ tests/scriptmodule/test_scriptmodule.cpp | 99 ++++++++++++++++++++++++ tests/tests.qbs | 1 + 6 files changed, 117 insertions(+), 7 deletions(-) create mode 100644 tests/scriptmodule/scriptmodule.qbs create mode 100644 tests/scriptmodule/test_scriptmodule.cpp diff --git a/NEWS.md b/NEWS.md index 1b12dafb56..9027fee041 100644 --- a/NEWS.md +++ b/NEWS.md @@ -10,6 +10,7 @@ * Scripting: Added 'tiled.cell' function, 'cell.flags' property and 'TileLayerEdit.setCell' function (#4538) * Scripting: Added MapObject.resolvedClassName() (by MatusGuy, #4529) * Scripting: Added Tileset.selectedTilesChanged signal (#4615) +* Scripting: Fixed world scripting APIs to work without the editor (by Shubham Padkonde, #4584) * Fixed crash when the selection becomes empty while starting a move (#4536) * Fixed Properties view update on 'Reset Template Instance' and 'Replace With Template' actions * Fixed restoring of the layout for maximized windows on startup (#4580) diff --git a/docs/scripting-doc/index.d.ts b/docs/scripting-doc/index.d.ts index 7e8989010a..18b593082e 100644 --- a/docs/scripting-doc/index.d.ts +++ b/docs/scripting-doc/index.d.ts @@ -5472,6 +5472,7 @@ declare namespace tiled { /** * Load a world contained in a .world file in the path fileName. + * Throws an error if the world could not be loaded. * @since 1.11 */ export function loadWorld(fileName: string): void; diff --git a/src/tiled/scriptmodule.cpp b/src/tiled/scriptmodule.cpp index eab1d081a9..dbebe25e56 100644 --- a/src/tiled/scriptmodule.cpp +++ b/src/tiled/scriptmodule.cpp @@ -70,9 +70,9 @@ ScriptModule::ScriptModule(QObject *parent) connect(documentManager, &DocumentManager::documentSaved, this, &ScriptModule::documentSaved); connect(documentManager, &DocumentManager::documentAboutToClose, this, &ScriptModule::documentAboutToClose); connect(documentManager, &DocumentManager::currentDocumentChanged, this, &ScriptModule::currentDocumentChanged); - - connect(&WorldManager::instance(), &WorldManager::worldsChanged, this, &ScriptModule::worldsChanged); } + + connect(&WorldManager::instance(), &WorldManager::worldsChanged, this, &ScriptModule::worldsChanged); } ScriptModule::~ScriptModule() @@ -756,10 +756,6 @@ QList ScriptModule::worlds() const { QList worlds; - auto documentManager = DocumentManager::maybeInstance(); - if (!documentManager) - return worlds; - for (auto &worldDocument : WorldManager::instance().worlds()) worlds.append(worldDocument->editable()); @@ -768,7 +764,9 @@ QList ScriptModule::worlds() const void ScriptModule::loadWorld(const QString &fileName) const { - WorldManager::instance().loadWorld(fileName); + QString error; + if (!WorldManager::instance().loadWorld(fileName, &error)) + ScriptManager::instance().throwError(error); } void ScriptModule::unloadWorld(const QString &fileName) const diff --git a/tests/scriptmodule/scriptmodule.qbs b/tests/scriptmodule/scriptmodule.qbs new file mode 100644 index 0000000000..78f3abcf69 --- /dev/null +++ b/tests/scriptmodule/scriptmodule.qbs @@ -0,0 +1,10 @@ +TiledTest { + name: "test_scriptmodule" + + Depends { name: "libtilededitor" } + Depends { name: "Qt.qml" } + + files: [ + "test_scriptmodule.cpp", + ] +} diff --git a/tests/scriptmodule/test_scriptmodule.cpp b/tests/scriptmodule/test_scriptmodule.cpp new file mode 100644 index 0000000000..cfed9fc812 --- /dev/null +++ b/tests/scriptmodule/test_scriptmodule.cpp @@ -0,0 +1,99 @@ +#include "projectmanager.h" +#include "scriptmanager.h" +#include "worldmanager.h" + +#include +#include +#include + +#include + +using namespace Tiled; + +class test_ScriptModule : public QObject +{ + Q_OBJECT + +private slots: + void initTestCase(); + void cleanupTestCase(); + void cleanup(); + void worldsWithoutDocumentManager(); + void worldsChangedWithoutDocumentManager(); + void loadMissingWorld(); + +private: + std::unique_ptr mProjectManager; +}; + +void test_ScriptModule::initTestCase() +{ + QStandardPaths::setTestModeEnabled(true); + mProjectManager = std::make_unique(); + ScriptManager::instance().ensureInitialized(); +} + +void test_ScriptModule::cleanupTestCase() +{ + ScriptManager::deleteInstance(); + mProjectManager.reset(); +} + +void test_ScriptModule::cleanup() +{ + WorldManager::instance().unloadAllWorlds(); +} + +void test_ScriptModule::worldsWithoutDocumentManager() +{ + QTemporaryFile file; + QVERIFY(file.open()); + file.write("{\"type\":\"world\",\"maps\":[]}"); + file.flush(); + + auto &manager = ScriptManager::instance(); + manager.engine()->globalObject().setProperty(QStringLiteral("testWorldPath"), file.fileName()); + const auto result = manager.evaluate(QStringLiteral("tiled.loadWorld(testWorldPath); tiled.worlds.length")); + QVERIFY2(!result.isError(), qPrintable(result.toString())); + QCOMPARE(WorldManager::instance().worlds().size(), 1); + QCOMPARE(result.toInt(), 1); + + const auto afterUnload = manager.evaluate(QStringLiteral("tiled.unloadWorld(testWorldPath); tiled.worlds.length")); + QCOMPARE(afterUnload.toInt(), 0); +} + +void test_ScriptModule::worldsChangedWithoutDocumentManager() +{ + QTemporaryFile file; + QVERIFY(file.open()); + file.write("{\"type\":\"world\",\"maps\":[]}"); + file.flush(); + + auto &manager = ScriptManager::instance(); + manager.engine()->globalObject().setProperty(QStringLiteral("testWorldPath"), file.fileName()); + const auto result = manager.evaluate(QStringLiteral( + "var changes = 0;" + "tiled.worldsChanged.connect(function() { ++changes; });" + "tiled.loadWorld(testWorldPath); changes")); + QVERIFY2(!result.isError(), qPrintable(result.toString())); + QCOMPARE(result.toInt(), 1); + + const auto afterUnload = manager.evaluate(QStringLiteral("tiled.unloadAllWorlds(); changes")); + QCOMPARE(afterUnload.toInt(), 2); +} + +void test_ScriptModule::loadMissingWorld() +{ + QTemporaryDir directory; + QVERIFY(directory.isValid()); + auto &manager = ScriptManager::instance(); + manager.engine()->globalObject().setProperty(QStringLiteral("testWorldPath"), + directory.filePath(QStringLiteral("missing.world"))); + const auto result = manager.evaluate(QStringLiteral("tiled.loadWorld(testWorldPath)")); + QVERIFY(result.isError()); + QVERIFY(WorldManager::instance().worlds().isEmpty()); +} + +QTEST_MAIN(test_ScriptModule) + +#include "test_scriptmodule.moc" diff --git a/tests/tests.qbs b/tests/tests.qbs index 71d2cdb6d5..63fd01784b 100644 --- a/tests/tests.qbs +++ b/tests/tests.qbs @@ -5,6 +5,7 @@ Project { "automapping", "mapreader", "properties", + "scriptmodule", "staggeredrenderer", ] }