mirror of
https://github.com/Heretek-AI/GDevelop-Heretek.git
synced 2026-10-11 21:09:52 +00:00
chore(sync): sync upstream 4ian/GDevelop changes
Merge 4ian/GDevelop master (7a7736a3a4) into the Heretek fork.
- Resolve conflicts in README.md, newIDE/app/package.json and
AiRequestChat (ChatMessages.js, index.js), re-applying BYOK
behavior on top of upstream's AiUsageIndicator refactor.
- Keep Heretek Vite/prettier TS scripts alongside upstream's
check-build-output.js build checks.
- Fix BYOK event-generation path after upstream dropped
existingEventsAsText, now derived from existingEventsJson.
- Lock .github/workflows to master to prevent upstream workflow
tampering.
This commit is contained in:
+381
-4
@@ -9,11 +9,50 @@
|
||||
# to avoid sharing secrets.
|
||||
|
||||
version: 2.1
|
||||
parameters:
|
||||
# Run *every* visual monkey test, even on a branch. Set it when triggering a
|
||||
# pipeline (CircleCI UI or API) to check everything without waiting for the
|
||||
# change to land on `master`.
|
||||
run-all-visual-tests:
|
||||
type: boolean
|
||||
default: false
|
||||
# Number of parallel containers the visual monkey tests are split across.
|
||||
all-visual-tests-parallelism:
|
||||
type: integer
|
||||
default: 4
|
||||
# Same, for a branch (which only runs the tests related to its changes).
|
||||
changed-visual-tests-parallelism:
|
||||
type: integer
|
||||
default: 2
|
||||
orbs:
|
||||
aws-cli: circleci/[email protected]
|
||||
macos: circleci/[email protected] # For Rosetta (see below)
|
||||
node: circleci/[email protected] # For a recent npm version (see below)
|
||||
win: circleci/[email protected]
|
||||
commands:
|
||||
# Used by the visual monkey tests jobs on a "changed" scope: stop the job
|
||||
# (before anything expensive) when no test of the suite is related to what
|
||||
# the branch changes. A change to the tests themselves relates every test.
|
||||
halt-unless-a-visual-test-is-related-to-the-changes:
|
||||
parameters:
|
||||
scope:
|
||||
type: enum
|
||||
enum: ['all', 'changed']
|
||||
suite:
|
||||
type: enum
|
||||
enum: ['storybook', 'editor']
|
||||
steps:
|
||||
- run:
|
||||
name: Stop here if no test is related to the changes
|
||||
command: |
|
||||
if [ "<< parameters.scope >>" != "changed" ]; then exit 0; fi
|
||||
git fetch --depth=50 origin master
|
||||
cd newIDE/visual-tests
|
||||
PUPPETEER_SKIP_DOWNLOAD=1 npm install --no-audit --no-fund --silent
|
||||
if [ -z "$(node run.js --suite=<< parameters.suite >> --list-names --only-changed --base-ref=origin/master)" ]; then
|
||||
echo "No visual monkey test of the << parameters.suite >> suite is related to these changes."
|
||||
circleci-agent step halt
|
||||
fi
|
||||
jobs:
|
||||
# Build the **entire** app for macOS (including the GDevelop.js library).
|
||||
build-macos:
|
||||
@@ -715,6 +754,268 @@ jobs:
|
||||
path: ./cli-html5-export-artifacts
|
||||
destination: cli-html5-export-artifacts
|
||||
|
||||
# Visual monkey tests: manipulate an editor in a real browser and check that
|
||||
# nothing is broken (see newIDE/visual-tests/README.md).
|
||||
#
|
||||
# The "storybook" suite needs a built Storybook, which is built once here and
|
||||
# shared with the containers running the tests.
|
||||
build-storybook-for-visual-tests:
|
||||
parameters:
|
||||
scope:
|
||||
description: >
|
||||
"all" runs every test. "changed" only runs the tests related to what
|
||||
the branch changes - and nothing at all if it changes nothing related.
|
||||
type: enum
|
||||
enum: ['all', 'changed']
|
||||
default: 'all'
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: cimg/node:24.13.0
|
||||
|
||||
working_directory: ~/GDevelop
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- halt-unless-a-visual-test-is-related-to-the-changes:
|
||||
scope: << parameters.scope >>
|
||||
suite: 'storybook'
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- gd-linux-nodejs-dependencies-v2-{{ checksum "newIDE/app/package.json" }}-{{ checksum "newIDE/electron-app/package.json" }}-{{ checksum "GDevelop.js/package.json" }}-{{ checksum "GDJS/package-lock.json" }}
|
||||
# fallback to using the latest cache if no exact match is found
|
||||
- gd-linux-nodejs-dependencies-v2-
|
||||
|
||||
- run:
|
||||
name: Install GDevelop IDE dependencies
|
||||
command: cd newIDE/app && npm install
|
||||
|
||||
- run:
|
||||
name: Build Storybook
|
||||
no_output_timeout: 20m
|
||||
command: export NODE_OPTIONS="--max-old-space-size=7168" && cd newIDE/app && npm run build-storybook
|
||||
|
||||
- persist_to_workspace:
|
||||
root: newIDE/app
|
||||
paths:
|
||||
- build-storybook
|
||||
|
||||
run-visual-monkey-tests-storybook-linux:
|
||||
parameters:
|
||||
scope:
|
||||
type: enum
|
||||
enum: ['all', 'changed']
|
||||
default: 'all'
|
||||
parallelism:
|
||||
description: Number of containers the tests are split across.
|
||||
type: integer
|
||||
default: 1
|
||||
parallelism: << parameters.parallelism >>
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: cimg/node:24.13.0
|
||||
|
||||
working_directory: ~/GDevelop
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- halt-unless-a-visual-test-is-related-to-the-changes:
|
||||
scope: << parameters.scope >>
|
||||
suite: 'storybook'
|
||||
|
||||
- attach_workspace:
|
||||
at: newIDE/app
|
||||
|
||||
# The tests run in the Chrome that `npm install` downloads for Puppeteer
|
||||
# (see newIDE/visual-tests/package.json), which needs these libraries.
|
||||
- run:
|
||||
name: Install the libraries Chrome needs
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libnss3 \
|
||||
libasound2t64 \
|
||||
libgbm1 \
|
||||
libgtk-3-0 \
|
||||
libxss1 \
|
||||
libatk-bridge2.0-0 \
|
||||
libdrm2 \
|
||||
libxkbcommon0
|
||||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- gd-visual-tests-dependencies-v1-{{ checksum "newIDE/visual-tests/package.json" }}
|
||||
|
||||
- run:
|
||||
name: Install the visual tests dependencies (and the Chrome to run them in)
|
||||
# The install of "Stop here if no test is related to the changes"
|
||||
# skipped the Chrome download, and npm does not re-run the postinstall
|
||||
# of an already installed Puppeteer: run it explicitly (it does
|
||||
# nothing when Chrome is already in the restored cache).
|
||||
command: |
|
||||
cd newIDE/visual-tests
|
||||
npm install --no-audit --no-fund
|
||||
node node_modules/puppeteer/install.mjs
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- newIDE/visual-tests/node_modules
|
||||
- ~/.cache/puppeteer
|
||||
key: gd-visual-tests-dependencies-v1-{{ checksum "newIDE/visual-tests/package.json" }}
|
||||
|
||||
- run:
|
||||
name: List the tests to run
|
||||
command: |
|
||||
cd newIDE/visual-tests
|
||||
if [ "<< parameters.scope >>" = "changed" ]; then
|
||||
node run.js --suite=storybook --list-names --only-changed --base-ref=origin/master > /tmp/tests.txt
|
||||
else
|
||||
node run.js --suite=storybook --list-names > /tmp/tests.txt
|
||||
fi
|
||||
echo "Tests to run:"
|
||||
cat /tmp/tests.txt
|
||||
|
||||
- run:
|
||||
name: Split the tests across the parallel containers
|
||||
# Balanced using the durations recorded by `store_test_results` on the
|
||||
# previous runs (falls back to an even split the first time).
|
||||
command: |
|
||||
if [ -s /tmp/tests.txt ]; then
|
||||
circleci tests split --split-by=timings --timings-type=filename \
|
||||
< /tmp/tests.txt > /tmp/tests-for-this-container.txt
|
||||
else
|
||||
: > /tmp/tests-for-this-container.txt
|
||||
fi
|
||||
echo "Tests for this container:"
|
||||
cat /tmp/tests-for-this-container.txt
|
||||
|
||||
- run:
|
||||
name: Run the visual monkey tests on Storybook
|
||||
no_output_timeout: 30m
|
||||
# The failure is reported by the last step of the job, so that the
|
||||
# results and the screenshots of a failing run are uploaded first.
|
||||
command: |
|
||||
set +e
|
||||
cd newIDE/visual-tests
|
||||
node run.js --suite=storybook \
|
||||
--tests-file=/tmp/tests-for-this-container.txt \
|
||||
--artifacts-dir=/tmp/visual-tests-artifacts \
|
||||
--junit-path=/tmp/visual-tests-results/results.xml
|
||||
echo $? > /tmp/visual-tests-exit-code
|
||||
|
||||
- store_test_results:
|
||||
path: /tmp/visual-tests-results
|
||||
- store_artifacts:
|
||||
path: /tmp/visual-tests-artifacts
|
||||
destination: visual-tests
|
||||
|
||||
- run:
|
||||
name: Report whether the visual monkey tests passed
|
||||
command: exit "$(cat /tmp/visual-tests-exit-code)"
|
||||
|
||||
# Visual monkey tests, "editor" suite: smoke tests using a real, packaged
|
||||
# version of the app (like run-cli-html5-export-linux does) opening a real
|
||||
# example game.
|
||||
#
|
||||
# Without a local build (so on a branch), the tests run against the latest
|
||||
# portable build of `master` on S3: they then exercise the tests of the
|
||||
# branch, not its changes to the app itself - those are only tested by the
|
||||
# jobs following build-linux, on the branches building the app.
|
||||
run-visual-monkey-tests-editor-linux:
|
||||
parameters:
|
||||
scope:
|
||||
description: >
|
||||
"all" runs every test. "changed" only runs the tests related to what
|
||||
the branch changes - and nothing at all if it changes nothing related.
|
||||
type: enum
|
||||
enum: ['all', 'changed']
|
||||
default: 'all'
|
||||
use-local-build:
|
||||
description: When true, attach the workspace produced by build-linux and use its portable zip instead of downloading the latest one from S3.
|
||||
type: boolean
|
||||
default: false
|
||||
resource_class: medium
|
||||
docker:
|
||||
- image: cimg/node:24.13.0
|
||||
|
||||
working_directory: ~/GDevelop
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
- halt-unless-a-visual-test-is-related-to-the-changes:
|
||||
scope: << parameters.scope >>
|
||||
suite: 'editor'
|
||||
|
||||
- when:
|
||||
condition: << parameters.use-local-build >>
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: ~/GDevelop/build-linux-workspace
|
||||
|
||||
# Runtime libraries required by Electron (headless still needs nss /
|
||||
# alsa to even start up).
|
||||
- run:
|
||||
name: Install Electron runtime dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y \
|
||||
libnss3 \
|
||||
libasound2t64 \
|
||||
libgbm1 \
|
||||
libgtk-3-0 \
|
||||
libxss1 \
|
||||
libatk-bridge2.0-0 \
|
||||
libdrm2 \
|
||||
libxkbcommon0 \
|
||||
xvfb
|
||||
|
||||
- run:
|
||||
name: Install the visual tests dependencies
|
||||
command: cd newIDE/visual-tests && npm install --no-audit --no-fund
|
||||
|
||||
- run:
|
||||
name: Run the visual monkey tests on the real editor
|
||||
# The editor is a GUI app: it needs a display, even to run headless.
|
||||
# The failure is reported by the last step of the job, so that the
|
||||
# results and the screenshots of a failing run are uploaded first.
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
set +e
|
||||
cd newIDE/visual-tests
|
||||
if [ "<< parameters.use-local-build >>" = "true" ]; then
|
||||
ZIP_PATH=$(ls ~/GDevelop/build-linux-workspace/gdevelop-*.zip | grep -v -- '-arm64' | head -1)
|
||||
if [ -z "$ZIP_PATH" ]; then
|
||||
echo "No portable zip found in the build-linux workspace." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Using the locally built portable zip: $ZIP_PATH"
|
||||
EXTRA_ARGS="--gdevelop-zip=$ZIP_PATH"
|
||||
else
|
||||
echo "No local build available; the latest portable zip of master will be downloaded."
|
||||
EXTRA_ARGS="--gdevelop-branch=master"
|
||||
fi
|
||||
if [ "<< parameters.scope >>" = "changed" ]; then
|
||||
EXTRA_ARGS="$EXTRA_ARGS --only-changed --base-ref=origin/master"
|
||||
fi
|
||||
xvfb-run -a --server-args="-screen 0 1500x1000x24" \
|
||||
node run.js --suite=editor $EXTRA_ARGS \
|
||||
--artifacts-dir=/tmp/visual-tests-artifacts \
|
||||
--junit-path=/tmp/visual-tests-results/results.xml
|
||||
echo $? > /tmp/visual-tests-exit-code
|
||||
|
||||
- store_test_results:
|
||||
path: /tmp/visual-tests-results
|
||||
- store_artifacts:
|
||||
path: /tmp/visual-tests-artifacts
|
||||
destination: visual-tests
|
||||
|
||||
- run:
|
||||
name: Report whether the visual monkey tests passed
|
||||
command: exit "$(cat /tmp/visual-tests-exit-code)"
|
||||
|
||||
workflows:
|
||||
gdevelop_js-wasm-extra-checks:
|
||||
jobs:
|
||||
@@ -766,6 +1067,19 @@ workflows:
|
||||
- master
|
||||
- stable
|
||||
- /experimental-build.*/
|
||||
# Right after build-linux, run the visual monkey tests of the real
|
||||
# editor against the portable zip it just produced.
|
||||
- run-visual-monkey-tests-editor-linux:
|
||||
name: visual-monkey-tests-editor-linux
|
||||
use-local-build: true
|
||||
requires:
|
||||
- build-linux
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- stable
|
||||
- /experimental-build.*/
|
||||
- build-windows:
|
||||
requires:
|
||||
- build-gdevelop_js-wasm-only
|
||||
@@ -786,10 +1100,10 @@ workflows:
|
||||
# - stable
|
||||
# - /experimental-build.*/
|
||||
|
||||
# Daily end-to-end test of the GDevelop CLI HTML5 export. Runs every day
|
||||
# at 16:00 UTC (4pm UTC) against the latest portable build uploaded to S3
|
||||
# for master.
|
||||
daily-cli-html5-export-linux:
|
||||
# Daily end-to-end tests running against the latest portable build uploaded
|
||||
# to S3 for master: the CLI HTML5 export, and the visual monkey tests
|
||||
# manipulating the editor. Runs every day at 16:00 UTC (4pm UTC).
|
||||
daily-end-to-end-tests:
|
||||
triggers:
|
||||
- schedule:
|
||||
cron: "0 16 * * *"
|
||||
@@ -799,3 +1113,66 @@ workflows:
|
||||
- master
|
||||
jobs:
|
||||
- run-cli-html5-export-linux
|
||||
- run-visual-monkey-tests-editor-linux
|
||||
|
||||
# On `master`/`stable` (or when explicitly asked for): manipulate the editors
|
||||
# in a real browser and check that nothing is broken, with every test.
|
||||
# (`master`, `stable` and the experimental builds also run the editor suite
|
||||
# against the build build-linux just produced, in the main workflow.)
|
||||
all-visual-tests:
|
||||
when:
|
||||
or:
|
||||
- equal: [master, << pipeline.git.branch >>]
|
||||
- equal: [stable, << pipeline.git.branch >>]
|
||||
- << pipeline.parameters.run-all-visual-tests >>
|
||||
jobs:
|
||||
- build-storybook-for-visual-tests:
|
||||
name: visual-tests-storybook-build-all
|
||||
scope: 'all'
|
||||
- run-visual-monkey-tests-storybook-linux:
|
||||
name: visual-tests-storybook-all
|
||||
scope: 'all'
|
||||
parallelism: << pipeline.parameters.all-visual-tests-parallelism >>
|
||||
requires:
|
||||
- visual-tests-storybook-build-all
|
||||
# On a branch forced with `run-all-visual-tests`: every test of the
|
||||
# editor suite too, against the latest portable build of `master`.
|
||||
- run-visual-monkey-tests-editor-linux:
|
||||
name: visual-tests-editor-all
|
||||
scope: 'all'
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- master
|
||||
- stable
|
||||
- /experimental-build.*/
|
||||
|
||||
# On a branch / Pull Request: only run the tests related to what it changes
|
||||
# (nothing at all when it changes nothing related to them). The editor suite
|
||||
# runs against the latest portable build of `master` (a branch builds no
|
||||
# app), so it exercises the tests of the branch, not its changes to the app.
|
||||
changed-visual-tests:
|
||||
when:
|
||||
and:
|
||||
- not:
|
||||
equal: [master, << pipeline.git.branch >>]
|
||||
- not:
|
||||
equal: [stable, << pipeline.git.branch >>]
|
||||
- not: << pipeline.parameters.run-all-visual-tests >>
|
||||
jobs:
|
||||
- build-storybook-for-visual-tests:
|
||||
name: visual-tests-storybook-build-changed
|
||||
scope: 'changed'
|
||||
- run-visual-monkey-tests-storybook-linux:
|
||||
name: visual-tests-storybook-changed
|
||||
scope: 'changed'
|
||||
parallelism: << pipeline.parameters.changed-visual-tests-parallelism >>
|
||||
requires:
|
||||
- visual-tests-storybook-build-changed
|
||||
- run-visual-monkey-tests-editor-linux:
|
||||
name: visual-tests-editor-changed
|
||||
scope: 'changed'
|
||||
filters:
|
||||
branches:
|
||||
ignore:
|
||||
- /experimental-build.*/
|
||||
|
||||
@@ -533,7 +533,7 @@ class GD_CORE_API EventsCodeGenerator {
|
||||
* - object : Object name -> string
|
||||
* - expression : Mathematical expression -> number (double)
|
||||
* - string : %Text expression -> string
|
||||
* - layer, color, file, stringWithSelector : Same as string
|
||||
* - layer, color, file, stringWithSelector, easing : Same as string
|
||||
* - relationalOperator : Used to make a comparison between the function
|
||||
return value and value of the parameter preceding the relationOperator
|
||||
parameter -> string
|
||||
|
||||
@@ -55,6 +55,7 @@ gd::String ExpressionCodeGenerator::GenerateExpressionCode(
|
||||
gd::ExpressionValidator validator(codeGenerator.GetPlatform(),
|
||||
codeGenerator.GetProjectScopedContainers(),
|
||||
rootType,
|
||||
rootObjectName,
|
||||
extraInfo);
|
||||
node->Visit(validator);
|
||||
if (!validator.GetFatalErrors().empty()) {
|
||||
|
||||
@@ -53,7 +53,7 @@ void EventsList::InsertEvent(std::shared_ptr<gd::BaseEvent> event,
|
||||
events.push_back(event);
|
||||
}
|
||||
|
||||
gd::BaseEvent& EventsList::InsertNewEvent(gd::Project& project,
|
||||
gd::BaseEvent& EventsList::InsertNewEvent(const gd::Project& project,
|
||||
const gd::String& eventType,
|
||||
size_t position) {
|
||||
gd::BaseEventSPtr event = project.CreateEvent(eventType);
|
||||
|
||||
@@ -66,7 +66,7 @@ class GD_CORE_API EventsList {
|
||||
* \param position Insertion position. If the position is invalid, the object
|
||||
* is inserted at the end of the objects list.
|
||||
*/
|
||||
gd::BaseEvent& InsertNewEvent(gd::Project& project,
|
||||
gd::BaseEvent& InsertNewEvent(const gd::Project& project,
|
||||
const gd::String& eventType,
|
||||
size_t position = (size_t)-1);
|
||||
|
||||
|
||||
@@ -179,6 +179,10 @@ class GD_CORE_API ValueTypeMetadata {
|
||||
return type == "scenevar" || type == "globalvar" || type == "objectvar";
|
||||
}
|
||||
|
||||
static bool IsTypeObjectVariable(const gd::String &type) {
|
||||
return type == "objectvar";
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Return true if the type is representing one object
|
||||
* (or more, i.e: an object group).
|
||||
@@ -223,6 +227,7 @@ class GD_CORE_API ValueTypeMetadata {
|
||||
return parameterType == "string" || parameterType == "layer" ||
|
||||
parameterType == "color" || parameterType == "file" ||
|
||||
parameterType == "stringWithSelector" ||
|
||||
parameterType == "easing" ||
|
||||
parameterType == "sceneName" ||
|
||||
parameterType == "layerEffectName" ||
|
||||
parameterType == "layerEffectParameterName" ||
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/ProjectScopedContainers.h"
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Project/ProjectScopedContainers.h"
|
||||
|
||||
@@ -407,120 +407,6 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
|
||||
eventsParameterReplacer.Launch(events, projectScopedContainers);
|
||||
}
|
||||
|
||||
bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
|
||||
const gd::ProjectScopedContainers& projectScopedContainers,
|
||||
gd::InstructionsList& actions,
|
||||
gd::String name) {
|
||||
bool somethingModified = false;
|
||||
|
||||
for (std::size_t aId = 0; aId < actions.size(); ++aId) {
|
||||
bool deleteMe = false;
|
||||
|
||||
const gd::InstructionMetadata& instrInfos =
|
||||
MetadataProvider::GetActionMetadata(platform, actions[aId].GetType());
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
|
||||
// Find object's name in parameters
|
||||
if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) &&
|
||||
actions[aId].GetParameter(pNb).GetPlainString() == name) {
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
// Find object's name in expressions
|
||||
else if (ParameterMetadata::IsExpression(
|
||||
"number", instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
auto node = actions[aId].GetParameter(pNb).GetRootNode();
|
||||
|
||||
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Find object's name in text expressions
|
||||
else if (ParameterMetadata::IsExpression(
|
||||
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
auto node = actions[aId].GetParameter(pNb).GetRootNode();
|
||||
|
||||
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteMe) {
|
||||
somethingModified = true;
|
||||
actions.Remove(aId);
|
||||
aId--;
|
||||
} else if (!actions[aId].GetSubInstructions().empty())
|
||||
somethingModified =
|
||||
RemoveObjectInActions(platform,
|
||||
projectScopedContainers,
|
||||
actions[aId].GetSubInstructions(),
|
||||
name) ||
|
||||
somethingModified;
|
||||
}
|
||||
|
||||
return somethingModified;
|
||||
}
|
||||
|
||||
bool EventsRefactorer::RemoveObjectInConditions(
|
||||
const gd::Platform& platform,
|
||||
const gd::ProjectScopedContainers& projectScopedContainers,
|
||||
gd::InstructionsList& conditions,
|
||||
gd::String name) {
|
||||
bool somethingModified = false;
|
||||
|
||||
for (std::size_t cId = 0; cId < conditions.size(); ++cId) {
|
||||
bool deleteMe = false;
|
||||
|
||||
const gd::InstructionMetadata& instrInfos =
|
||||
MetadataProvider::GetConditionMetadata(platform,
|
||||
conditions[cId].GetType());
|
||||
for (std::size_t pNb = 0; pNb < instrInfos.parameters.GetParametersCount(); ++pNb) {
|
||||
// Find object's name in parameters
|
||||
if (gd::ParameterMetadata::IsObject(instrInfos.parameters.GetParameter(pNb).GetType()) &&
|
||||
conditions[cId].GetParameter(pNb).GetPlainString() == name) {
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
// Find object's name in expressions
|
||||
else if (ParameterMetadata::IsExpression(
|
||||
"number", instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
|
||||
|
||||
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Find object's name in text expressions
|
||||
else if (ParameterMetadata::IsExpression(
|
||||
"string", instrInfos.parameters.GetParameter(pNb).GetType())) {
|
||||
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
|
||||
|
||||
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {
|
||||
deleteMe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteMe) {
|
||||
somethingModified = true;
|
||||
conditions.Remove(cId);
|
||||
cId--;
|
||||
} else if (!conditions[cId].GetSubInstructions().empty())
|
||||
somethingModified =
|
||||
RemoveObjectInConditions(platform,
|
||||
projectScopedContainers,
|
||||
conditions[cId].GetSubInstructions(),
|
||||
name) ||
|
||||
somethingModified;
|
||||
}
|
||||
|
||||
return somethingModified;
|
||||
}
|
||||
|
||||
gd::String ReplaceAllOccurrencesCaseInsensitive(gd::String context,
|
||||
const gd::String& from,
|
||||
const gd::String& to) {
|
||||
|
||||
@@ -123,26 +123,6 @@ class GD_CORE_API EventsRefactorer {
|
||||
virtual ~EventsRefactorer(){};
|
||||
|
||||
private:
|
||||
/**
|
||||
* Remove all conditions of the list using an object
|
||||
*
|
||||
* \return true if something was modified.
|
||||
*/
|
||||
static bool RemoveObjectInConditions(const gd::Platform& platform,
|
||||
const gd::ProjectScopedContainers& projectScopedContainers,
|
||||
gd::InstructionsList& conditions,
|
||||
gd::String name);
|
||||
|
||||
/**
|
||||
* Remove all actions of the list using an object
|
||||
*
|
||||
* \return true if something was modified.
|
||||
*/
|
||||
static bool RemoveObjectInActions(const gd::Platform& platform,
|
||||
const gd::ProjectScopedContainers& projectScopedContainers,
|
||||
gd::InstructionsList& conditions,
|
||||
gd::String name);
|
||||
|
||||
/**
|
||||
* Replace all occurrences of a gd::String in conditions
|
||||
*
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
|
||||
#include "GDCore/IDE/Events/ExpressionVariableNameFinder.h"
|
||||
#include "GDCore/IDE/VariableInstructionSwitcher.h"
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
|
||||
#include "GDCore/IDE/Events/ExpressionVariableNameFinder.h"
|
||||
#include "GDCore/IDE/VariableInstructionSwitcher.h"
|
||||
|
||||
@@ -580,7 +580,7 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
// Complete a potential child variable:
|
||||
if (variablesContainer->Has(node.identifierName)) {
|
||||
AddCompletionsForChildrenVariablesOf(
|
||||
&variablesContainer->Get(node.identifierName),
|
||||
variablesContainer->Get(node.identifierName),
|
||||
node.childIdentifierNameLocation,
|
||||
node.childIdentifierName);
|
||||
}
|
||||
@@ -611,7 +611,7 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
if (variablesContainer &&
|
||||
variablesContainer->Has(node.identifierName)) {
|
||||
AddCompletionsForChildrenVariablesOf(
|
||||
&variablesContainer->Get(node.identifierName),
|
||||
variablesContainer->Get(node.identifierName),
|
||||
node.childIdentifierNameLocation,
|
||||
node.childIdentifierName);
|
||||
}
|
||||
@@ -873,9 +873,9 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
const ExpressionParserLocation& location,
|
||||
gd::String eagerlyCompleteForVariableName = "") {
|
||||
if (variableAndItsParent.parentVariable) {
|
||||
AddCompletionsForChildrenVariablesOf(variableAndItsParent.parentVariable,
|
||||
location,
|
||||
eagerlyCompleteForVariableName);
|
||||
AddCompletionsForChildrenVariablesOf(
|
||||
*variableAndItsParent.parentVariable,
|
||||
location, eagerlyCompleteForVariableName);
|
||||
} else if (variableAndItsParent.parentVariablesContainer) {
|
||||
AddCompletionsForVariablesMatchingSearch(
|
||||
*variableAndItsParent.parentVariablesContainer, "", location);
|
||||
@@ -883,16 +883,14 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
}
|
||||
|
||||
void AddCompletionsForChildrenVariablesOf(
|
||||
const gd::Variable* variable,
|
||||
const gd::Variable& variable,
|
||||
const ExpressionParserLocation& location,
|
||||
gd::String eagerlyCompleteForVariableName = "") {
|
||||
if (!variable) return;
|
||||
|
||||
if (variable->GetType() == gd::Variable::Structure) {
|
||||
for (const auto& name : variable->GetAllChildrenNames()) {
|
||||
if (variable.GetType() == gd::Variable::Structure) {
|
||||
for (const auto& name : variable.GetAllChildrenNames()) {
|
||||
if (!IsIdentifierSafe(name)) continue;
|
||||
|
||||
const auto& childVariable = variable->GetChild(name);
|
||||
const auto& childVariable = variable.GetChild(name);
|
||||
ExpressionCompletionDescription description(
|
||||
ExpressionCompletionDescription::Variable,
|
||||
location.GetStartPosition(),
|
||||
@@ -902,7 +900,10 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
completions.push_back(description);
|
||||
|
||||
if (name == eagerlyCompleteForVariableName) {
|
||||
AddEagerCompletionForVariableChildren(childVariable, name, location);
|
||||
AddEagerCompletionForVariableChildren(
|
||||
childVariable, name,
|
||||
// The scope is only displayed with the root variable.
|
||||
gd::VariablesContainer::SourceType::Unknown, location);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -914,6 +915,7 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
void AddEagerCompletionForVariableChildren(
|
||||
const gd::Variable& variable,
|
||||
const gd::String& variableName,
|
||||
const gd::VariablesContainer::SourceType variableScope,
|
||||
const ExpressionParserLocation& location) {
|
||||
if (variable.GetType() == gd::Variable::Structure) {
|
||||
gd::String prefix = variableName + ".";
|
||||
@@ -932,6 +934,7 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
location.GetEndPosition());
|
||||
description.SetCompletion(completion);
|
||||
description.SetVariableType(childVariable.GetType());
|
||||
description.SetVariableScope(variableScope);
|
||||
completions.push_back(description);
|
||||
}
|
||||
}
|
||||
@@ -956,7 +959,8 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
|
||||
if (eagerlyCompleteIfExactMatch && variableName == search) {
|
||||
AddEagerCompletionForVariableChildren(
|
||||
variable, variableName, location);
|
||||
variable, variableName, variablesContainer.GetSourceType(),
|
||||
location);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -982,7 +986,8 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
|
||||
if (eagerlyCompleteIfExactMatch && variableName == search) {
|
||||
AddEagerCompletionForVariableChildren(
|
||||
variable, variableName, location);
|
||||
variable, variableName, gd::VariablesContainer::Object,
|
||||
location);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1032,15 +1037,17 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
location.GetEndPosition());
|
||||
description.SetCompletion(variableName);
|
||||
description.SetVariableType(variable.GetType());
|
||||
description.SetVariableScope(
|
||||
auto variableScope =
|
||||
projectScopedContainers.GetVariablesContainersList()
|
||||
.GetVariablesContainerFromVariableOrPropertyOrParameterName(variableName)
|
||||
.GetSourceType());
|
||||
.GetVariablesContainerFromVariableOrPropertyOrParameterName(
|
||||
variableName)
|
||||
.GetSourceType();
|
||||
description.SetVariableScope(variableScope);
|
||||
completions.push_back(description);
|
||||
|
||||
if (eagerlyCompleteIfExactMatch && variableName == search) {
|
||||
AddEagerCompletionForVariableChildren(
|
||||
variable, variableName, location);
|
||||
variable, variableName, variableScope, location);
|
||||
}
|
||||
},
|
||||
[&](const gd::NamedPropertyDescriptor& property) {
|
||||
@@ -1084,15 +1091,17 @@ class GD_CORE_API ExpressionCompletionFinder
|
||||
location.GetEndPosition());
|
||||
description.SetCompletion(variableName);
|
||||
description.SetVariableType(variable.GetType());
|
||||
description.SetVariableScope(
|
||||
auto variableScope =
|
||||
projectScopedContainers.GetVariablesContainersList()
|
||||
.GetVariablesContainerFromVariableOrPropertyOrParameterName(variableName)
|
||||
.GetSourceType());
|
||||
.GetVariablesContainerFromVariableOrPropertyOrParameterName(
|
||||
variableName)
|
||||
.GetSourceType();
|
||||
description.SetVariableScope(variableScope);
|
||||
completions.push_back(description);
|
||||
|
||||
if (eagerlyCompleteIfExactMatch && variableName == search) {
|
||||
AddEagerCompletionForVariableChildren(
|
||||
variable, variableName, location);
|
||||
variable, variableName, variableScope, location);
|
||||
}
|
||||
},
|
||||
[&](const gd::NamedPropertyDescriptor &property) {
|
||||
|
||||
@@ -66,34 +66,57 @@ size_t GetMaximumParametersNumber(
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ExpressionValidator::ValidateChildVariable(
|
||||
const gd::Variable &parentVariable, const gd::String &childVariableName,
|
||||
const gd::ExpressionParserLocation childNameLocation,
|
||||
const bool isUndeclaredVariableFatal) {
|
||||
// A child variable is accessed, check it can be used in an expression.
|
||||
if (!parentVariable.HasChild(childVariableName)) {
|
||||
RaiseTypeError(_("No child variable with this name found."),
|
||||
childNameLocation, isUndeclaredVariableFatal);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExpressionValidator::ValidateLastChildVariable(
|
||||
const gd::Variable &lastChildVariable,
|
||||
const gd::ExpressionParserLocation childNameLocation) {
|
||||
const auto type = lastChildVariable.GetType();
|
||||
|
||||
if (parentType == Type::String || parentType == Type::Number ||
|
||||
parentType == Type::NumberOrString) {
|
||||
// Collections type can't be used directly in expressions, a child
|
||||
// must be accessed.
|
||||
if (type == Variable::Structure) {
|
||||
RaiseTypeError(_("You need to specify the name of the child variable "
|
||||
"to access. For example: `MyVariable.child`."),
|
||||
childNameLocation);
|
||||
} else if (type == Variable::Array) {
|
||||
RaiseTypeError(_("You need to specify the name of the child variable "
|
||||
"to access. For example: `MyVariable[0]`."),
|
||||
childNameLocation);
|
||||
}
|
||||
}
|
||||
// Number, string or boolean variables can be used in expressions.
|
||||
ReadChildTypeFromVariable(type);
|
||||
}
|
||||
|
||||
bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
|
||||
const gd::IdentifierNode& identifier) {
|
||||
return ValidateObjectVariableOrVariableOrProperty(identifier.identifierName, identifier.identifierNameLocation, identifier.childIdentifierName, identifier.childIdentifierNameLocation);
|
||||
const gd::IdentifierNode &identifier) {
|
||||
return ValidateObjectVariableOrVariableOrProperty(
|
||||
identifier.identifierName, identifier.identifierNameLocation,
|
||||
identifier.childIdentifierName, identifier.childIdentifierNameLocation,
|
||||
true);
|
||||
}
|
||||
|
||||
bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
|
||||
const gd::String &identifierName,
|
||||
const gd::ExpressionParserLocation identifierNameLocation,
|
||||
const gd::String &childIdentifierName,
|
||||
const gd::ExpressionParserLocation childIdentifierNameLocation) {
|
||||
auto validateVariableTypeForExpression =
|
||||
[this, &identifierNameLocation](gd::Variable::Type type) {
|
||||
// Collections type can't be used directly in expressions, a child
|
||||
// must be accessed.
|
||||
if (type == Variable::Structure) {
|
||||
RaiseTypeError(_("You need to specify the name of the child variable "
|
||||
"to access. For example: `MyVariable.child`."),
|
||||
identifierNameLocation);
|
||||
} else if (type == Variable::Array) {
|
||||
RaiseTypeError(_("You need to specify the name of the child variable "
|
||||
"to access. For example: `MyVariable[0]`."),
|
||||
identifierNameLocation);
|
||||
} else {
|
||||
// Number, string or boolean variables can be used in expressions.
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
const gd::ExpressionParserLocation childIdentifierNameLocation,
|
||||
const bool isUndeclaredVariableFatal,
|
||||
const bool hasMoreChildren) {
|
||||
const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
|
||||
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
|
||||
const auto& propertiesContainersList = projectScopedContainers.GetPropertiesContainersList();
|
||||
@@ -118,27 +141,34 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
|
||||
identifierName, childIdentifierName);
|
||||
|
||||
if (variableExistence == gd::ObjectsContainersList::DoesNotExist) {
|
||||
RaiseUndeclaredVariableError(_("This variable does not exist on this object or group."),
|
||||
childIdentifierNameLocation, childIdentifierName, identifierName);
|
||||
RaiseUndeclaredVariableError(
|
||||
_("This variable does not exist on this object or group."),
|
||||
childIdentifierNameLocation, childIdentifierName, identifierName,
|
||||
isUndeclaredVariableFatal);
|
||||
|
||||
return true; // We should have found a variable.
|
||||
}
|
||||
else if (variableExistence == gd::ObjectsContainersList::ExistsOnlyOnSomeObjectsOfTheGroup) {
|
||||
RaiseUndeclaredVariableError(_("This variable only exists on some objects of the group. It must be declared for all objects."),
|
||||
childIdentifierNameLocation, childIdentifierName, identifierName);
|
||||
} else if (variableExistence ==
|
||||
gd::ObjectsContainersList::ExistsOnlyOnSomeObjectsOfTheGroup) {
|
||||
RaiseUndeclaredVariableError(
|
||||
_("This variable only exists on some objects of the group. It must be declared for all objects."),
|
||||
childIdentifierNameLocation, childIdentifierName, identifierName,
|
||||
isUndeclaredVariableFatal);
|
||||
|
||||
return true; // We should have found a variable.
|
||||
}
|
||||
else if (variableExistence == gd::ObjectsContainersList::GroupIsEmpty) {
|
||||
} else if (variableExistence == gd::ObjectsContainersList::GroupIsEmpty) {
|
||||
RaiseUndeclaredVariableError(_("This group is empty. Add an object to this group first."),
|
||||
identifierNameLocation, childIdentifierName, identifierName);
|
||||
|
||||
return true; // We should have found a variable.
|
||||
}
|
||||
|
||||
auto variableType = objectsContainersList.GetTypeOfObjectOrGroupVariable(
|
||||
identifierName, childIdentifierName);
|
||||
ReadChildTypeFromVariable(variableType);
|
||||
if (!hasMoreChildren) {
|
||||
const auto &objectVariable =
|
||||
objectsContainersList
|
||||
.GetObjectOrGroupVariablesContainer(identifierName)
|
||||
->Get(childIdentifierName);
|
||||
ValidateLastChildVariable(objectVariable, childIdentifierNameLocation);
|
||||
}
|
||||
|
||||
return true; // We found a variable.
|
||||
}, [&]() {
|
||||
@@ -152,22 +182,16 @@ bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
|
||||
if (childIdentifierName.empty()) {
|
||||
// Just the root variable is accessed, check it can be used in an
|
||||
// expression.
|
||||
validateVariableTypeForExpression(variable.GetType());
|
||||
ReadChildTypeFromVariable(variable.GetType());
|
||||
|
||||
ValidateLastChildVariable(variable, identifierNameLocation);
|
||||
return true; // We found a variable.
|
||||
} else {
|
||||
// A child variable is accessed, check it can be used in an expression.
|
||||
if (!variable.HasChild(childIdentifierName)) {
|
||||
RaiseTypeError(_("No child variable with this name found."),
|
||||
childIdentifierNameLocation);
|
||||
|
||||
return true; // We should have found a variable.
|
||||
ValidateChildVariable(variable, childIdentifierName,
|
||||
childIdentifierNameLocation,
|
||||
isUndeclaredVariableFatal);
|
||||
if (variable.HasChild(childIdentifierName)) {
|
||||
ValidateLastChildVariable(variable.GetChild(childIdentifierName),
|
||||
childIdentifierNameLocation);
|
||||
}
|
||||
|
||||
const gd::Variable& childVariable =
|
||||
variable.GetChild(childIdentifierName);
|
||||
ReadChildTypeFromVariable(childVariable.GetType());
|
||||
return true; // We found a variable.
|
||||
}
|
||||
}, [&]() {
|
||||
@@ -388,6 +412,12 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(
|
||||
return returnType;
|
||||
}
|
||||
|
||||
const gd::String parentRootObjectName = rootObjectName;
|
||||
// We don't check objectvar parameters since they are only used by the legacy
|
||||
// functions like: `Object.Variable(MyVariable)` which allow undeclared
|
||||
// variables.
|
||||
rootObjectName = emptyParameterExtraInfo;
|
||||
|
||||
// TODO: reverse the order of diagnostic?
|
||||
size_t writtenParametersFirstIndex =
|
||||
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
|
||||
@@ -448,6 +478,7 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(
|
||||
}
|
||||
metadataIndex++;
|
||||
}
|
||||
rootObjectName = parentRootObjectName;
|
||||
return returnType;
|
||||
}
|
||||
|
||||
@@ -478,6 +509,7 @@ const gd::String& ExpressionValidator::TypeToString(Type type) {
|
||||
// if it allows properties and parameters.
|
||||
case Type::VariableOrProperty:
|
||||
case Type::VariableOrPropertyOrParameter:
|
||||
case Type::ObjectVariable:
|
||||
case Type::LegacyVariable:
|
||||
return variableTypeString;
|
||||
case Type::Object:
|
||||
@@ -506,7 +538,9 @@ ExpressionValidator::Type ExpressionValidator::StringToType(
|
||||
if (type == ExpressionValidator::variableTypeString ||
|
||||
gd::ParameterMetadata::IsExpression(
|
||||
ExpressionValidator::variableTypeString, type)) {
|
||||
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
|
||||
if (gd::ValueTypeMetadata::IsTypeObjectVariable(type)) {
|
||||
return Type::ObjectVariable;
|
||||
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
|
||||
return Type::LegacyVariable;
|
||||
} else if (type == "variableOrProperty") {
|
||||
return Type::VariableOrProperty;
|
||||
|
||||
@@ -40,10 +40,12 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
ExpressionValidator(const gd::Platform &platform_,
|
||||
const gd::ProjectScopedContainers & projectScopedContainers_,
|
||||
const gd::String &rootType_,
|
||||
const gd::String &rootObjectName_ = emptyParameterExtraInfo,
|
||||
const gd::String &extraInfo_ = emptyParameterExtraInfo)
|
||||
: platform(platform_),
|
||||
projectScopedContainers(projectScopedContainers_),
|
||||
parentType(StringToType(gd::ValueTypeMetadata::GetExpressionPrimitiveValueType(rootType_))),
|
||||
rootObjectName(rootObjectName_),
|
||||
childType(Type::Unknown),
|
||||
forbidsUsageOfBracketsBecauseParentIsObject(false),
|
||||
currentParameterExtraInfo(&extraInfo_),
|
||||
@@ -103,14 +105,19 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
node.leftHandSide->Visit(*this);
|
||||
const Type leftType = childType; // Store the type of the first operand.
|
||||
|
||||
if (leftType == Type::Number) {
|
||||
if (parentType == Type::Variable || parentType == Type::ObjectVariable ||
|
||||
parentType == Type::LegacyVariable) {
|
||||
RaiseOperatorError(
|
||||
_("Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name."),
|
||||
node.rightHandSide->location);
|
||||
} else if (leftType == Type::Number) {
|
||||
if (node.op == ' ') {
|
||||
RaiseError(gd::ExpressionParserError::ErrorType::SyntaxError,
|
||||
"No operator found. Did you forget to enter an operator (like +, -, "
|
||||
"* or /) between numbers or expressions?", node.rightHandSide->location);
|
||||
}
|
||||
}
|
||||
else if (leftType == Type::String) {
|
||||
} else if (leftType == Type::String) {
|
||||
if (node.op == ' ') {
|
||||
RaiseError(gd::ExpressionParserError::ErrorType::SyntaxError,
|
||||
"You must add the operator + between texts or expressions. For "
|
||||
@@ -127,11 +134,6 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
_("Operators (+, -, /, *) can't be used with an object name. Remove "
|
||||
"the operator."),
|
||||
node.rightHandSide->location);
|
||||
} else if (leftType == Type::Variable || leftType == Type::LegacyVariable) {
|
||||
RaiseOperatorError(
|
||||
_("Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name."),
|
||||
node.rightHandSide->location);
|
||||
}
|
||||
|
||||
// The "required" type ("parentType") of the second operator is decided by:
|
||||
@@ -153,7 +155,13 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
node.factor->Visit(*this);
|
||||
const Type rightType = childType;
|
||||
|
||||
if (rightType == Type::Number) {
|
||||
if (parentType == Type::Variable || parentType == Type::ObjectVariable ||
|
||||
parentType == Type::LegacyVariable) {
|
||||
RaiseTypeError(
|
||||
_("Operators (+, -) can't be used in variable names. Remove "
|
||||
"the operator from the variable name."),
|
||||
node.location);
|
||||
} else if (rightType == Type::Number) {
|
||||
if (node.op != '+' && node.op != '-') {
|
||||
// This is actually a dead code because the parser takes them as
|
||||
// binary operations with an empty left side which makes as much sense.
|
||||
@@ -174,11 +182,6 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
_("Operators (+, -) can't be used with an object name. Remove the "
|
||||
"operator."),
|
||||
node.location);
|
||||
} else if (rightType == Type::Variable || rightType == Type::LegacyVariable) {
|
||||
RaiseTypeError(
|
||||
_("Operators (+, -) can't be used in variable names. Remove "
|
||||
"the operator from the variable name."),
|
||||
node.location);
|
||||
}
|
||||
}
|
||||
void OnVisitNumberNode(NumberNode& node) override {
|
||||
@@ -188,6 +191,14 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
RaiseTypeError(
|
||||
_("You entered a number, but a text was expected (in quotes)."),
|
||||
node.location);
|
||||
} else if (parentType == Type::Variable ||
|
||||
parentType == Type::ObjectVariable ||
|
||||
parentType == Type::LegacyVariable) {
|
||||
RaiseTypeError(
|
||||
_("The variable name looks like you're building an expression or a "
|
||||
"formula. You can only use this for structure or arrays, for "
|
||||
"example: Score[3]."),
|
||||
node.location);
|
||||
} else if (parentType != Type::Number &&
|
||||
parentType != Type::NumberOrString) {
|
||||
RaiseTypeError(_("You entered a number, but this type was expected:") +
|
||||
@@ -201,6 +212,14 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
if (parentType == Type::Number) {
|
||||
RaiseTypeError(_("You entered a text, but a number was expected."),
|
||||
node.location);
|
||||
} else if (parentType == Type::Variable ||
|
||||
parentType == Type::ObjectVariable ||
|
||||
parentType == Type::LegacyVariable) {
|
||||
RaiseTypeError(
|
||||
_("The variable name looks like you're building an expression or a "
|
||||
"formula. You can only use this for structure or arrays, for "
|
||||
"example: Score[\"Player1\"]."),
|
||||
node.location);
|
||||
} else if (parentType != Type::String &&
|
||||
parentType != Type::NumberOrString) {
|
||||
RaiseTypeError(_("You entered a text, but this type was expected:") +
|
||||
@@ -210,13 +229,51 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
}
|
||||
void OnVisitVariableNode(VariableNode& node) override {
|
||||
ReportAnyError(node);
|
||||
parentVariable = nullptr;
|
||||
variableChildDepth = 0;
|
||||
|
||||
if (parentType == Type::Variable ||
|
||||
parentType == Type::VariableOrProperty ||
|
||||
parentType == Type::VariableOrPropertyOrParameter) {
|
||||
childType = parentType;
|
||||
|
||||
CheckVariableExistence(node.location, node.name, node.child != nullptr);
|
||||
bool isRootVariableDeclared = CheckVariableExistence(
|
||||
node.location, node.name, node.child != nullptr);
|
||||
if (node.child) {
|
||||
if (isRootVariableDeclared) {
|
||||
const auto &variable =
|
||||
projectScopedContainers.GetVariablesContainersList().Get(
|
||||
node.name);
|
||||
parentVariable = &variable;
|
||||
}
|
||||
node.child->Visit(*this);
|
||||
}
|
||||
} else if (parentType == Type::ObjectVariable) {
|
||||
childType = parentType;
|
||||
|
||||
if (!rootObjectName.empty()) {
|
||||
ValidateObjectVariableOrVariableOrProperty(
|
||||
rootObjectName, node.nameLocation, node.name, node.nameLocation,
|
||||
false, !!node.child);
|
||||
|
||||
const auto &objectsContainersList =
|
||||
projectScopedContainers.GetObjectsContainersList();
|
||||
auto variableExistence =
|
||||
objectsContainersList.HasObjectOrGroupWithVariableNamed(
|
||||
rootObjectName, node.name);
|
||||
if (variableExistence == gd::ObjectsContainersList::Exists) {
|
||||
const auto &objectVariable =
|
||||
objectsContainersList
|
||||
.GetObjectOrGroupVariablesContainer(rootObjectName)
|
||||
->Get(node.name);
|
||||
if (node.child) {
|
||||
parentVariable = &objectVariable;
|
||||
} else {
|
||||
ValidateLastChildVariable(objectVariable, node.nameLocation);
|
||||
}
|
||||
}
|
||||
rootObjectName = "";
|
||||
}
|
||||
if (node.child) {
|
||||
node.child->Visit(*this);
|
||||
}
|
||||
@@ -246,6 +303,14 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
forbidsUsageOfBracketsBecauseParentIsObject = true;
|
||||
}, [&]() {
|
||||
// This is a variable.
|
||||
const auto &variable =
|
||||
projectScopedContainers.GetVariablesContainersList().Get(
|
||||
node.name);
|
||||
if (node.child) {
|
||||
parentVariable = &variable;
|
||||
} else {
|
||||
ValidateLastChildVariable(variable, node.location);
|
||||
}
|
||||
}, [&]() {
|
||||
// This is a property.
|
||||
// Being in this node implies that there is at least a child - which is not supported for properties.
|
||||
@@ -281,10 +346,48 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
ReportAnyError(node);
|
||||
// TODO Also check child-variables existence on a path with only VariableAccessor to raise non-fatal errors.
|
||||
if (!variableObjectName.empty()) {
|
||||
ValidateObjectVariableOrVariableOrProperty(variableObjectName,
|
||||
variableObjectNameLocation,
|
||||
node.name, node.nameLocation);
|
||||
ValidateObjectVariableOrVariableOrProperty(
|
||||
variableObjectName, variableObjectNameLocation, node.name,
|
||||
node.nameLocation, true, !!node.child);
|
||||
|
||||
const auto &objectsContainersList =
|
||||
projectScopedContainers.GetObjectsContainersList();
|
||||
auto variableExistence =
|
||||
objectsContainersList.HasObjectOrGroupWithVariableNamed(
|
||||
variableObjectName, node.name);
|
||||
if (variableExistence == gd::ObjectsContainersList::Exists) {
|
||||
const auto &objectVariable =
|
||||
objectsContainersList
|
||||
.GetObjectOrGroupVariablesContainer(variableObjectName)
|
||||
->Get(node.name);
|
||||
if (node.child) {
|
||||
parentVariable = &objectVariable;
|
||||
} else {
|
||||
parentVariable = nullptr;
|
||||
}
|
||||
} else {
|
||||
parentVariable = nullptr;
|
||||
}
|
||||
variableChildDepth = 0;
|
||||
variableObjectName = "";
|
||||
} else if (parentVariable) {
|
||||
const bool isChildVariableDeclared = ValidateChildVariable(
|
||||
*parentVariable, node.name, node.nameLocation, false);
|
||||
if (isChildVariableDeclared) {
|
||||
const auto &childVariable = parentVariable->GetChild(node.name);
|
||||
if (node.child) {
|
||||
parentVariable = &childVariable;
|
||||
variableChildDepth++;
|
||||
} else {
|
||||
ValidateLastChildVariable(childVariable, node.nameLocation);
|
||||
parentVariable = nullptr;
|
||||
variableChildDepth = 0;
|
||||
}
|
||||
}
|
||||
else {
|
||||
parentVariable = nullptr;
|
||||
variableChildDepth = 0;
|
||||
}
|
||||
}
|
||||
// In the case we accessed an object variable (`MyObject.MyVariable`),
|
||||
// brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
|
||||
@@ -299,6 +402,8 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
ReportAnyError(node);
|
||||
|
||||
variableObjectName = "";
|
||||
parentVariable = nullptr;
|
||||
variableChildDepth = 0;
|
||||
if (forbidsUsageOfBracketsBecauseParentIsObject) {
|
||||
RaiseError(gd::ExpressionParserError::ErrorType::BracketsNotAllowedForObjects,
|
||||
_("You can't use the brackets to access an object variable. "
|
||||
@@ -350,7 +455,46 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
} else if (parentType == Type::Variable ||
|
||||
parentType == Type::VariableOrProperty ||
|
||||
parentType == Type::VariableOrPropertyOrParameter) {
|
||||
CheckVariableExistence(node.location, node.identifierName, !node.childIdentifierName.empty());
|
||||
bool isRootVariableDeclared =
|
||||
CheckVariableExistence(node.location, node.identifierName,
|
||||
!node.childIdentifierName.empty());
|
||||
if (isRootVariableDeclared && !node.childIdentifierName.empty()) {
|
||||
ValidateObjectVariableOrVariableOrProperty(
|
||||
node.identifierName, node.identifierNameLocation,
|
||||
node.childIdentifierName, node.childIdentifierNameLocation, false);
|
||||
}
|
||||
} else if (parentType == Type::ObjectVariable) {
|
||||
childType = parentType;
|
||||
if (!rootObjectName.empty()) {
|
||||
ValidateObjectVariableOrVariableOrProperty(
|
||||
rootObjectName, node.identifierNameLocation, node.identifierName,
|
||||
node.identifierNameLocation, false,
|
||||
!node.childIdentifierName.empty());
|
||||
|
||||
const auto &objectsContainersList =
|
||||
projectScopedContainers.GetObjectsContainersList();
|
||||
auto variableExistence =
|
||||
objectsContainersList.HasObjectOrGroupWithVariableNamed(
|
||||
rootObjectName, node.identifierName);
|
||||
if (variableExistence == gd::ObjectsContainersList::Exists) {
|
||||
const auto &objectVariable =
|
||||
objectsContainersList
|
||||
.GetObjectOrGroupVariablesContainer(rootObjectName)
|
||||
->Get(node.identifierName);
|
||||
if (!node.childIdentifierName.empty()) {
|
||||
const bool isChildVariableDeclared =
|
||||
ValidateChildVariable(objectVariable, node.childIdentifierName,
|
||||
node.childIdentifierNameLocation, false);
|
||||
if (isChildVariableDeclared) {
|
||||
const auto &childVariable =
|
||||
objectVariable.GetChild(node.childIdentifierName);
|
||||
ValidateLastChildVariable(childVariable,
|
||||
node.childIdentifierNameLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
rootObjectName = "";
|
||||
}
|
||||
} else if (parentType != Type::Object &&
|
||||
parentType != Type::LegacyVariable) {
|
||||
// It can't happen.
|
||||
@@ -376,7 +520,9 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
|
||||
} else if (parentType == Type::String) {
|
||||
message = _(
|
||||
"You must enter a text (between quotes) or a valid expression call.");
|
||||
} else if (parentType == Type::Variable || parentType == Type::LegacyVariable) {
|
||||
} else if (parentType == Type::Variable ||
|
||||
parentType == Type::ObjectVariable ||
|
||||
parentType == Type::LegacyVariable) {
|
||||
message = _("You must enter a variable name.");
|
||||
} else if (parentType == Type::Object) {
|
||||
message = _("You must enter a valid object name.");
|
||||
@@ -395,6 +541,7 @@ private:
|
||||
String,
|
||||
NumberOrString,
|
||||
Variable,
|
||||
ObjectVariable,
|
||||
LegacyVariable,
|
||||
Object,
|
||||
Empty,
|
||||
@@ -407,12 +554,22 @@ private:
|
||||
const gd::String &identifierName,
|
||||
const gd::ExpressionParserLocation identifierNameLocation,
|
||||
const gd::String &childIdentifierName,
|
||||
const gd::ExpressionParserLocation childIdentifierNameLocation);
|
||||
const gd::ExpressionParserLocation childIdentifierNameLocation,
|
||||
const bool isUndeclaredVariableFatal,
|
||||
const bool hasMoreChildren = false);
|
||||
bool ValidateChildVariable(
|
||||
const gd::Variable &parentVariable, const gd::String &childVariableName,
|
||||
const gd::ExpressionParserLocation childNameLocation,
|
||||
const bool isUndeclaredVariableFatal);
|
||||
void ValidateLastChildVariable(
|
||||
const gd::Variable &lastChildVariable,
|
||||
const gd::ExpressionParserLocation childNameLocation);
|
||||
|
||||
void CheckVariableExistence(const ExpressionParserLocation &location,
|
||||
bool CheckVariableExistence(const ExpressionParserLocation &location,
|
||||
const gd::String &name, bool hasChild) {
|
||||
if (!currentParameterExtraInfo ||
|
||||
*currentParameterExtraInfo != "AllowUndeclaredVariable") {
|
||||
bool isRootVariableDeclared = false;
|
||||
projectScopedContainers.MatchIdentifierWithName<void>(
|
||||
name,
|
||||
[&]() {
|
||||
@@ -424,6 +581,7 @@ private:
|
||||
},
|
||||
[&]() {
|
||||
// This is a variable.
|
||||
isRootVariableDeclared = true;
|
||||
},
|
||||
[&]() {
|
||||
// This is a property.
|
||||
@@ -456,7 +614,9 @@ private:
|
||||
_("No variable with this name found."), location,
|
||||
name);
|
||||
});
|
||||
return isRootVariableDeclared;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ReportAnyError(const ExpressionNode& node, bool isFatal = true) {
|
||||
@@ -497,9 +657,10 @@ private:
|
||||
void RaiseUndeclaredVariableError(const gd::String &message,
|
||||
const ExpressionParserLocation &location,
|
||||
const gd::String &variableName,
|
||||
const gd::String &objectName = "") {
|
||||
const gd::String &objectName = "",
|
||||
const bool isUndeclaredVariableFatal = true) {
|
||||
RaiseError(gd::ExpressionParserError::ErrorType::UndeclaredVariable,
|
||||
message, location, true, variableName, objectName);
|
||||
message, location, isUndeclaredVariableFatal, variableName, objectName);
|
||||
}
|
||||
|
||||
void RaiseVariableNameCollisionError(const gd::String &message,
|
||||
@@ -566,9 +727,13 @@ private:
|
||||
std::vector<std::unique_ptr<ExpressionParserError>> supplementalErrors;
|
||||
Type childType; ///< The type "discovered" down the tree and passed up.
|
||||
Type parentType; ///< The type "required" by the top of the tree.
|
||||
/** The root object name of the expression or a function call. */
|
||||
gd::String rootObjectName;
|
||||
bool forbidsUsageOfBracketsBecauseParentIsObject;
|
||||
gd::String variableObjectName;
|
||||
gd::ExpressionParserLocation variableObjectNameLocation;
|
||||
const gd::Variable *parentVariable = nullptr;
|
||||
size_t variableChildDepth = 0;
|
||||
const gd::String *currentParameterExtraInfo;
|
||||
const gd::Platform &platform;
|
||||
const gd::ProjectScopedContainers &projectScopedContainers;
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-present Florian Rival ([email protected]). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "EventsFunctionsExtensionBrowser.h"
|
||||
|
||||
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryEventBasedBehaviorsWorker.h"
|
||||
#include "GDCore/IDE/Project/ArbitraryEventsFunctionsWorker.h"
|
||||
#include "GDCore/IDE/ProjectBrowserHelper.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
void EventsFunctionsExtensionBrowser::ExposeEvents(
|
||||
gd::Project &project, gd::ArbitraryEventsWorker &worker) const {
|
||||
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
|
||||
project, eventsFunctionsExtension, worker);
|
||||
}
|
||||
|
||||
void EventsFunctionsExtensionBrowser::ExposeEvents(
|
||||
gd::Project &project, gd::ArbitraryEventsWorkerWithContext &worker) const {
|
||||
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionEvents(
|
||||
project, eventsFunctionsExtension, worker);
|
||||
}
|
||||
|
||||
void EventsFunctionsExtensionBrowser::ExposeFunctions(
|
||||
gd::Project &project, gd::ArbitraryEventsFunctionsWorker &worker) const {
|
||||
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionFunctions(
|
||||
project, eventsFunctionsExtension, worker);
|
||||
}
|
||||
|
||||
void EventsFunctionsExtensionBrowser::ExposeEventBasedBehaviors(
|
||||
gd::Project &project,
|
||||
gd::ArbitraryEventBasedBehaviorsWorker &worker) const {
|
||||
worker.Launch(eventsFunctionsExtension.GetEventsBasedBehaviors());
|
||||
}
|
||||
|
||||
void EventsFunctionsExtensionBrowser::ExposeObjects(
|
||||
gd::Project &project, gd::ArbitraryObjectsWorker &worker) const {
|
||||
gd::ProjectBrowserHelper::ExposeEventsFunctionsExtensionObjects(
|
||||
eventsFunctionsExtension, worker);
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-present Florian Rival ([email protected]). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/IDE/ProjectBrowser.h"
|
||||
|
||||
namespace gd {
|
||||
class Project;
|
||||
class String;
|
||||
class EventsFunctionsExtension;
|
||||
class EventsFunction;
|
||||
class EventsBasedBehavior;
|
||||
class EventsBasedObject;
|
||||
class ArbitraryEventsWorker;
|
||||
class ArbitraryEventsWorkerWithContext;
|
||||
class ArbitraryEventsFunctionsWorker;
|
||||
class ArbitraryObjectsWorker;
|
||||
class ArbitraryEventBasedBehaviorsWorker;
|
||||
class ArbitraryBehaviorSharedDataWorker;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
|
||||
/**
|
||||
* \brief Expose events-functions extension contents to workers.
|
||||
*/
|
||||
class GD_CORE_API EventsFunctionsExtensionBrowser : public ProjectBrowser {
|
||||
public:
|
||||
EventsFunctionsExtensionBrowser(
|
||||
gd::EventsFunctionsExtension &eventsFunctionsExtension_)
|
||||
: eventsFunctionsExtension(eventsFunctionsExtension_) {}
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all events of the events-functions
|
||||
* extension.
|
||||
*
|
||||
* This should be the preferred way to traverse all the events of an
|
||||
* events-functions extension.
|
||||
*/
|
||||
void ExposeEvents(gd::Project &project,
|
||||
gd::ArbitraryEventsWorker &worker) const override;
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all events of the events-functions
|
||||
* extension.
|
||||
*
|
||||
* This should be the preferred way to traverse all the events of an
|
||||
* events-functions extension.
|
||||
*/
|
||||
void
|
||||
ExposeEvents(gd::Project &project,
|
||||
gd::ArbitraryEventsWorkerWithContext &worker) const override;
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all functions of the events-functions
|
||||
* extension (free function, behavior function and object function).
|
||||
*
|
||||
* This should be the preferred way to traverse all the function signatures
|
||||
* of an events-functions extension.
|
||||
*/
|
||||
void
|
||||
ExposeFunctions(gd::Project &project,
|
||||
gd::ArbitraryEventsFunctionsWorker &worker) const override;
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all ObjectContainers of the
|
||||
* events-functions extension.
|
||||
*
|
||||
* This should be the preferred way to traverse all the objects of an
|
||||
* events-functions extension.
|
||||
*/
|
||||
void ExposeObjects(gd::Project &project,
|
||||
gd::ArbitraryObjectsWorker &worker) const override;
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on the event-based behaviors of the
|
||||
* events-functions extension.
|
||||
*/
|
||||
void ExposeEventBasedBehaviors(
|
||||
gd::Project &project,
|
||||
gd::ArbitraryEventBasedBehaviorsWorker &worker) const override;
|
||||
|
||||
/**
|
||||
* \brief Do nothing.
|
||||
*/
|
||||
void ExposeBehaviorSharedDatas(
|
||||
gd::Project &project,
|
||||
gd::ArbitraryBehaviorSharedDataWorker &worker) const override {};
|
||||
|
||||
private:
|
||||
gd::EventsFunctionsExtension &eventsFunctionsExtension;
|
||||
};
|
||||
|
||||
} // namespace gd
|
||||
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-present Florian Rival ([email protected]). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#include "ChildObjectForwardFunctionGenerator.h"
|
||||
|
||||
#include "GDCore/Events/Builtin/CommentEvent.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
|
||||
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/WholeProjectRefactorer.h"
|
||||
#include "GDCore/Project/EventsBasedObject.h"
|
||||
#include "GDCore/Project/EventsFunction.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/String.h"
|
||||
|
||||
namespace gd {
|
||||
|
||||
void ChildObjectForwardFunctionGenerator::GenerateChildObjectForwardFunctions(
|
||||
const gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::String &childObjectName) {
|
||||
if (!eventsBasedObject.GetObjects().HasObjectNamed(childObjectName)) {
|
||||
return;
|
||||
}
|
||||
auto &childObject = eventsBasedObject.GetObjects().GetObject(childObjectName);
|
||||
if (!project.HasEventsBasedObject(childObject.GetType())) {
|
||||
return;
|
||||
}
|
||||
auto &childEventsBasedObject =
|
||||
project.GetEventsBasedObject(childObject.GetType());
|
||||
for (auto &childEventsFunction :
|
||||
childEventsBasedObject.GetEventsFunctions().GetInternalVector()) {
|
||||
if (childEventsFunction->IsPrivate() ||
|
||||
childEventsFunction->IsDeprecated() ||
|
||||
gd::EventsBasedObject::IsObjectLifecycleEventsFunction(
|
||||
childEventsFunction->GetName())) {
|
||||
continue;
|
||||
}
|
||||
ChildObjectForwardFunctionGenerator::GenerateChildObjectForwardFunction(
|
||||
project, eventsFunctionsExtension, eventsBasedObject, childObjectName,
|
||||
*childEventsFunction);
|
||||
}
|
||||
}
|
||||
|
||||
void ChildObjectForwardFunctionGenerator::GenerateChildObjectForwardFunction(
|
||||
const gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &parentEventsFunctionsExtension,
|
||||
gd::EventsBasedObject &parentEventsBasedObject,
|
||||
const gd::String &childObjectName,
|
||||
const gd::EventsFunction &childEventsFunction) {
|
||||
if (!parentEventsBasedObject.GetObjects().HasObjectNamed(childObjectName)) {
|
||||
return;
|
||||
}
|
||||
auto &childObject =
|
||||
parentEventsBasedObject.GetObjects().GetObject(childObjectName);
|
||||
if (!project.HasEventsBasedObject(childObject.GetType())) {
|
||||
return;
|
||||
}
|
||||
auto &childEventsBasedObject =
|
||||
project.GetEventsBasedObject(childObject.GetType());
|
||||
auto &functionName = childEventsFunction.GetName();
|
||||
if (parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
functionName)) {
|
||||
return;
|
||||
}
|
||||
auto &parentEventsFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().InsertEventsFunction(
|
||||
childEventsFunction, 0);
|
||||
parentEventsFunction.GetEvents().Clear();
|
||||
parentEventsBasedObject.GetEventsFunctions()
|
||||
.AddMissingFunctionsInRootFolder();
|
||||
auto &rootFolder =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetRootFolder();
|
||||
// TODO Handle sub-folders
|
||||
auto &folder =
|
||||
!childEventsFunction.GetGroup().empty()
|
||||
? rootFolder.GetOrCreateChildFolder(childEventsFunction.GetGroup())
|
||||
: rootFolder;
|
||||
rootFolder.MoveFunctionFolderOrFunctionToAnotherFolder(
|
||||
rootFolder.GetFunctionNamed(parentEventsFunction.GetName()), folder,
|
||||
folder.GetChildrenCount());
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
parentEventsFunctionsExtension, parentEventsBasedObject);
|
||||
|
||||
auto childExtensionName =
|
||||
gd::PlatformExtension::GetExtensionFromFullObjectType(
|
||||
childObject.GetType());
|
||||
auto childFunctionType =
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
childExtensionName, childEventsBasedObject.GetName(), functionName);
|
||||
|
||||
bool hasAnyBooleanParameter = false;
|
||||
for (auto ¶meter :
|
||||
parentEventsFunction.GetParameters().GetInternalVector()) {
|
||||
if (parameter->GetValueTypeMetadata().IsBoolean()) {
|
||||
hasAnyBooleanParameter = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (hasAnyBooleanParameter &&
|
||||
parentEventsFunction.GetParameters().GetParametersCount() > 2) {
|
||||
auto &event = dynamic_cast<gd::CommentEvent &>(
|
||||
parentEventsFunction.GetEvents().InsertNewEvent(
|
||||
project, "BuiltinCommonInstructions::Comment", 0));
|
||||
event.SetComment("TODO: Please implement this function manually.");
|
||||
return;
|
||||
}
|
||||
auto &event = dynamic_cast<gd::StandardEvent &>(
|
||||
parentEventsFunction.GetEvents().InsertNewEvent(
|
||||
project, "BuiltinCommonInstructions::Standard", 0));
|
||||
switch (childEventsFunction.GetFunctionType()) {
|
||||
case gd::EventsFunction::FunctionType::ActionWithOperator:
|
||||
case gd::EventsFunction::FunctionType::Action: {
|
||||
if (parentEventsFunction.GetParameters().GetParametersCount() == 2 &&
|
||||
hasAnyBooleanParameter) {
|
||||
gd::Instruction condition;
|
||||
condition.SetType("BooleanVariable");
|
||||
condition.AddParameter("Value");
|
||||
condition.AddParameter("True");
|
||||
condition.AddParameter("");
|
||||
event.GetConditions().Insert(condition, 0);
|
||||
|
||||
gd::Instruction action;
|
||||
action.SetType(childFunctionType);
|
||||
action.AddParameter(childObjectName);
|
||||
action.AddParameter("yes");
|
||||
event.GetActions().Insert(action, 0);
|
||||
{
|
||||
auto &event = dynamic_cast<gd::StandardEvent &>(
|
||||
parentEventsFunction.GetEvents().InsertNewEvent(
|
||||
project, "BuiltinCommonInstructions::Standard", 0));
|
||||
|
||||
gd::Instruction condition;
|
||||
condition.SetType("BooleanVariable");
|
||||
condition.AddParameter("Value");
|
||||
condition.AddParameter("False");
|
||||
condition.AddParameter("");
|
||||
event.GetConditions().Insert(condition, 0);
|
||||
|
||||
gd::Instruction action;
|
||||
action.SetType(childFunctionType);
|
||||
action.AddParameter(childObjectName);
|
||||
action.AddParameter("no");
|
||||
event.GetActions().Insert(action, 0);
|
||||
}
|
||||
} else {
|
||||
gd::Instruction action;
|
||||
action.SetType(childFunctionType);
|
||||
for (auto ¶meter :
|
||||
childEventsFunction
|
||||
.GetParametersForEvents(
|
||||
parentEventsBasedObject.GetEventsFunctions())
|
||||
.GetInternalVector()) {
|
||||
if (childEventsFunction.GetFunctionType() ==
|
||||
gd::EventsFunction::FunctionType::ActionWithOperator &&
|
||||
parameter->GetName() == "Value") {
|
||||
action.AddParameter("=");
|
||||
}
|
||||
action.AddParameter(parameter->GetName());
|
||||
}
|
||||
action.SetParameter(0, childObjectName);
|
||||
event.GetActions().Insert(action, 0);
|
||||
if (childEventsFunction.IsAsync()) {
|
||||
gd::Instruction action;
|
||||
action.SetType("BuiltinAsync::ResolveAsyncEventsFunction");
|
||||
event.GetActions().Insert(action, 1);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case gd::EventsFunction::FunctionType::Condition: {
|
||||
gd::Instruction condition;
|
||||
condition.SetType(childFunctionType);
|
||||
for (auto ¶meter : childEventsFunction
|
||||
.GetParametersForEvents(
|
||||
parentEventsBasedObject.GetEventsFunctions())
|
||||
.GetInternalVector()) {
|
||||
condition.AddParameter(parameter->GetName());
|
||||
}
|
||||
condition.SetParameter(0, childObjectName);
|
||||
event.GetConditions().Insert(condition, 0);
|
||||
|
||||
gd::Instruction action;
|
||||
action.SetType("SetReturnBoolean");
|
||||
action.AddParameter("True");
|
||||
event.GetActions().Insert(action, 0);
|
||||
if (childEventsFunction.IsAsync()) {
|
||||
gd::Instruction action;
|
||||
action.SetType("BuiltinAsync::ResolveAsyncEventsFunction");
|
||||
event.GetActions().Insert(action, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case gd::EventsFunction::FunctionType::ExpressionAndCondition:
|
||||
case gd::EventsFunction::FunctionType::Expression: {
|
||||
gd::Instruction action;
|
||||
action.SetType(childEventsFunction.GetExpressionType().IsNumber()
|
||||
? "SetReturnNumber"
|
||||
: "SetReturnString");
|
||||
gd::String expression =
|
||||
childObjectName + "." + childEventsFunction.GetName() + "(";
|
||||
auto ¶meters = childEventsFunction.GetParametersForEvents(
|
||||
parentEventsBasedObject.GetEventsFunctions());
|
||||
for (size_t i = 1; i < parameters.GetParametersCount(); i++) {
|
||||
if (i > 1) {
|
||||
expression += ", ";
|
||||
}
|
||||
expression += parameters.GetParameter(i).GetName();
|
||||
}
|
||||
expression += ")";
|
||||
action.AddParameter(expression);
|
||||
event.GetActions().Insert(action, 0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool ChildObjectForwardFunctionGenerator::HasAnyChildCustomObject(
|
||||
const gd::Project &project, gd::EventsBasedObject &eventsBasedObject) {
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
if (project.HasEventsBasedObject(childObject->GetType())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<gd::String>
|
||||
ChildObjectForwardFunctionGenerator::GetChildCustomObjectNames(
|
||||
const gd::Project &project, gd::EventsBasedObject &eventsBasedObject) {
|
||||
std::vector<gd::String> objectNames;
|
||||
for (auto &childObject : eventsBasedObject.GetObjects().GetObjects()) {
|
||||
if (project.HasEventsBasedObject(childObject->GetType())) {
|
||||
objectNames.push_back(childObject->GetName());
|
||||
}
|
||||
}
|
||||
return objectNames;
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-present Florian Rival ([email protected]). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include "GDCore/String.h"
|
||||
#include <vector>
|
||||
|
||||
namespace gd {
|
||||
class Project;
|
||||
class EventsFunctionsExtension;
|
||||
class EventsBasedObject;
|
||||
class EventsFunction;
|
||||
class Object;
|
||||
} // namespace gd
|
||||
|
||||
namespace gd {
|
||||
/**
|
||||
* Generate events functions in a custom object that forwards the call to one of
|
||||
* its child object. It allows users to quickly expose features from a child
|
||||
* object.
|
||||
*/
|
||||
class GD_CORE_API ChildObjectForwardFunctionGenerator {
|
||||
public:
|
||||
static void GenerateChildObjectForwardFunctions(
|
||||
const gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &parentEventsFunctionsExtension,
|
||||
gd::EventsBasedObject &parentEventsBasedObject,
|
||||
const gd::String &childObjectName);
|
||||
|
||||
static void GenerateChildObjectForwardFunction(
|
||||
const gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &parentEventsFunctionsExtension,
|
||||
gd::EventsBasedObject &parentEventsBasedObject,
|
||||
const gd::String &childObjectName,
|
||||
const gd::EventsFunction &eventsFunction);
|
||||
|
||||
static bool HasAnyChildCustomObject(const gd::Project &project,
|
||||
gd::EventsBasedObject &eventsBasedObject);
|
||||
|
||||
static std::vector<gd::String>
|
||||
GetChildCustomObjectNames(const gd::Project &project,
|
||||
gd::EventsBasedObject &eventsBasedObject);
|
||||
};
|
||||
} // namespace gd
|
||||
@@ -65,10 +65,14 @@ ParameterValidationResult InstructionValidator::ValidateParameter(
|
||||
if (gd::ParameterMetadata::IsExpression("number", parameterType) ||
|
||||
gd::ParameterMetadata::IsExpression("string", parameterType) ||
|
||||
gd::ParameterMetadata::IsExpression("variable", parameterType)) {
|
||||
|
||||
gd::String rootObjectName = InstructionValidator::GetObjectNameForParameter(
|
||||
projectScopedContainers, instruction, parameterType);
|
||||
auto &expressionNode =
|
||||
*instruction.GetParameter(parameterIndex).GetRootNode();
|
||||
ExpressionValidator expressionValidator(platform, projectScopedContainers,
|
||||
parameterType,
|
||||
rootObjectName,
|
||||
parameterMetadata.GetExtraInfo());
|
||||
expressionNode.Visit(expressionValidator);
|
||||
|
||||
@@ -78,27 +82,6 @@ ParameterValidationResult InstructionValidator::ValidateParameter(
|
||||
if (!expressionValidator.GetDeprecationWarnings().empty()) {
|
||||
result.hasDeprecationWarning = true;
|
||||
}
|
||||
|
||||
// New object variable instructions require the variable to be
|
||||
// declared while legacy ones don't.
|
||||
// This is why it's done here instead of in the parser directly.
|
||||
if (result.isValid && parameterType == "objectvar" &&
|
||||
gd::VariableInstructionSwitcher::IsSwitchableVariableInstruction(
|
||||
instruction.GetType())) {
|
||||
// Check at least the name of the root variable, it's the best we can
|
||||
// do.
|
||||
const auto &objectsContainersList =
|
||||
projectScopedContainers.GetObjectsContainersList();
|
||||
const auto &objectName = instruction.GetParameter(0).GetPlainString();
|
||||
const auto &variableName =
|
||||
instruction.GetParameter(parameterIndex).GetPlainString();
|
||||
if (objectsContainersList.HasObjectOrGroupWithVariableNamed(
|
||||
objectName,
|
||||
gd::InstructionValidator::GetRootVariableName(variableName)) ==
|
||||
gd::ObjectsContainersList::DoesNotExist) {
|
||||
result.isValid = false;
|
||||
}
|
||||
}
|
||||
} else if (gd::ParameterMetadata::IsObject(parameterType)) {
|
||||
const auto &objectOrGroupName =
|
||||
instruction.GetParameter(parameterIndex).GetPlainString();
|
||||
@@ -121,6 +104,35 @@ ParameterValidationResult InstructionValidator::ValidateParameter(
|
||||
return result;
|
||||
}
|
||||
|
||||
gd::String InstructionValidator::GetObjectNameForParameter(
|
||||
const gd::ProjectScopedContainers projectScopedContainers,
|
||||
const gd::Instruction &instruction, const gd::String ¶meterType) {
|
||||
// New object variable instructions require the variable to be
|
||||
// declared while legacy ones don't.
|
||||
// For legacy variable instruction, we pass an empty object name.
|
||||
gd::String rootObjectName = "";
|
||||
if (parameterType != "objectvar") {
|
||||
return "";
|
||||
}
|
||||
if (instruction.GetParametersCount() == 0) {
|
||||
return "";
|
||||
}
|
||||
rootObjectName = instruction.GetParameter(0).GetPlainString();
|
||||
|
||||
if (!gd::VariableInstructionSwitcher::IsSwitchableVariableInstruction(
|
||||
instruction.GetType())) {
|
||||
// Extensions still rely on legacy object variables instructions.
|
||||
auto objectSourceType =
|
||||
projectScopedContainers.GetObjectsContainersList()
|
||||
.GetObjectsContainerSourceType(rootObjectName);
|
||||
// Only child-object variable declarations are checked.
|
||||
if (objectSourceType != gd::ObjectsContainer::SourceType::Object) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return rootObjectName;
|
||||
}
|
||||
|
||||
bool InstructionValidator::IsParameterValid(
|
||||
const gd::Platform &platform,
|
||||
const gd::ProjectScopedContainers projectScopedContainers,
|
||||
|
||||
@@ -63,6 +63,10 @@ public:
|
||||
|
||||
static gd::String GetRootVariableName(const gd::String &name);
|
||||
|
||||
static gd::String GetObjectNameForParameter(
|
||||
const gd::ProjectScopedContainers projectScopedContainers,
|
||||
const gd::Instruction &instruction, const gd::String ¶meterType);
|
||||
|
||||
private:
|
||||
static bool
|
||||
HasRequiredBehaviors(const gd::Instruction &instruction,
|
||||
|
||||
@@ -451,6 +451,21 @@ void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
|
||||
propertyVariablesContainer, worker);
|
||||
}
|
||||
|
||||
void ProjectBrowserHelper::ExposeEventsFunctionsExtensionFunctions(
|
||||
gd::Project &project,
|
||||
gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
ArbitraryEventsFunctionsWorker &worker) {
|
||||
worker.Launch(eventsFunctionsExtension.GetEventsFunctions());
|
||||
for (auto &&eventsBasedBehavior :
|
||||
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
|
||||
worker.Launch(eventsBasedBehavior->GetEventsFunctions());
|
||||
}
|
||||
for (auto &&eventsBasedObject :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
|
||||
worker.Launch(eventsBasedObject->GetEventsFunctions());
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectBrowserHelper::ExposeProjectObjects(
|
||||
gd::Project &project, gd::ArbitraryObjectsWorker &worker) {
|
||||
|
||||
|
||||
@@ -239,6 +239,18 @@ public:
|
||||
const gd::EventsBasedObject &eventsBasedObject,
|
||||
gd::ReadOnlyArbitraryEventsWorkerWithContext &worker);
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all functions of the event-based
|
||||
* extension.
|
||||
*
|
||||
* This should be the preferred way to traverse all the functions of an events
|
||||
* based extension.
|
||||
*/
|
||||
static void ExposeEventsFunctionsExtensionFunctions(
|
||||
gd::Project &project,
|
||||
gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
ArbitraryEventsFunctionsWorker &worker);
|
||||
|
||||
/**
|
||||
* \brief Call the specified worker on all ObjectContainers of the project
|
||||
* (global, layouts...)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "GDCore/IDE/ObjectRefactorer.h"
|
||||
#include "GDCore/IDE/EventBasedBehaviorBrowser.h"
|
||||
#include "GDCore/IDE/EventBasedObjectBrowser.h"
|
||||
#include "GDCore/IDE/EventsFunctionsExtensionBrowser.h"
|
||||
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
|
||||
#include "GDCore/IDE/Events/BehaviorParametersFiller.h"
|
||||
#include "GDCore/IDE/Events/BehaviorPropertyRenamer.h"
|
||||
@@ -449,6 +450,17 @@ void WholeProjectRefactorer::ApplyRefactoringForGroupVariablesContainer(
|
||||
project, eventsVariableInstructionTypeSwitcher);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::UpdateExtensionNameInExtension(
|
||||
gd::Project &project,
|
||||
gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &sourceExtensionName) {
|
||||
const EventsFunctionsExtensionBrowser eventsFunctionsExtensionBrowser(
|
||||
eventsFunctionsExtension);
|
||||
WholeProjectRefactorer::RenameEventsFunctionsExtension(
|
||||
project, eventsFunctionsExtension, sourceExtensionName,
|
||||
eventsFunctionsExtension.GetName(), eventsFunctionsExtensionBrowser);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::UpdateExtensionNameInEventsBasedBehavior(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
@@ -497,145 +509,6 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension(
|
||||
projectBrowser);
|
||||
};
|
||||
|
||||
auto renameBehaviorEventsFunction =
|
||||
[&project, &oldName, &newName,
|
||||
&projectBrowser](const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
if (eventsFunction.IsExpression()) {
|
||||
// Nothing to do, expressions are not including the extension name
|
||||
}
|
||||
if (eventsFunction.IsAction() || eventsFunction.IsCondition()) {
|
||||
gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
oldName, eventsBasedBehavior.GetName(),
|
||||
eventsFunction.GetName()),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
newName, eventsBasedBehavior.GetName(),
|
||||
eventsFunction.GetName()));
|
||||
projectBrowser.ExposeEvents(project, renamer);
|
||||
}
|
||||
};
|
||||
|
||||
auto renameBehaviorPropertyFunctions =
|
||||
[&project, &oldName, &newName,
|
||||
&projectBrowser](const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::NamedPropertyDescriptor &property) {
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
oldName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetPropertyActionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
newName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetPropertyActionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, actionRenamer);
|
||||
|
||||
gd::InstructionsTypeRenamer conditionRenamer =
|
||||
gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
oldName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetPropertyConditionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
newName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetPropertyConditionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, conditionRenamer);
|
||||
|
||||
// Nothing to do for expressions, expressions are not including the
|
||||
// extension name
|
||||
};
|
||||
|
||||
auto renameBehaviorSharedPropertyFunctions =
|
||||
[&project, &oldName, &newName,
|
||||
&projectBrowser](const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::NamedPropertyDescriptor &property) {
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
oldName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetSharedPropertyActionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
newName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetSharedPropertyActionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, actionRenamer);
|
||||
|
||||
gd::InstructionsTypeRenamer conditionRenamer =
|
||||
gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
oldName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetSharedPropertyConditionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
newName, eventsBasedBehavior.GetName(),
|
||||
gd::EventsBasedBehavior::GetSharedPropertyConditionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, conditionRenamer);
|
||||
|
||||
// Nothing to do for expressions, expressions are not including the
|
||||
// extension name
|
||||
};
|
||||
|
||||
auto renameObjectEventsFunction =
|
||||
[&project, &oldName, &newName,
|
||||
&projectBrowser](const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::EventsFunction &eventsFunction) {
|
||||
if (eventsFunction.IsExpression()) {
|
||||
// Nothing to do, expressions are not including the extension name
|
||||
}
|
||||
if (eventsFunction.IsAction() || eventsFunction.IsCondition()) {
|
||||
gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
oldName, eventsBasedObject.GetName(),
|
||||
eventsFunction.GetName()),
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
newName, eventsBasedObject.GetName(),
|
||||
eventsFunction.GetName()));
|
||||
projectBrowser.ExposeEvents(project, renamer);
|
||||
}
|
||||
};
|
||||
|
||||
auto renameObjectPropertyFunctions =
|
||||
[&project, &oldName, &newName,
|
||||
&projectBrowser](const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::NamedPropertyDescriptor &property) {
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
oldName, eventsBasedObject.GetName(),
|
||||
gd::EventsBasedObject::GetPropertyActionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
newName, eventsBasedObject.GetName(),
|
||||
gd::EventsBasedObject::GetPropertyActionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, actionRenamer);
|
||||
|
||||
gd::InstructionsTypeRenamer conditionRenamer =
|
||||
gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
oldName, eventsBasedObject.GetName(),
|
||||
gd::EventsBasedObject::GetPropertyConditionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
newName, eventsBasedObject.GetName(),
|
||||
gd::EventsBasedObject::GetPropertyConditionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, conditionRenamer);
|
||||
|
||||
// Nothing to do for expressions, expressions are not including the
|
||||
// extension name
|
||||
};
|
||||
|
||||
// Order is important: we first rename the expressions then the instructions,
|
||||
// to avoid being unable to fetch the metadata (the types of parameters) of
|
||||
// instructions after they are renamed.
|
||||
@@ -647,16 +520,6 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension(
|
||||
renameEventsFunction(*eventsFunction);
|
||||
}
|
||||
}
|
||||
// Behavior expressions
|
||||
for (auto &&eventsBasedBehavior :
|
||||
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
|
||||
auto &behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
|
||||
for (auto &&eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
|
||||
if (eventsFunction->IsExpression()) {
|
||||
renameBehaviorEventsFunction(*eventsBasedBehavior, *eventsFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Free instructions
|
||||
for (auto &&eventsFunction :
|
||||
@@ -666,73 +529,20 @@ void WholeProjectRefactorer::RenameEventsFunctionsExtension(
|
||||
}
|
||||
}
|
||||
|
||||
// Behavior instructions
|
||||
for (auto &&eventsBasedBehavior :
|
||||
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
|
||||
auto &behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
|
||||
for (auto &&eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
|
||||
if (eventsFunction->IsAction() || eventsFunction->IsCondition()) {
|
||||
renameBehaviorEventsFunction(*eventsBasedBehavior, *eventsFunction);
|
||||
}
|
||||
}
|
||||
WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, oldName, newName, *eventsBasedBehavior,
|
||||
eventsBasedBehavior->GetName(), eventsBasedBehavior->GetName(),
|
||||
projectBrowser);
|
||||
}
|
||||
|
||||
// Behavior properties
|
||||
for (auto &&eventsBasedBehavior :
|
||||
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
|
||||
for (auto &&propertyDescriptor :
|
||||
eventsBasedBehavior->GetPropertyDescriptors().GetInternalVector()) {
|
||||
renameBehaviorPropertyFunctions(*eventsBasedBehavior,
|
||||
*propertyDescriptor);
|
||||
}
|
||||
for (auto &&propertyDescriptor :
|
||||
eventsBasedBehavior->GetSharedPropertyDescriptors()
|
||||
.GetInternalVector()) {
|
||||
renameBehaviorSharedPropertyFunctions(*eventsBasedBehavior,
|
||||
*propertyDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
// Object instructions
|
||||
for (auto &&eventsBasedObject :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
|
||||
auto &objectEventsFunctions = eventsBasedObject->GetEventsFunctions();
|
||||
for (auto &&eventsFunction : objectEventsFunctions.GetInternalVector()) {
|
||||
if (eventsFunction->IsAction() || eventsFunction->IsCondition()) {
|
||||
renameObjectEventsFunction(*eventsBasedObject, *eventsFunction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Object properties
|
||||
for (auto &&eventsBasedObject :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
|
||||
auto &objectProperties = eventsBasedObject->GetPropertyDescriptors();
|
||||
for (auto &&propertyDescriptor : objectProperties.GetInternalVector()) {
|
||||
renameObjectPropertyFunctions(*eventsBasedObject, *propertyDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, rename behaviors used in objects
|
||||
for (auto &&eventsBasedBehavior :
|
||||
eventsFunctionsExtension.GetEventsBasedBehaviors().GetInternalVector()) {
|
||||
DoRenameBehavior(project,
|
||||
gd::PlatformExtension::GetBehaviorFullType(
|
||||
oldName, eventsBasedBehavior->GetName()),
|
||||
gd::PlatformExtension::GetBehaviorFullType(
|
||||
newName, eventsBasedBehavior->GetName()),
|
||||
projectBrowser);
|
||||
}
|
||||
|
||||
// Finally, rename custom objects type
|
||||
for (auto &&eventsBasedObject :
|
||||
eventsFunctionsExtension.GetEventsBasedObjects().GetInternalVector()) {
|
||||
DoRenameObject(project,
|
||||
gd::PlatformExtension::GetObjectFullType(
|
||||
oldName, eventsBasedObject->GetName()),
|
||||
gd::PlatformExtension::GetObjectFullType(
|
||||
newName, eventsBasedObject->GetName()),
|
||||
projectBrowser);
|
||||
WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
project, oldName, newName, *eventsBasedObject,
|
||||
eventsBasedObject->GetName(), eventsBasedObject->GetName(),
|
||||
projectBrowser);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,6 +550,19 @@ void WholeProjectRefactorer::RenameEventsFunction(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldFunctionName, const gd::String &newFunctionName) {
|
||||
WholeProjectRefactorer::MoveEventsFunction(
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
eventsFunctionsExtension.GetName(),
|
||||
eventsFunctionsExtension.GetName(),
|
||||
oldFunctionName, newFunctionName);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::MoveEventsFunction(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::String &oldFunctionName, const gd::String &newFunctionName) {
|
||||
const auto &eventsFunctions = eventsFunctionsExtension.GetEventsFunctions();
|
||||
if (!eventsFunctions.HasEventsFunctionNamed(oldFunctionName))
|
||||
return;
|
||||
@@ -751,9 +574,9 @@ void WholeProjectRefactorer::RenameEventsFunction(
|
||||
DoRenameEventsFunction(
|
||||
project, eventsFunction,
|
||||
gd::PlatformExtension::GetEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldFunctionName),
|
||||
oldExtensionName, oldFunctionName),
|
||||
gd::PlatformExtension::GetEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newFunctionName),
|
||||
newExtensionName, newFunctionName),
|
||||
wholeProjectExposer);
|
||||
|
||||
if (eventsFunction.GetFunctionType() ==
|
||||
@@ -1500,8 +1323,9 @@ void WholeProjectRefactorer::UpdateBehaviorNameInEventsBasedBehavior(
|
||||
const gd::String &sourceBehaviorName) {
|
||||
const EventBasedBehaviorBrowser eventBasedBehaviorExposer(
|
||||
eventsFunctionsExtension, eventsBasedBehavior);
|
||||
WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
project, eventsFunctionsExtension, eventsBasedBehavior,
|
||||
WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, eventsFunctionsExtension.GetName(),
|
||||
eventsFunctionsExtension.GetName(), eventsBasedBehavior,
|
||||
sourceBehaviorName, eventsBasedBehavior.GetName(),
|
||||
eventBasedBehaviorExposer);
|
||||
}
|
||||
@@ -1519,21 +1343,44 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
}
|
||||
auto &eventsBasedBehavior = eventsBasedBehaviors.Get(oldBehaviorName);
|
||||
const WholeProjectBrowser projectBrowser;
|
||||
WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
project, eventsFunctionsExtension, eventsBasedBehavior, oldBehaviorName,
|
||||
WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, eventsFunctionsExtension.GetName(),
|
||||
eventsFunctionsExtension.GetName(), eventsBasedBehavior, oldBehaviorName,
|
||||
newBehaviorName, projectBrowser);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
void WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::String &oldBehaviorName, const gd::String &newBehaviorName) {
|
||||
auto &eventsBasedBehaviors =
|
||||
eventsFunctionsExtension.GetEventsBasedBehaviors();
|
||||
if (!eventsBasedBehaviors.Has(oldBehaviorName)) {
|
||||
gd::LogWarning("Warning, " + oldBehaviorName +
|
||||
" was not found when calling RenameEventsBasedBehavior.");
|
||||
return;
|
||||
}
|
||||
auto &eventsBasedBehavior = eventsBasedBehaviors.Get(oldBehaviorName);
|
||||
const WholeProjectBrowser projectBrowser;
|
||||
WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, oldExtensionName,
|
||||
newExtensionName, eventsBasedBehavior, oldBehaviorName,
|
||||
newBehaviorName, projectBrowser);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
gd::Project &project,
|
||||
const gd::String &oldExtensionName,
|
||||
const gd::String &newExtensionName,
|
||||
const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::String &oldBehaviorName,
|
||||
const gd::String &newBehaviorName,
|
||||
const gd::ProjectBrowser &projectBrowser) {
|
||||
auto renameBehaviorEventsFunction =
|
||||
[&project, &eventsFunctionsExtension, &oldBehaviorName,
|
||||
&newBehaviorName, &projectBrowser](const gd::EventsFunction &eventsFunction) {
|
||||
[&project, &oldExtensionName, &newExtensionName, &oldBehaviorName,
|
||||
&newBehaviorName,
|
||||
&projectBrowser](const gd::EventsFunction &eventsFunction) {
|
||||
if (eventsFunction.IsExpression()) {
|
||||
// Nothing to do, expressions are not including the name of the
|
||||
// behavior
|
||||
@@ -1542,36 +1389,36 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldBehaviorName,
|
||||
oldExtensionName, oldBehaviorName,
|
||||
eventsFunction.GetName()),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newBehaviorName,
|
||||
newExtensionName, newBehaviorName,
|
||||
eventsFunction.GetName()));
|
||||
projectBrowser.ExposeEvents(project, renamer);
|
||||
}
|
||||
};
|
||||
|
||||
auto renameBehaviorProperty = [&project, &eventsFunctionsExtension,
|
||||
auto renameBehaviorProperty = [&project, &oldExtensionName, &newExtensionName,
|
||||
&oldBehaviorName, &newBehaviorName, &projectBrowser](
|
||||
const gd::NamedPropertyDescriptor
|
||||
&property) {
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldBehaviorName,
|
||||
oldExtensionName, oldBehaviorName,
|
||||
EventsBasedBehavior::GetPropertyActionName(property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newBehaviorName,
|
||||
newExtensionName, newBehaviorName,
|
||||
EventsBasedBehavior::GetPropertyActionName(property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, actionRenamer);
|
||||
|
||||
gd::InstructionsTypeRenamer conditionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldBehaviorName,
|
||||
oldExtensionName, oldBehaviorName,
|
||||
EventsBasedBehavior::GetPropertyConditionName(property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newBehaviorName,
|
||||
newExtensionName, newBehaviorName,
|
||||
EventsBasedBehavior::GetPropertyConditionName(property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, conditionRenamer);
|
||||
|
||||
@@ -1580,16 +1427,16 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
};
|
||||
|
||||
auto renameBehaviorSharedProperty =
|
||||
[&project, &eventsFunctionsExtension, &oldBehaviorName,
|
||||
[&project, &oldExtensionName, &newExtensionName, &oldBehaviorName,
|
||||
&newBehaviorName, &projectBrowser](const gd::NamedPropertyDescriptor &property) {
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldBehaviorName,
|
||||
oldExtensionName, oldBehaviorName,
|
||||
EventsBasedBehavior::GetSharedPropertyActionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newBehaviorName,
|
||||
newExtensionName, newBehaviorName,
|
||||
EventsBasedBehavior::GetSharedPropertyActionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, actionRenamer);
|
||||
@@ -1598,11 +1445,11 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldBehaviorName,
|
||||
oldExtensionName, oldBehaviorName,
|
||||
EventsBasedBehavior::GetSharedPropertyConditionName(
|
||||
property.GetName())),
|
||||
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newBehaviorName,
|
||||
newExtensionName, newBehaviorName,
|
||||
EventsBasedBehavior::GetSharedPropertyConditionName(
|
||||
property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, conditionRenamer);
|
||||
@@ -1619,6 +1466,8 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
// Behavior expressions
|
||||
for (auto &&eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
|
||||
if (eventsFunction->IsExpression()) {
|
||||
// This does nothing in practice.
|
||||
// Expressions are not including the name of the behavior.
|
||||
renameBehaviorEventsFunction(*eventsFunction);
|
||||
}
|
||||
}
|
||||
@@ -1642,9 +1491,9 @@ void WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
|
||||
DoRenameBehavior(project,
|
||||
gd::PlatformExtension::GetBehaviorFullType(
|
||||
eventsFunctionsExtension.GetName(), oldBehaviorName),
|
||||
oldExtensionName, oldBehaviorName),
|
||||
gd::PlatformExtension::GetBehaviorFullType(
|
||||
eventsFunctionsExtension.GetName(), newBehaviorName),
|
||||
newExtensionName, newBehaviorName),
|
||||
projectBrowser);
|
||||
}
|
||||
|
||||
@@ -1655,8 +1504,9 @@ void WholeProjectRefactorer::UpdateObjectNameInEventsBasedObject(
|
||||
const gd::String &sourceObjectName) {
|
||||
const EventBasedObjectBrowser eventBasedObjectBrowser(
|
||||
eventsFunctionsExtension, eventsBasedObject);
|
||||
WholeProjectRefactorer::RenameEventsBasedObject(
|
||||
project, eventsFunctionsExtension, eventsBasedObject,
|
||||
WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
project, eventsFunctionsExtension.GetName(),
|
||||
eventsFunctionsExtension.GetName(), eventsBasedObject,
|
||||
sourceObjectName, eventsBasedObject.GetName(),
|
||||
eventBasedObjectBrowser);
|
||||
}
|
||||
@@ -1665,6 +1515,18 @@ void WholeProjectRefactorer::RenameEventsBasedObject(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldObjectName, const gd::String &newObjectName) {
|
||||
WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
project,
|
||||
eventsFunctionsExtension,
|
||||
eventsFunctionsExtension.GetName(), eventsFunctionsExtension.GetName(),
|
||||
oldObjectName, newObjectName);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::String &oldObjectName, const gd::String &newObjectName) {
|
||||
auto &eventsBasedObjects = eventsFunctionsExtension.GetEventsBasedObjects();
|
||||
if (!eventsBasedObjects.Has(oldObjectName)) {
|
||||
gd::LogWarning("Warning, " + oldObjectName +
|
||||
@@ -1673,19 +1535,20 @@ void WholeProjectRefactorer::RenameEventsBasedObject(
|
||||
}
|
||||
auto &eventsBasedObject = eventsBasedObjects.Get(oldObjectName);
|
||||
const WholeProjectBrowser projectBrowser;
|
||||
WholeProjectRefactorer::RenameEventsBasedObject(
|
||||
project, eventsFunctionsExtension, eventsBasedObject, oldObjectName,
|
||||
WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
project, oldExtensionName,
|
||||
newExtensionName, eventsBasedObject, oldObjectName,
|
||||
newObjectName, projectBrowser);
|
||||
}
|
||||
|
||||
void WholeProjectRefactorer::RenameEventsBasedObject(
|
||||
void WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::String &oldObjectName, const gd::String &newObjectName,
|
||||
const gd::ProjectBrowser &projectBrowser) {
|
||||
auto renameObjectEventsFunction =
|
||||
[&project, &eventsFunctionsExtension, &oldObjectName, &newObjectName,
|
||||
[&project, &oldExtensionName, &newExtensionName, &oldObjectName, &newObjectName,
|
||||
&projectBrowser](const gd::EventsFunction &eventsFunction) {
|
||||
if (eventsFunction.IsExpression()) {
|
||||
// Nothing to do, expressions are not including the name of the
|
||||
@@ -1695,35 +1558,35 @@ void WholeProjectRefactorer::RenameEventsBasedObject(
|
||||
gd::InstructionsTypeRenamer renamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldObjectName,
|
||||
oldExtensionName, oldObjectName,
|
||||
eventsFunction.GetName()),
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newObjectName,
|
||||
newExtensionName, newObjectName,
|
||||
eventsFunction.GetName()));
|
||||
projectBrowser.ExposeEvents(project, renamer);
|
||||
}
|
||||
};
|
||||
|
||||
auto renameObjectProperty = [&project, &eventsFunctionsExtension,
|
||||
auto renameObjectProperty = [&project, &oldExtensionName, &newExtensionName,
|
||||
&oldObjectName, &newObjectName, &projectBrowser](
|
||||
const gd::NamedPropertyDescriptor &property) {
|
||||
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldObjectName,
|
||||
oldExtensionName, oldObjectName,
|
||||
EventsBasedObject::GetPropertyActionName(property.GetName())),
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newObjectName,
|
||||
newExtensionName, newObjectName,
|
||||
EventsBasedObject::GetPropertyActionName(property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, actionRenamer);
|
||||
|
||||
gd::InstructionsTypeRenamer conditionRenamer = gd::InstructionsTypeRenamer(
|
||||
project,
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), oldObjectName,
|
||||
oldExtensionName, oldObjectName,
|
||||
EventsBasedObject::GetPropertyConditionName(property.GetName())),
|
||||
gd::PlatformExtension::GetObjectEventsFunctionFullType(
|
||||
eventsFunctionsExtension.GetName(), newObjectName,
|
||||
newExtensionName, newObjectName,
|
||||
EventsBasedObject::GetPropertyConditionName(property.GetName())));
|
||||
projectBrowser.ExposeEvents(project, conditionRenamer);
|
||||
|
||||
@@ -1758,9 +1621,9 @@ void WholeProjectRefactorer::RenameEventsBasedObject(
|
||||
|
||||
DoRenameObject(project,
|
||||
gd::PlatformExtension::GetObjectFullType(
|
||||
eventsFunctionsExtension.GetName(), oldObjectName),
|
||||
oldExtensionName, oldObjectName),
|
||||
gd::PlatformExtension::GetObjectFullType(
|
||||
eventsFunctionsExtension.GetName(), newObjectName),
|
||||
newExtensionName, newObjectName),
|
||||
projectBrowser);
|
||||
}
|
||||
|
||||
@@ -1891,7 +1754,7 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInScene(
|
||||
// Rename object in the current layout
|
||||
gd::EventsRefactorer::RenameObjectInEvents(
|
||||
project.GetCurrentPlatform(), projectScopedContainers, layout.GetEvents(),
|
||||
layout.GetObjects(), oldName, newName);
|
||||
targetedObjectsContainer, oldName, newName);
|
||||
|
||||
// Object groups can't have instances or be in other groups
|
||||
if (!isObjectGroup) {
|
||||
@@ -1908,7 +1771,7 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInScene(
|
||||
auto &externalEvents = project.GetExternalEvents(externalEventsName);
|
||||
gd::EventsRefactorer::RenameObjectInEvents(
|
||||
project.GetCurrentPlatform(), projectScopedContainers,
|
||||
externalEvents.GetEvents(), layout.GetObjects(), oldName, newName);
|
||||
externalEvents.GetEvents(), targetedObjectsContainer, oldName, newName);
|
||||
}
|
||||
|
||||
// Rename object in external layouts
|
||||
|
||||
@@ -125,6 +125,15 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
const gd::String& oldName,
|
||||
const gd::String& newName);
|
||||
|
||||
/**
|
||||
* \brief Refactor extension events after the extension has been pasted or
|
||||
* duplicated.
|
||||
*/
|
||||
static void UpdateExtensionNameInExtension(
|
||||
gd::Project &project,
|
||||
gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &sourceExtensionName);
|
||||
|
||||
/**
|
||||
* \brief Refactor behavior events after the behavior has been placed in a new
|
||||
* extension.
|
||||
@@ -158,6 +167,19 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
const gd::String& oldFunctionName,
|
||||
const gd::String& newFunctionName);
|
||||
|
||||
/**
|
||||
* \brief Refactor the project **before** an events function is moved.
|
||||
*
|
||||
* \warning Do move the specified function after calling this.
|
||||
* This is because the function is expected to have its old name for the
|
||||
* refactoring.
|
||||
*/
|
||||
static void MoveEventsFunction(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::String &oldFunctionName, const gd::String &newFunctionName);
|
||||
|
||||
/**
|
||||
* \brief Refactor the project **before** an events function of a behavior is
|
||||
* renamed.
|
||||
@@ -384,6 +406,19 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
const gd::String& oldBehaviorName,
|
||||
const gd::String& newBehaviorName);
|
||||
|
||||
/**
|
||||
* \brief Refactor the project **before** a behavior is moved.
|
||||
*
|
||||
* \warning Move the specified behavior after calling this.
|
||||
* This is because the behavior is expected to have its old name for the
|
||||
* refactoring.
|
||||
*/
|
||||
static void MoveEventsBasedBehavior(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::String &oldBehaviorName, const gd::String &newBehaviorName);
|
||||
|
||||
/**
|
||||
* \brief Refactor events-based behavior events after the events-based
|
||||
* behavior has been duplicated.
|
||||
@@ -402,10 +437,22 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
* refactoring.
|
||||
*/
|
||||
static void RenameEventsBasedObject(
|
||||
gd::Project& project,
|
||||
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
|
||||
const gd::String& oldObjectName,
|
||||
const gd::String& newObjectName);
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldObjectName, const gd::String &newObjectName);
|
||||
|
||||
/**
|
||||
* \brief Refactor the project **before** an object is moved.
|
||||
*
|
||||
* \warning Do move the specified object after calling this.
|
||||
* This is because the object is expected to have its old name for the
|
||||
* refactoring.
|
||||
*/
|
||||
static void MoveEventsBasedObject(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::String &oldObjectName, const gd::String &newObjectName);
|
||||
|
||||
/**
|
||||
* \brief Refactor events-based object events after the events-based object
|
||||
@@ -770,9 +817,10 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
* This is because the behavior is expected to have its old name for the
|
||||
* refactoring.
|
||||
*/
|
||||
static void RenameEventsBasedBehavior(
|
||||
static void MoveEventsBasedBehavior(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName,
|
||||
const gd::String &newExtensionName,
|
||||
const gd::EventsBasedBehavior &eventsBasedBehavior,
|
||||
const gd::String &oldBehaviorName,
|
||||
const gd::String &newBehaviorName,
|
||||
@@ -785,9 +833,9 @@ class GD_CORE_API WholeProjectRefactorer {
|
||||
* This is because the object is expected to have its old name for the
|
||||
* refactoring.
|
||||
*/
|
||||
static void RenameEventsBasedObject(
|
||||
static void MoveEventsBasedObject(
|
||||
gd::Project &project,
|
||||
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
|
||||
const gd::String &oldExtensionName, const gd::String &newExtensionName,
|
||||
const gd::EventsBasedObject &eventsBasedObject,
|
||||
const gd::String &oldObjectName, const gd::String &newObjectName,
|
||||
const gd::ProjectBrowser &projectBrowser);
|
||||
|
||||
@@ -88,4 +88,10 @@ void EventsBasedObject::UnserializeDefaultVariantFrom(
|
||||
defaultVariant.SetName("");
|
||||
}
|
||||
|
||||
bool EventsBasedObject::IsObjectLifecycleEventsFunction(
|
||||
const gd::String &functionName) {
|
||||
return functionName == "onCreated" || functionName == "doStepPostEvents" ||
|
||||
functionName == "onDestroy" || functionName == "onHotReloading";
|
||||
}
|
||||
|
||||
} // namespace gd
|
||||
|
||||
@@ -391,7 +391,19 @@ class GD_CORE_API EventsBasedObject: public AbstractEventsBasedEntity {
|
||||
void UnserializeDefaultVariantFrom(gd::Project &project,
|
||||
const SerializerElement &element);
|
||||
|
||||
private:
|
||||
/** \name Lifecycle event functions
|
||||
*/
|
||||
///@{
|
||||
/**
|
||||
* Check if the name of the function is the name of a lifecycle function (for
|
||||
* events-based objects), that will be called automatically by the game
|
||||
* engine.
|
||||
*/
|
||||
static bool
|
||||
IsObjectLifecycleEventsFunction(const gd::String &eventsFunctionName);
|
||||
///@}
|
||||
|
||||
private:
|
||||
gd::String defaultName;
|
||||
gd::String assetStoreTag;
|
||||
bool isRenderedIn3D;
|
||||
|
||||
@@ -58,6 +58,7 @@ void EventsFunctionsContainer::AddMissingFunctionsInRootFolder() {
|
||||
}
|
||||
}
|
||||
const gd::String &group = groupSource->GetGroup();
|
||||
// TODO Handle sub-folders
|
||||
auto &folder = !group.empty() ? rootFolder->GetOrCreateChildFolder(group)
|
||||
: *rootFolder;
|
||||
folder.InsertFunction(&function);
|
||||
|
||||
@@ -225,7 +225,7 @@ public:
|
||||
* Don't forget to update me if members were changed!
|
||||
*/
|
||||
void Init(const gd::EventsFunctionsContainer& other) {
|
||||
// The properties folders are not copied.
|
||||
// The function folders are not copied.
|
||||
// It's not an issue because the UI uses the serialization for duplication.
|
||||
rootFolder = gd::make_unique<gd::FunctionFolderOrFunction>("__ROOT");
|
||||
SerializableWithNameList<gd::EventsFunction>::Init(other);
|
||||
|
||||
+54
-111
@@ -448,58 +448,30 @@ gd::String GD_CORE_API GetTypeOfObject(const gd::ObjectsContainer& project,
|
||||
// objects. Search "groups is the intersection of its objects" in the
|
||||
// codebase.
|
||||
else if (searchInGroups) {
|
||||
for (std::size_t i = 0; i < layout.GetObjectGroups().size(); ++i) {
|
||||
if (layout.GetObjectGroups()[i].GetName() == name) {
|
||||
// A group has the name searched
|
||||
// Verifying now that all objects have the same type.
|
||||
|
||||
vector<gd::String> groupsObjects =
|
||||
layout.GetObjectGroups()[i].GetAllObjectsNames();
|
||||
gd::String previousType =
|
||||
groupsObjects.empty()
|
||||
? ""
|
||||
: GetTypeOfObject(project, layout, groupsObjects[0], false);
|
||||
|
||||
for (std::size_t j = 0; j < groupsObjects.size(); ++j) {
|
||||
if (GetTypeOfObject(project, layout, groupsObjects[j], false) !=
|
||||
previousType)
|
||||
return ""; // The group has more than one type.
|
||||
}
|
||||
|
||||
if (!type.empty() && previousType != type)
|
||||
return ""; // The group has objects of different type, so the group
|
||||
// has not any type.
|
||||
|
||||
type = previousType;
|
||||
}
|
||||
// A group of the layout shadows a global group with the same name: only
|
||||
// the most local group is considered (as done for objects, and by
|
||||
// gd::ObjectsContainersList::ExpandObjectName when generating code).
|
||||
const gd::ObjectsContainer* container = nullptr;
|
||||
if (layout.GetObjectGroups().Has(name)) {
|
||||
container = &layout;
|
||||
} else if (project.GetObjectGroups().Has(name)) {
|
||||
container = &project;
|
||||
}
|
||||
for (std::size_t i = 0; i < project.GetObjectGroups().size(); ++i) {
|
||||
if (project.GetObjectGroups()[i].GetName() == name) {
|
||||
// A group has the name searched
|
||||
// Verifying now that all objects have the same type.
|
||||
|
||||
vector<gd::String> groupsObjects =
|
||||
project.GetObjectGroups()[i].GetAllObjectsNames();
|
||||
gd::String previousType =
|
||||
groupsObjects.empty()
|
||||
? ""
|
||||
: GetTypeOfObject(project, layout, groupsObjects[0], false);
|
||||
|
||||
for (std::size_t j = 0; j < groupsObjects.size(); ++j) {
|
||||
if (GetTypeOfObject(project, layout, groupsObjects[j], false) !=
|
||||
previousType)
|
||||
return ""; // The group has more than one type.
|
||||
}
|
||||
|
||||
if (!type.empty() && previousType != type)
|
||||
return ""; // The group has objects of different type, so the group
|
||||
// has not any type.
|
||||
|
||||
type = previousType;
|
||||
if (container) {
|
||||
// Verifying now that all objects have the same type.
|
||||
const vector<gd::String>& groupsObjects =
|
||||
container->GetObjectGroups().Get(name).GetAllObjectsNames();
|
||||
// Empty groups don't have any type.
|
||||
if (groupsObjects.empty()) {
|
||||
return "";
|
||||
}
|
||||
type = GetTypeOfObject(project, layout, groupsObjects[0], false);
|
||||
for (std::size_t j = 1; j < groupsObjects.size(); ++j) {
|
||||
if (GetTypeOfObject(project, layout, groupsObjects[j], false) != type)
|
||||
return ""; // The group has more than one type.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
@@ -828,70 +800,41 @@ GetBehaviorsOfObject(const gd::ObjectsContainer& project,
|
||||
// Currently, a group is considered as the "intersection" of all of its
|
||||
// objects. Search "groups is the intersection of its objects" in the
|
||||
// codebase.
|
||||
if (searchInGroups) {
|
||||
for (std::size_t i = 0; i < layout.GetObjectGroups().size(); ++i) {
|
||||
if (layout.GetObjectGroups()[i].GetName() == name) {
|
||||
// A group has the name searched
|
||||
// Verifying now that all objects have common behaviors.
|
||||
|
||||
vector<gd::String> groupsObjects =
|
||||
layout.GetObjectGroups()[i].GetAllObjectsNames();
|
||||
for (std::size_t objectIndex = 0; objectIndex < groupsObjects.size();
|
||||
++objectIndex) {
|
||||
auto &objectName = groupsObjects[objectIndex];
|
||||
if (!layout.HasObjectNamed(objectName) &&
|
||||
!project.HasObjectNamed(objectName)) {
|
||||
continue;
|
||||
}
|
||||
// Get behaviors of the object of the group and delete behavior which
|
||||
// are not in commons.
|
||||
vector<gd::String> objectBehaviors =
|
||||
GetBehaviorsOfObject(project, layout, objectName, false);
|
||||
if (!behaviorsAlreadyInserted) {
|
||||
behaviorsAlreadyInserted = true;
|
||||
behaviors = objectBehaviors;
|
||||
} else {
|
||||
for (std::size_t a = 0; a < behaviors.size(); ++a) {
|
||||
if (find(objectBehaviors.begin(),
|
||||
objectBehaviors.end(),
|
||||
behaviors[a]) == objectBehaviors.end()) {
|
||||
behaviors.erase(behaviors.begin() + a);
|
||||
--a;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!behaviorsAlreadyInserted && searchInGroups) {
|
||||
// A group of the layout shadows a global group with the same name: only
|
||||
// the most local group is considered (as done for objects, and by
|
||||
// gd::ObjectsContainersList::ExpandObjectName when generating code).
|
||||
const gd::ObjectsContainer* container = nullptr;
|
||||
if (layout.GetObjectGroups().Has(name)) {
|
||||
container = &layout;
|
||||
} else if (project.GetObjectGroups().Has(name)) {
|
||||
container = &project;
|
||||
}
|
||||
for (std::size_t i = 0; i < project.GetObjectGroups().size(); ++i) {
|
||||
if (project.GetObjectGroups()[i].GetName() == name) {
|
||||
// A group has the name searched
|
||||
// Verifying now that all objects have common behaviors.
|
||||
|
||||
vector<gd::String> groupsObjects =
|
||||
project.GetObjectGroups()[i].GetAllObjectsNames();
|
||||
for (std::size_t objectIndex = 0; objectIndex < groupsObjects.size();
|
||||
++objectIndex) {
|
||||
auto &objectName = groupsObjects[objectIndex];
|
||||
if (!layout.HasObjectNamed(objectName) &&
|
||||
!project.HasObjectNamed(objectName)) {
|
||||
continue;
|
||||
}
|
||||
// Get behaviors of the object of the group and delete behavior which
|
||||
// are not in commons.
|
||||
vector<gd::String> objectBehaviors =
|
||||
GetBehaviorsOfObject(project, layout, objectName, false);
|
||||
if (!behaviorsAlreadyInserted) {
|
||||
behaviorsAlreadyInserted = true;
|
||||
behaviors = objectBehaviors;
|
||||
} else {
|
||||
for (std::size_t a = 0; a < behaviors.size(); ++a) {
|
||||
if (find(objectBehaviors.begin(),
|
||||
objectBehaviors.end(),
|
||||
behaviors[a]) == objectBehaviors.end()) {
|
||||
behaviors.erase(behaviors.begin() + a);
|
||||
--a;
|
||||
}
|
||||
if (container) {
|
||||
// Verifying now that all objects have common behaviors.
|
||||
const vector<gd::String>& groupsObjects =
|
||||
container->GetObjectGroups().Get(name).GetAllObjectsNames();
|
||||
for (std::size_t objectIndex = 0; objectIndex < groupsObjects.size();
|
||||
++objectIndex) {
|
||||
auto& objectName = groupsObjects[objectIndex];
|
||||
if (!layout.HasObjectNamed(objectName) &&
|
||||
!project.HasObjectNamed(objectName)) {
|
||||
continue;
|
||||
}
|
||||
// Get behaviors of the object of the group and delete behavior which
|
||||
// are not in commons.
|
||||
vector<gd::String> objectBehaviors =
|
||||
GetBehaviorsOfObject(project, layout, objectName, false);
|
||||
if (!behaviorsAlreadyInserted) {
|
||||
behaviorsAlreadyInserted = true;
|
||||
behaviors = objectBehaviors;
|
||||
} else {
|
||||
for (std::size_t a = 0; a < behaviors.size(); ++a) {
|
||||
if (find(objectBehaviors.begin(),
|
||||
objectBehaviors.end(),
|
||||
behaviors[a]) == objectBehaviors.end()) {
|
||||
behaviors.erase(behaviors.begin() + a);
|
||||
--a;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,6 +454,8 @@ std::vector<gd::String> GetHiddenLayers(const Layout& layout);
|
||||
* \note If a group contains only objects of a same type, then the group has
|
||||
* this type. Otherwise, it is considered as an object without any specific
|
||||
* type.
|
||||
* \note A group of the layout shadows a global group with the same name:
|
||||
* only the group of the layout is considered.
|
||||
* \deprecated Use gd::ObjectsContainersList::GetTypeOfObject instead.
|
||||
*
|
||||
* @return Type of the object/group.
|
||||
@@ -517,6 +519,8 @@ gd::String GD_CORE_API GetTypeOfBehavior(const ObjectsContainer& game,
|
||||
* \brief Get behaviors of an object/group
|
||||
* \note The behaviors of a group are the behaviors which are found in common
|
||||
* when looking all the objects of the group.
|
||||
* \note A group of the layout shadows a global group with the same name:
|
||||
* only the group of the layout is considered.
|
||||
*
|
||||
* @return Vector containing names of behaviors
|
||||
*/
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
namespace gd {
|
||||
|
||||
std::size_t Object::nameGeneration = 0;
|
||||
|
||||
Object::~Object() {}
|
||||
|
||||
Object::Object(const gd::String& name_,
|
||||
@@ -47,7 +49,7 @@ void Object::Init(const gd::Object& object) {
|
||||
|
||||
void Object::CopyWithoutConfiguration(const gd::Object& object) {
|
||||
persistentUuid = object.persistentUuid;
|
||||
name = object.name;
|
||||
SetName(object.name);
|
||||
assetStoreId = object.assetStoreId;
|
||||
objectVariables = object.objectVariables;
|
||||
effectsContainer = object.effectsContainer;
|
||||
@@ -95,7 +97,7 @@ void Object::UnserializeFrom(gd::Project& project,
|
||||
|
||||
SetType(element.GetStringAttribute("type"));
|
||||
assetStoreId = element.GetStringAttribute("assetStoreId");
|
||||
name = element.GetStringAttribute("name", name, "nom");
|
||||
SetName(element.GetStringAttribute("name", name, "nom"));
|
||||
resourcesPreloading = element.GetStringAttribute("resourcesPreloading", "with-scene");
|
||||
|
||||
objectVariables.UnserializeFrom(
|
||||
|
||||
@@ -100,7 +100,18 @@ class GD_CORE_API Object {
|
||||
|
||||
/** \brief Change the name of the object with the name passed as parameter.
|
||||
*/
|
||||
void SetName(const gd::String& name_) { name = name_; };
|
||||
void SetName(const gd::String& name_) {
|
||||
name = name_;
|
||||
++nameGeneration;
|
||||
};
|
||||
|
||||
/**
|
||||
* \brief Return a counter incremented each time any object is renamed.
|
||||
*
|
||||
* Used by gd::ObjectsContainer to know when its index of objects by name
|
||||
* must be rebuilt, so that renaming an object (from anywhere) is always safe.
|
||||
*/
|
||||
static std::size_t GetNameGeneration() { return nameGeneration; }
|
||||
|
||||
/** \brief Return the name of the object.
|
||||
*/
|
||||
@@ -298,6 +309,8 @@ class GD_CORE_API Object {
|
||||
/** When set to `"manually"`, its resources are not preloaded with the scene. */
|
||||
gd::String resourcesPreloading = "with-scene";
|
||||
|
||||
static std::size_t nameGeneration; ///< See GetNameGeneration.
|
||||
|
||||
/**
|
||||
* Initialize object using another object. Used by copy-ctor and assign-op.
|
||||
* Don't forget to update me if members were changed!
|
||||
|
||||
@@ -37,6 +37,7 @@ ObjectsContainer& ObjectsContainer::operator=(const ObjectsContainer& other) {
|
||||
void ObjectsContainer::Init(const gd::ObjectsContainer& other) {
|
||||
sourceType = other.sourceType;
|
||||
initialObjects = gd::Clone(other.initialObjects);
|
||||
InvalidateObjectsByName();
|
||||
objectGroups = other.objectGroups;
|
||||
// The objects folders are not copied.
|
||||
// It's not an issue because the UI uses the serialization for duplication.
|
||||
@@ -84,28 +85,34 @@ void ObjectsContainer::UnserializeObjectsFrom(
|
||||
std::cout << "WARNING: Unknown object type \"" << type << "\""
|
||||
<< std::endl;
|
||||
}
|
||||
InvalidateObjectsByName();
|
||||
}
|
||||
|
||||
gd::Object* ObjectsContainer::FindObject(const gd::String& name) const {
|
||||
const std::size_t nameGeneration = gd::Object::GetNameGeneration();
|
||||
if (!objectsByNameUpToDate || objectsByNameGeneration != nameGeneration) {
|
||||
objectsByName.clear();
|
||||
for (const auto& object : initialObjects) {
|
||||
// If several objects have the same name, the first one wins (as it
|
||||
// would with a linear search).
|
||||
objectsByName.emplace(object->GetName(), object.get());
|
||||
}
|
||||
objectsByNameGeneration = nameGeneration;
|
||||
objectsByNameUpToDate = true;
|
||||
}
|
||||
|
||||
auto it = objectsByName.find(name);
|
||||
return it == objectsByName.end() ? nullptr : it->second;
|
||||
}
|
||||
|
||||
bool ObjectsContainer::HasObjectNamed(const gd::String& name) const {
|
||||
return (find_if(initialObjects.begin(),
|
||||
initialObjects.end(),
|
||||
[&](const std::unique_ptr<gd::Object>& object) {
|
||||
return object->GetName() == name;
|
||||
}) != initialObjects.end());
|
||||
return FindObject(name) != nullptr;
|
||||
}
|
||||
gd::Object& ObjectsContainer::GetObject(const gd::String& name) {
|
||||
return *(*find_if(initialObjects.begin(),
|
||||
initialObjects.end(),
|
||||
[&](const std::unique_ptr<gd::Object>& object) {
|
||||
return object->GetName() == name;
|
||||
}));
|
||||
return *FindObject(name);
|
||||
}
|
||||
const gd::Object& ObjectsContainer::GetObject(const gd::String& name) const {
|
||||
return *(*find_if(initialObjects.begin(),
|
||||
initialObjects.end(),
|
||||
[&](const std::unique_ptr<gd::Object>& object) {
|
||||
return object->GetName() == name;
|
||||
}));
|
||||
return *FindObject(name);
|
||||
}
|
||||
gd::Object& ObjectsContainer::GetObject(std::size_t index) {
|
||||
return *initialObjects[index];
|
||||
@@ -130,6 +137,7 @@ gd::Object& ObjectsContainer::InsertNewObject(const gd::Project& project,
|
||||
position < initialObjects.size() ? initialObjects.begin() + position
|
||||
: initialObjects.end(),
|
||||
project.CreateObject(objectType, name))));
|
||||
InvalidateObjectsByName();
|
||||
|
||||
rootFolder->InsertObject(&newlyCreatedObject);
|
||||
|
||||
@@ -144,6 +152,7 @@ gd::Object& ObjectsContainer::InsertNewObjectInFolder(
|
||||
std::size_t position) {
|
||||
gd::Object& newlyCreatedObject = *(*(initialObjects.insert(
|
||||
initialObjects.end(), project.CreateObject(objectType, name))));
|
||||
InvalidateObjectsByName();
|
||||
|
||||
objectFolderOrObject.InsertObject(&newlyCreatedObject, position);
|
||||
|
||||
@@ -156,6 +165,7 @@ gd::Object& ObjectsContainer::InsertObject(const gd::Object& object,
|
||||
position < initialObjects.size() ? initialObjects.begin() + position
|
||||
: initialObjects.end(),
|
||||
std::unique_ptr<gd::Object>(object.Clone()))));
|
||||
InvalidateObjectsByName();
|
||||
|
||||
return newlyCreatedObject;
|
||||
}
|
||||
@@ -167,6 +177,7 @@ void ObjectsContainer::MoveObject(std::size_t oldIndex, std::size_t newIndex) {
|
||||
std::unique_ptr<gd::Object> object = std::move(initialObjects[oldIndex]);
|
||||
initialObjects.erase(initialObjects.begin() + oldIndex);
|
||||
initialObjects.insert(initialObjects.begin() + newIndex, std::move(object));
|
||||
InvalidateObjectsByName();
|
||||
}
|
||||
|
||||
void ObjectsContainer::RemoveObject(const gd::String& name) {
|
||||
@@ -181,11 +192,13 @@ void ObjectsContainer::RemoveObject(const gd::String& name) {
|
||||
rootFolder->RemoveRecursivelyObjectNamed(name);
|
||||
|
||||
initialObjects.erase(objectIt);
|
||||
InvalidateObjectsByName();
|
||||
}
|
||||
|
||||
void ObjectsContainer::Clear() {
|
||||
rootFolder->Clear();
|
||||
initialObjects.clear();
|
||||
InvalidateObjectsByName();
|
||||
}
|
||||
|
||||
void ObjectsContainer::MoveObjectFolderOrObjectToAnotherContainerInFolder(
|
||||
@@ -205,8 +218,10 @@ void ObjectsContainer::MoveObjectFolderOrObjectToAnotherContainerInFolder(
|
||||
|
||||
std::unique_ptr<gd::Object> object = std::move(*objectIt);
|
||||
initialObjects.erase(objectIt);
|
||||
InvalidateObjectsByName();
|
||||
|
||||
newContainer.initialObjects.push_back(std::move(object));
|
||||
newContainer.InvalidateObjectsByName();
|
||||
|
||||
objectFolderOrObject.GetParent().MoveObjectFolderOrObjectToAnotherFolder(
|
||||
objectFolderOrObject, newParentFolder, newPosition);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
#include "GDCore/Project/MemoryTrackedRegistry.h"
|
||||
#include "GDCore/String.h"
|
||||
#include "GDCore/Project/ObjectGroupsContainer.h"
|
||||
@@ -173,14 +174,10 @@ class GD_CORE_API ObjectsContainer {
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* Provide a raw access to the vector containing the objects
|
||||
*/
|
||||
std::vector<std::unique_ptr<gd::Object> >& GetObjects() {
|
||||
return initialObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a raw access to the vector containing the objects
|
||||
* Provide a read-only access to the vector containing the objects.
|
||||
*
|
||||
* \note Objects must only be added, removed or moved using the methods of
|
||||
* this class, so that the index of objects by name is kept up-to-date.
|
||||
*/
|
||||
const std::vector<std::unique_ptr<gd::Object> >& GetObjects() const {
|
||||
return initialObjects;
|
||||
@@ -255,6 +252,25 @@ class GD_CORE_API ObjectsContainer {
|
||||
std::unique_ptr<gd::ObjectFolderOrObject> rootFolder;
|
||||
gd::MemoryTracked _memoryTracked{this, "ObjectsContainer"};
|
||||
|
||||
/**
|
||||
* Objects indexed by name, for fast lookups. Lazily (re)built by
|
||||
* `FindObject` when marked as outdated (after any change to `initialObjects`)
|
||||
* or when any object was renamed (see gd::Object::GetNameGeneration).
|
||||
*/
|
||||
mutable std::unordered_map<gd::String, gd::Object*> objectsByName;
|
||||
mutable std::size_t objectsByNameGeneration = 0;
|
||||
mutable bool objectsByNameUpToDate = false;
|
||||
|
||||
/**
|
||||
* Return the object called \a name, or nullptr if not found.
|
||||
*/
|
||||
gd::Object* FindObject(const gd::String& name) const;
|
||||
|
||||
/**
|
||||
* Must be called after any change to `initialObjects`.
|
||||
*/
|
||||
void InvalidateObjectsByName() { objectsByNameUpToDate = false; }
|
||||
|
||||
/**
|
||||
* Initialize from another variables container, copying elements. Used by
|
||||
* copy-ctor and assign-op. Don't forget to update me if members were changed!
|
||||
|
||||
@@ -231,7 +231,7 @@ const gd::EventsBasedBehavior& Project::GetEventsBasedBehavior(
|
||||
}
|
||||
|
||||
std::shared_ptr<gd::BaseEvent> Project::CreateEvent(
|
||||
const gd::String& type, const gd::String& platformName) {
|
||||
const gd::String& type, const gd::String& platformName) const {
|
||||
for (std::size_t i = 0; i < platforms.size(); ++i) {
|
||||
if (!platformName.empty() && platforms[i]->GetName() != platformName)
|
||||
continue;
|
||||
|
||||
@@ -569,7 +569,7 @@ class GD_CORE_API Project {
|
||||
* first platform supporting the object is used.
|
||||
*/
|
||||
std::shared_ptr<gd::BaseEvent> CreateEvent(
|
||||
const gd::String& type, const gd::String& platformName = "");
|
||||
const gd::String& type, const gd::String& platformName = "") const;
|
||||
///@}
|
||||
|
||||
/** \name Layouts management
|
||||
|
||||
@@ -18,7 +18,7 @@ PropertiesContainer::PropertiesContainer(const PropertiesContainer &other)
|
||||
: properties(other.properties), owner(other.owner) {
|
||||
// The properties folders are not copied.
|
||||
// It's not an issue because the UI uses the serialization for duplication.
|
||||
rootFolder = gd::make_unique<gd::PropertyFolderOrProperty>("__ROOT");
|
||||
rootFolder = gd::make_unique<gd::PropertyFolderOrProperty>("__ROOT");;
|
||||
}
|
||||
|
||||
PropertiesContainer &
|
||||
|
||||
@@ -324,12 +324,10 @@ void Variable::UnserializeFrom(const SerializerElement& element) {
|
||||
} else if (type == Type::Boolean) {
|
||||
SetBool(element.GetBoolAttribute("value", false, "Value"));
|
||||
}
|
||||
} else {
|
||||
} else if (type == Type::Structure || type == Type::Array) {
|
||||
const SerializerElement& childrenElement =
|
||||
element.GetChild("children", 0, "Children");
|
||||
childrenElement.ConsiderAsArrayOf("variable", "Variable");
|
||||
if (childrenElement.GetChildrenCount() == 0) return;
|
||||
|
||||
for (int i = 0; i < childrenElement.GetChildrenCount(); ++i) {
|
||||
const SerializerElement& childElement = childrenElement.GetChild(i);
|
||||
if (type == Type::Structure) {
|
||||
@@ -340,6 +338,10 @@ void Variable::UnserializeFrom(const SerializerElement& element) {
|
||||
PushNew().UnserializeFrom(childElement);
|
||||
}
|
||||
}
|
||||
// The editor-only "mixed values" marker is restored last, and whatever the
|
||||
// type is: a variable marked as having mixed values has no children to read
|
||||
// (they are cleared by MarkAsMixedValues), and a variable with entirely
|
||||
// mixed types ("mixed" type) has no value and no children either.
|
||||
if (element.GetBoolAttribute("hasMixedValues", false)) {
|
||||
MarkAsMixedValues();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival ([email protected]). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests covering common features of GDevelop Core.
|
||||
*/
|
||||
#include "GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.h"
|
||||
#include "DummyPlatform.h"
|
||||
#include "GDCore/Events/Builtin/CommentEvent.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/IDE/WholeProjectRefactorer.h"
|
||||
#include "GDCore/Project/EventsBasedBehavior.h"
|
||||
#include "GDCore/Project/EventsFunctionsExtension.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/String.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
TEST_CASE("ChildObjectForwardFunctionGenerator", "[common]") {
|
||||
SECTION("Can generate a function to forward an action") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &extension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyParentEventsBasedObject", 0);
|
||||
auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyChildEventsBasedObject", 0);
|
||||
parentEventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject",
|
||||
0);
|
||||
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MyFunction", 0);
|
||||
childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action);
|
||||
childFunction.SetFullName("My function");
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
{
|
||||
auto ¶meter =
|
||||
childFunction.GetParameters().AddNewParameter("MyStringParameter");
|
||||
parameter.SetType("string");
|
||||
}
|
||||
{
|
||||
auto ¶meter =
|
||||
childFunction.GetParameters().AddNewParameter("MyNumberParameter");
|
||||
parameter.SetType("number");
|
||||
}
|
||||
|
||||
gd::ChildObjectForwardFunctionGenerator::
|
||||
GenerateChildObjectForwardFunctions(
|
||||
project, extension, parentEventsBasedObject, "MyChildObject");
|
||||
|
||||
REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MyFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MyFunction");
|
||||
|
||||
REQUIRE(parentFunction.GetFullName() == "My function");
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() ==
|
||||
"string");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() ==
|
||||
"number");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1);
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0);
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 1);
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() ==
|
||||
"MyEventsExtension::MyChildEventsBasedObject::MyFunction");
|
||||
REQUIRE(action.GetParametersCount() == 3);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject");
|
||||
REQUIRE(action.GetParameter(1).GetPlainString() == "MyStringParameter");
|
||||
REQUIRE(action.GetParameter(2).GetPlainString() == "MyNumberParameter");
|
||||
}
|
||||
|
||||
SECTION("Can generate a function to forward a condition") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &extension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyParentEventsBasedObject", 0);
|
||||
auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyChildEventsBasedObject", 0);
|
||||
|
||||
parentEventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject",
|
||||
0);
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MyFunction", 0);
|
||||
childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Condition);
|
||||
childFunction.SetFullName("My function");
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
{
|
||||
auto ¶meter =
|
||||
childFunction.GetParameters().AddNewParameter("MyStringParameter");
|
||||
parameter.SetType("string");
|
||||
}
|
||||
{
|
||||
auto ¶meter =
|
||||
childFunction.GetParameters().AddNewParameter("MyNumberParameter");
|
||||
parameter.SetType("number");
|
||||
}
|
||||
|
||||
gd::ChildObjectForwardFunctionGenerator::
|
||||
GenerateChildObjectForwardFunctions(
|
||||
project, extension, parentEventsBasedObject, "MyChildObject");
|
||||
|
||||
REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MyFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MyFunction");
|
||||
|
||||
REQUIRE(parentFunction.GetFullName() == "My function");
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() ==
|
||||
"string");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() ==
|
||||
"number");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1);
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 1);
|
||||
auto &condition = event.GetInstructionList("conditions")->Get(0);
|
||||
REQUIRE(condition.GetType() ==
|
||||
"MyEventsExtension::MyChildEventsBasedObject::MyFunction");
|
||||
REQUIRE(condition.GetParametersCount() == 3);
|
||||
REQUIRE(condition.GetParameter(0).GetPlainString() == "MyChildObject");
|
||||
REQUIRE(condition.GetParameter(1).GetPlainString() == "MyStringParameter");
|
||||
REQUIRE(condition.GetParameter(2).GetPlainString() == "MyNumberParameter");
|
||||
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 1);
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() == "SetReturnBoolean");
|
||||
REQUIRE(action.GetParametersCount() == 1);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() == "True");
|
||||
}
|
||||
|
||||
SECTION("Can generate a function to forward an expression") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &extension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyParentEventsBasedObject", 0);
|
||||
auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyChildEventsBasedObject", 0);
|
||||
|
||||
parentEventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject",
|
||||
0);
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MyFunction", 0);
|
||||
childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Expression);
|
||||
childFunction.SetFullName("My function");
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
{
|
||||
auto ¶meter =
|
||||
childFunction.GetParameters().AddNewParameter("MyStringParameter");
|
||||
parameter.SetType("string");
|
||||
}
|
||||
{
|
||||
auto ¶meter =
|
||||
childFunction.GetParameters().AddNewParameter("MyNumberParameter");
|
||||
parameter.SetType("number");
|
||||
}
|
||||
|
||||
gd::ChildObjectForwardFunctionGenerator::
|
||||
GenerateChildObjectForwardFunctions(
|
||||
project, extension, parentEventsBasedObject, "MyChildObject");
|
||||
|
||||
REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MyFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MyFunction");
|
||||
|
||||
REQUIRE(parentFunction.GetFullName() == "My function");
|
||||
REQUIRE(parentFunction.GetFunctionType() ==
|
||||
gd::EventsFunction::FunctionType::Expression);
|
||||
REQUIRE(parentFunction.GetExpressionType().IsNumber());
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() ==
|
||||
"string");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() ==
|
||||
"number");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1);
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0);
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 1);
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() == "SetReturnNumber");
|
||||
REQUIRE(action.GetParametersCount() == 1);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() ==
|
||||
"MyChildObject.MyFunction(MyStringParameter, MyNumberParameter)");
|
||||
}
|
||||
|
||||
SECTION("Can generate a function to forward a getter and setter") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &extension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyParentEventsBasedObject", 0);
|
||||
auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyChildEventsBasedObject", 0);
|
||||
|
||||
parentEventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject",
|
||||
0);
|
||||
{
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MyGetterFunction", 0);
|
||||
childFunction.SetFunctionType(
|
||||
gd::EventsFunction::FunctionType::ExpressionAndCondition);
|
||||
childFunction.GetExpressionType().SetName("number");
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
}
|
||||
{
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MySetterFunction", 1);
|
||||
childFunction.SetFunctionType(
|
||||
gd::EventsFunction::FunctionType::ActionWithOperator);
|
||||
childFunction.SetGetterName("MyGetterFunction");
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
}
|
||||
|
||||
gd::ChildObjectForwardFunctionGenerator::
|
||||
GenerateChildObjectForwardFunctions(
|
||||
project, extension, parentEventsBasedObject, "MyChildObject");
|
||||
|
||||
{
|
||||
REQUIRE(
|
||||
parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MyGetterFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MyGetterFunction");
|
||||
REQUIRE(parentFunction.GetFunctionType() ==
|
||||
gd::EventsFunction::FunctionType::ExpressionAndCondition);
|
||||
REQUIRE(parentFunction.GetExpressionType().IsNumber());
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 1);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1);
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0);
|
||||
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 1);
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() == "SetReturnNumber");
|
||||
REQUIRE(action.GetParametersCount() == 1);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() ==
|
||||
"MyChildObject.MyGetterFunction()");
|
||||
}
|
||||
{
|
||||
REQUIRE(
|
||||
parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MySetterFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MySetterFunction");
|
||||
REQUIRE(parentFunction.GetFunctionType() ==
|
||||
gd::EventsFunction::FunctionType::ActionWithOperator);
|
||||
REQUIRE(parentFunction.GetGetterName() == "MyGetterFunction");
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 1);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1);
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0);
|
||||
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 1);
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() ==
|
||||
"MyEventsExtension::MyChildEventsBasedObject::MySetterFunction");
|
||||
REQUIRE(action.GetParametersCount() == 3);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject");
|
||||
REQUIRE(action.GetParameter(1).GetPlainString() == "=");
|
||||
REQUIRE(action.GetParameter(2).GetPlainString() == "Value");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Can generate a function to forward a boolean setter") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &extension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyParentEventsBasedObject", 0);
|
||||
auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyChildEventsBasedObject", 0);
|
||||
|
||||
parentEventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject",
|
||||
0);
|
||||
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MySetterFunction", 1);
|
||||
childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action);
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
{
|
||||
auto ¶meter = childFunction.GetParameters().AddNewParameter("Value");
|
||||
parameter.SetType("yesorno");
|
||||
}
|
||||
|
||||
gd::ChildObjectForwardFunctionGenerator::
|
||||
GenerateChildObjectForwardFunctions(
|
||||
project, extension, parentEventsBasedObject, "MyChildObject");
|
||||
|
||||
REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MySetterFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MySetterFunction");
|
||||
REQUIRE(parentFunction.GetFunctionType() ==
|
||||
gd::EventsFunction::FunctionType::Action);
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 2);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() ==
|
||||
"yesorno");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 2);
|
||||
{
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 1);
|
||||
auto &condition = event.GetInstructionList("conditions")->Get(0);
|
||||
REQUIRE(condition.GetType() == "BooleanVariable");
|
||||
REQUIRE(condition.GetParametersCount() == 3);
|
||||
REQUIRE(condition.GetParameter(0).GetPlainString() == "Value");
|
||||
REQUIRE(condition.GetParameter(1).GetPlainString() == "False");
|
||||
REQUIRE(condition.GetParameter(2).GetPlainString() == "");
|
||||
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 1);
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() ==
|
||||
"MyEventsExtension::MyChildEventsBasedObject::MySetterFunction");
|
||||
REQUIRE(action.GetParametersCount() == 2);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject");
|
||||
REQUIRE(action.GetParameter(1).GetPlainString() == "no");
|
||||
}
|
||||
{
|
||||
auto &event = parentFunction.GetEvents().GetEvent(1);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 1);
|
||||
auto &condition = event.GetInstructionList("conditions")->Get(0);
|
||||
REQUIRE(condition.GetType() == "BooleanVariable");
|
||||
REQUIRE(condition.GetParametersCount() == 3);
|
||||
REQUIRE(condition.GetParameter(0).GetPlainString() == "Value");
|
||||
REQUIRE(condition.GetParameter(1).GetPlainString() == "True");
|
||||
REQUIRE(condition.GetParameter(2).GetPlainString() == "");
|
||||
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 1);
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() ==
|
||||
"MyEventsExtension::MyChildEventsBasedObject::MySetterFunction");
|
||||
REQUIRE(action.GetParametersCount() == 2);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject");
|
||||
REQUIRE(action.GetParameter(1).GetPlainString() == "yes");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Can generate a todo comment for function with boolean parameters") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &extension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyParentEventsBasedObject", 0);
|
||||
auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyChildEventsBasedObject", 0);
|
||||
|
||||
parentEventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject",
|
||||
0);
|
||||
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MySetterFunction", 1);
|
||||
childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action);
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
{
|
||||
auto ¶meter = childFunction.GetParameters().AddNewParameter("Value");
|
||||
parameter.SetType("yesorno");
|
||||
}
|
||||
{
|
||||
auto ¶meter =
|
||||
childFunction.GetParameters().AddNewParameter("MyNumberParameter");
|
||||
parameter.SetType("number");
|
||||
}
|
||||
|
||||
gd::ChildObjectForwardFunctionGenerator::
|
||||
GenerateChildObjectForwardFunctions(
|
||||
project, extension, parentEventsBasedObject, "MyChildObject");
|
||||
|
||||
REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MySetterFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MySetterFunction");
|
||||
REQUIRE(parentFunction.GetFunctionType() ==
|
||||
gd::EventsFunction::FunctionType::Action);
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 3);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(1).GetType() ==
|
||||
"yesorno");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(2).GetType() ==
|
||||
"number");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1);
|
||||
{
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetType() == "BuiltinCommonInstructions::Comment");
|
||||
auto &comment = dynamic_cast<gd::CommentEvent &>(event);
|
||||
REQUIRE(comment.GetComment() ==
|
||||
"TODO: Please implement this function manually.");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Can generate a function to forward an asynchronous action") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &extension =
|
||||
project.InsertNewEventsFunctionsExtension("MyEventsExtension", 0);
|
||||
auto &parentEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyParentEventsBasedObject", 0);
|
||||
auto &childEventsBasedObject = extension.GetEventsBasedObjects().InsertNew(
|
||||
"MyChildEventsBasedObject", 0);
|
||||
parentEventsBasedObject.GetObjects().InsertNewObject(
|
||||
project, "MyEventsExtension::MyChildEventsBasedObject", "MyChildObject",
|
||||
0);
|
||||
|
||||
auto &childFunction =
|
||||
childEventsBasedObject.GetEventsFunctions().InsertNewEventsFunction(
|
||||
"MyFunction", 0);
|
||||
childFunction.SetFunctionType(gd::EventsFunction::FunctionType::Action);
|
||||
childFunction.SetAsync(true);
|
||||
gd::WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
|
||||
extension, childEventsBasedObject);
|
||||
|
||||
gd::ChildObjectForwardFunctionGenerator::
|
||||
GenerateChildObjectForwardFunctions(
|
||||
project, extension, parentEventsBasedObject, "MyChildObject");
|
||||
|
||||
REQUIRE(parentEventsBasedObject.GetEventsFunctions().HasEventsFunctionNamed(
|
||||
"MyFunction"));
|
||||
auto &parentFunction =
|
||||
parentEventsBasedObject.GetEventsFunctions().GetEventsFunction(
|
||||
"MyFunction");
|
||||
REQUIRE(parentFunction.GetFunctionType() ==
|
||||
gd::EventsFunction::FunctionType::Action);
|
||||
REQUIRE(parentFunction.IsAsync());
|
||||
REQUIRE(parentFunction.GetParameters().GetParametersCount() == 1);
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetType() ==
|
||||
"object");
|
||||
REQUIRE(parentFunction.GetParameters().GetParameter(0).GetExtraInfo() ==
|
||||
"MyEventsExtension::MyParentEventsBasedObject");
|
||||
|
||||
REQUIRE(parentFunction.GetEvents().GetEventsCount() == 1);
|
||||
auto &event = parentFunction.GetEvents().GetEvent(0);
|
||||
REQUIRE(event.GetInstructionList("conditions")->GetCount() == 0);
|
||||
REQUIRE(event.GetInstructionList("actions")->GetCount() == 2);
|
||||
{
|
||||
auto &action = event.GetInstructionList("actions")->Get(0);
|
||||
REQUIRE(action.GetType() ==
|
||||
"MyEventsExtension::MyChildEventsBasedObject::MyFunction");
|
||||
REQUIRE(action.GetParametersCount() == 1);
|
||||
REQUIRE(action.GetParameter(0).GetPlainString() == "MyChildObject");
|
||||
}
|
||||
{
|
||||
auto &action = event.GetInstructionList("actions")->Get(1);
|
||||
REQUIRE(action.GetType() == "BuiltinAsync::ResolveAsyncEventsFunction");
|
||||
REQUIRE(action.GetParametersCount() == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,13 @@
|
||||
*/
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Behavior.h"
|
||||
#include "GDCore/Project/ObjectConfiguration.h"
|
||||
#include "GDCore/Extensions/Builtin/SpriteExtension/SpriteObject.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Tools/Localization.h"
|
||||
#include "GDCore/Events/Builtin/CommentEvent.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Events/Builtin/ForEachChildVariableEvent.h"
|
||||
#include "GDCore/Events/Builtin/RepeatEvent.h"
|
||||
@@ -176,6 +176,7 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
|
||||
commonInstructionsExtension->AddEvent("Else", "Else event", "", "", "", std::make_shared<gd::ElseEvent>());
|
||||
commonInstructionsExtension->AddEvent("ForEachChildVariable", "For each child variable event", "", "", "", std::make_shared<gd::ForEachChildVariableEvent>());
|
||||
commonInstructionsExtension->AddEvent("Repeat", "Repeat event", "", "", "", std::make_shared<gd::RepeatEvent>());
|
||||
commonInstructionsExtension->AddEvent("Comment", "Comment event", "", "", "", std::make_shared<gd::CommentEvent>());
|
||||
|
||||
std::shared_ptr<gd::PlatformExtension> baseObjectExtension =
|
||||
std::shared_ptr<gd::PlatformExtension>(new gd::PlatformExtension);
|
||||
@@ -527,6 +528,16 @@ void SetupProjectWithDummyPlatform(gd::Project& project,
|
||||
.AddParameter("string", "")
|
||||
.AddParameter("expression", "", "", true)
|
||||
.SetFunctionName("getNumberWith3Params");
|
||||
extension
|
||||
->AddStrExpression(
|
||||
"GetVariableString",
|
||||
"Get string of object variable",
|
||||
"",
|
||||
"",
|
||||
"")
|
||||
.AddParameter("object", _("Object parameter"))
|
||||
.AddParameter("objectvar", _("Variable for object"))
|
||||
.SetFunctionName("getObjectVariableString");
|
||||
extension
|
||||
->AddStrExpression(
|
||||
"GetStringWith2ObjectParamAnd2ObjectVarParam",
|
||||
|
||||
@@ -77,10 +77,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
layout1.GetObjects().InsertNewObject(project, "", "MyObject", 0);
|
||||
myObject.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
|
||||
myObject.GetVariables().InsertNew("MyObjectVariable");
|
||||
myObject.GetVariables()
|
||||
.InsertNew("MyObjectStructureVariable")
|
||||
.GetChild("MyChildStructure")
|
||||
.GetChild("MyChild");
|
||||
myObject.GetVariables().InsertNew("MyObjectStructureVariable").GetChild("MyChild");
|
||||
myObject.GetVariables().Get("MyObjectStructureVariable").GetChild("MyChildStructure").GetChild("MyChild");
|
||||
myObject.GetVariables().Get("MyObjectStructureVariable").GetChild("MyChildStructure").GetChild("MyChildStructure2").GetChild("MyChild");
|
||||
|
||||
auto &myGroup =
|
||||
layout1.GetObjects().GetObjectGroups().InsertNew("MyGroup");
|
||||
@@ -2099,7 +2098,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
}
|
||||
SECTION("in object variable parameter") {
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStructureVariable.MyChild");
|
||||
parser.ParseExpression("MyObjectStructureVariable.MyChild");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "MyObject");
|
||||
@@ -2238,7 +2237,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables (2 levels, child does not exist)") {
|
||||
@@ -2248,7 +2249,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "variable");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables (3 levels, child does not exist)") {
|
||||
@@ -2259,7 +2262,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "variable");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Undeclared legacy scene variables (2 levels, child does not exist)") {
|
||||
@@ -2283,17 +2288,47 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
SECTION("Undeclared legacy object variables (2 levels, child does not exist)") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MyObjectStructureVariable.MyNonExistingChild");
|
||||
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Undeclared legacy object variables (2 levels, child does not exist) in expression") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MyExtension::GetVariableString(MyObject, MyObjectStructureVariable.MyNonExistingChild)");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"string");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Empty group with legacy object variables in expression") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MyExtension::GetVariableString(EmptyGroup, MyObjectVariable)");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"string");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Empty group with legacy object variables (2 levels, child does not exist) in expression") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MyExtension::GetVariableString(EmptyGroup, MyObjectStructureVariable.MyNonExistingChild)");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"string");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Undeclared legacy object variables (3 levels, child does not exist)") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MyObjectStructureVariable.MyNonExistingChild.MyNonExistingChild");
|
||||
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "");
|
||||
node->Visit(validator);
|
||||
@@ -2308,7 +2343,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"objectvar", "MyObject");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Undeclared object variables (3 levels, child does not exist)") {
|
||||
@@ -2319,7 +2356,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"objectvar", "MyObject");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Undeclared object variables in expressions (2 levels, child does not exist)") {
|
||||
@@ -2330,7 +2369,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Undeclared object variables in expressions (3 levels, child does not exist)") {
|
||||
@@ -2342,7 +2383,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables (2 levels, variable and child do not exist)") {
|
||||
@@ -2362,9 +2405,8 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name.");
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables type in expression (1 level)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStructureVariable");
|
||||
SECTION("Invalid scene variables structure in expression (1 level)") {
|
||||
auto node = parser.ParseExpression("MySceneStructureVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
@@ -2374,48 +2416,122 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"You need to specify the name of the child variable to access. For example: `MyVariable.child`.");
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables type in expression (2 levels)") {
|
||||
SECTION("Valid scene variable structure in variable parameter (1 level)") {
|
||||
auto node = parser.ParseExpression("MySceneStructureVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"variable");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables structure in expression (2 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStructureVariable.MyChildStructure");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
// TODO Add a fatal error
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You need to specify the name of the child variable to access. For example: `MyVariable.child`.");
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables type in expression (3 levels)") {
|
||||
SECTION("Valid scene variables structure in variable parameter (2 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStructureVariable.MyChildStructure");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"variable");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Invalid scene variables structure in expression (3 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStructureVariable.MyChildStructure.MyChildStructure2");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
// TODO Add a fatal error
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You need to specify the name of the child variable to access. For example: `MyVariable.child`.");
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables type in expression (1 level)") {
|
||||
SECTION("Valid scene variables structure in variable parameter (3 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MySceneStructureVariable.MyChildStructure.MyChildStructure2");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"variable");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables structure in expression (1 level)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyObject.MyObjectStructureVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
// TODO Add a fatal error
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You need to specify the name of the child variable to access. For example: `MyVariable.child`.");
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables type in expression (2 levels)") {
|
||||
SECTION("Valid object variables structure in variable parameter (1 level)") {
|
||||
auto node = parser.ParseExpression("MyObjectStructureVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables structure in expression (2 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyObject.MyObjectStructureVariable.MyChildStructure");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You need to specify the name of the child variable to access. For example: `MyVariable.child`.");
|
||||
}
|
||||
|
||||
SECTION("Valid object variables structure in variable parameter (2 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyObjectStructureVariable.MyChildStructure");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables structure in expression (3 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyObject.MyObjectStructureVariable.MyChildStructure.MyChildStructure2");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You need to specify the name of the child variable to access. For example: `MyVariable.child`.");
|
||||
}
|
||||
|
||||
SECTION("Valid object variables structure in variable parameter (3 levels)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyObjectStructureVariable.MyChildStructure.MyChildStructure2");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
// TODO Add a fatal error
|
||||
}
|
||||
|
||||
SECTION("Valid object variables (1 level)") {
|
||||
@@ -2459,7 +2575,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
SECTION("Valid object variables (2 levels)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySpriteObject.MyVariable.MyChild");
|
||||
parser.ParseExpression("MyObject.MyObjectStructureVariable.MyChild");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
@@ -2467,7 +2583,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySpriteObject.MyVariable.MyChild + MySpriteObject.MyVariable2");
|
||||
parser.ParseExpression("MyObject.MyObjectStructureVariable.MyChild + MySpriteObject.MyVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
@@ -2475,7 +2591,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
}
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("MySpriteObject.MyVariable[\"MyChild\"] + MySpriteObject.MyVariable2");
|
||||
parser.ParseExpression("MyObject.MyObjectStructureVariable[\"MyChild\"] + MySpriteObject.MyVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
@@ -2499,8 +2615,10 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireNoFatalError(validator);
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"No child variable with this name found.");
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables (1 level, non existing object)") {
|
||||
@@ -2565,17 +2683,50 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables (empty object group)") {
|
||||
{
|
||||
auto node =
|
||||
parser.ParseExpression("EmptyGroup.MyVariable");
|
||||
SECTION("Invalid object variables (empty object group, in expression)") {
|
||||
auto node = parser.ParseExpression("EmptyGroup.MyVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"This group is empty. Add an object to this group first.");
|
||||
}
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"This group is empty. Add an object to this group first.");
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables with children (empty object group, in expression)") {
|
||||
auto node =
|
||||
parser.ParseExpression("EmptyGroup.MyObjectStructureVariable.MyChild");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"number|string");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"This group is empty. Add an object to this group first.");
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables (empty object group)") {
|
||||
auto node = parser.ParseExpression("MyVariable");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "EmptyGroup");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"This group is empty. Add an object to this group first.");
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables with children (empty object group)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyObjectStructureVariable.MyChild");
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "EmptyGroup");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"This group is empty. Add an object to this group first.");
|
||||
}
|
||||
|
||||
SECTION("Invalid object variables (2 levels, bracket accessor)") {
|
||||
@@ -3882,7 +4033,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You entered a number, but this type was expected: variable");
|
||||
"The variable name looks like you're building an expression or a "
|
||||
"formula. You can only use this for structure or arrays, for "
|
||||
"example: Score[3].");
|
||||
}
|
||||
SECTION("string instead") {
|
||||
auto node = parser.ParseExpression("\"text\"");
|
||||
@@ -3892,7 +4045,129 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"You entered a text, but this type was expected: variable");
|
||||
"The variable name looks like you're building an expression or a "
|
||||
"formula. You can only use this for structure or arrays, for "
|
||||
"example: Score[\"Player1\"].");
|
||||
}
|
||||
|
||||
SECTION("Object variable with unary operator") {
|
||||
auto node = parser.ParseExpression("-MyObjectVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "MyObject");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Object variable with operator") {
|
||||
auto node = parser.ParseExpression("MyObjectVariable+MyObjectVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "MyObject");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Object children variable with operator") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MyObjectStructureVariable.MyChild+MyObjectVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "MyObject");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Object variable with unary operator (legacy and extensions)") {
|
||||
auto node = parser.ParseExpression("-MyObjectVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Object variable with operator (legacy and extensions)") {
|
||||
auto node = parser.ParseExpression("MyObjectVariable+MyObjectVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Object children variable with operator (legacy and extensions)") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MyObjectStructureVariable.MyChild+MyObjectVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Variable with unary operator") {
|
||||
auto node = parser.ParseExpression("-MySceneVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"variable");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Variable with operator") {
|
||||
auto node = parser.ParseExpression("MySceneVariable+MySceneVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"variable");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
|
||||
SECTION("Children variable with operator") {
|
||||
auto node = parser.ParseExpression(
|
||||
"MySceneStructureVariable.MyChild+MySceneVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"variable");
|
||||
node->Visit(validator);
|
||||
RequireFatalErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetFatalErrors()[0]->GetMessage() ==
|
||||
"Operators (+, -, /, *) can't be used in variable names. Remove "
|
||||
"the operator from the variable name.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3929,7 +4204,9 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"objectvar", "MyObject");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"This variable does not exist on this object or group.");
|
||||
}
|
||||
|
||||
SECTION("Undeclared object variable with children") {
|
||||
@@ -3941,7 +4218,30 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
"objectvar", "MyObject");
|
||||
node->Visit(validator);
|
||||
RequireNoFatalError(validator);
|
||||
// TODO Add a non-fatal error
|
||||
RequireAllErrorsCount(validator, 1);
|
||||
REQUIRE(validator.GetAllErrors()[0]->GetMessage() ==
|
||||
"This variable does not exist on this object or group.");
|
||||
}
|
||||
|
||||
SECTION("Undeclared object variable (legacy and extensions)") {
|
||||
auto node = parser.ParseExpression("MyUndeclaredVariable");
|
||||
REQUIRE(node != nullptr);
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Undeclared object variable with children (legacy and extensions)") {
|
||||
auto node =
|
||||
parser.ParseExpression("MyUndeclaredVariable.MyChild.MyChild");
|
||||
REQUIRE(node != nullptr);
|
||||
// This is a legacy object variable, we don't pass the object name.
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers,
|
||||
"objectvar", "");
|
||||
node->Visit(validator);
|
||||
RequireNoError(validator);
|
||||
}
|
||||
|
||||
SECTION("Undeclared object variable in expression") {
|
||||
@@ -4324,7 +4624,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A string concatenated with a number variable (will have to be casted to a string in code generation)
|
||||
@@ -4335,7 +4634,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A string concatenated with an unknown variable (will have to be casted to a string in code generation)
|
||||
@@ -4346,8 +4644,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
RequireNoFatalError(validator);
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'number'") {
|
||||
@@ -4367,7 +4664,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A number concatenated with a string variable (will have to be casted to a number in code generation)
|
||||
@@ -4378,7 +4674,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A number concatenated with an unknown variable (will have to be casted to a number in code generation)
|
||||
@@ -4389,8 +4684,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
RequireNoFatalError(validator);
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent type is 'number|string'") {
|
||||
@@ -4411,7 +4705,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A string concatenated with a number variable (will have to be casted to a string in code generation)
|
||||
@@ -4422,7 +4715,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A string concatenated with an unknown variable (will have to be casted to a string in code generation)
|
||||
@@ -4433,8 +4725,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
RequireNoFatalError(validator);
|
||||
}
|
||||
}
|
||||
SECTION("Expression/parent inferred type is 'number'") {
|
||||
@@ -4454,7 +4745,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A number concatenated with a string variable (will have to be casted to a number in code generation)
|
||||
@@ -4465,7 +4755,6 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
}
|
||||
// A number concatenated with an unknown variable (will have to be casted to a number in code generation)
|
||||
@@ -4476,8 +4765,7 @@ TEST_CASE("ExpressionParser2", "[common][events]") {
|
||||
|
||||
gd::ExpressionValidator validator(platform, projectScopedContainers, "number|string");
|
||||
node->Visit(validator);
|
||||
|
||||
RequireNoError(validator);
|
||||
RequireNoFatalError(validator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "DummyPlatform.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "GDCore/Events/Parsers/ExpressionParser2.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Extensions/PlatformExtension.h"
|
||||
#include "GDCore/IDE/Events/ExpressionValidator.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
/*
|
||||
* GDevelop Core
|
||||
* Copyright 2008-2016 Florian Rival ([email protected]). All rights
|
||||
* reserved. This project is released under the MIT License.
|
||||
*/
|
||||
/**
|
||||
* @file Tests covering gd::ObjectsContainer, in particular the lookup of
|
||||
* objects by name, which must stay correct whatever the way objects are
|
||||
* added, removed, moved or renamed.
|
||||
*/
|
||||
#include "GDCore/Project/ObjectsContainer.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "DummyPlatform.h"
|
||||
#include "GDCore/Extensions/Platform.h"
|
||||
#include "GDCore/Project/Layout.h"
|
||||
#include "GDCore/Project/Object.h"
|
||||
#include "GDCore/Project/ObjectsContainersList.h"
|
||||
#include "GDCore/Project/Project.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
namespace {
|
||||
gd::Object &InsertObject(gd::Project &project,
|
||||
gd::ObjectsContainer &objectsContainer,
|
||||
const gd::String &name) {
|
||||
return objectsContainer.InsertNewObject(
|
||||
project, "MyExtension::Sprite", name,
|
||||
objectsContainer.GetObjectsCount());
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("ObjectsContainer", "[common]") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
gd::ObjectsContainer objectsContainer(gd::ObjectsContainer::Scene);
|
||||
|
||||
SECTION("Objects can be found by name once inserted") {
|
||||
gd::Object &object1 = InsertObject(project, objectsContainer, "Object1");
|
||||
gd::Object &object2 = InsertObject(project, objectsContainer, "Object2");
|
||||
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object2"));
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object3"));
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object1);
|
||||
REQUIRE(&objectsContainer.GetObject("Object2") == &object2);
|
||||
|
||||
// Insertion after a lookup is also found.
|
||||
gd::Object &object3 = InsertObject(project, objectsContainer, "Object3");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object3"));
|
||||
REQUIRE(&objectsContainer.GetObject("Object3") == &object3);
|
||||
|
||||
// Lookups on a const container work too.
|
||||
const gd::ObjectsContainer &constObjectsContainer = objectsContainer;
|
||||
REQUIRE(constObjectsContainer.HasObjectNamed("Object2"));
|
||||
REQUIRE(&constObjectsContainer.GetObject("Object2") == &object2);
|
||||
}
|
||||
|
||||
SECTION("Objects renamed directly with gd::Object::SetName are found") {
|
||||
gd::Object &object1 = InsertObject(project, objectsContainer, "Object1");
|
||||
gd::Object &object2 = InsertObject(project, objectsContainer, "Object2");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
object1.SetName("RenamedObject1");
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(objectsContainer.HasObjectNamed("RenamedObject1"));
|
||||
REQUIRE(&objectsContainer.GetObject("RenamedObject1") == &object1);
|
||||
|
||||
// Swap names between two objects.
|
||||
object2.SetName("Object1");
|
||||
object1.SetName("Object2");
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object2);
|
||||
REQUIRE(&objectsContainer.GetObject("Object2") == &object1);
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("RenamedObject1"));
|
||||
}
|
||||
|
||||
SECTION("Objects renamed by unserialization are found") {
|
||||
gd::Object &object = InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
gd::SerializerElement element;
|
||||
object.SerializeTo(element);
|
||||
element.SetAttribute("name", "RenamedObject1");
|
||||
object.UnserializeFrom(project, element);
|
||||
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(&objectsContainer.GetObject("RenamedObject1") == &object);
|
||||
}
|
||||
|
||||
SECTION("Objects renamed by copy-assignment are found") {
|
||||
gd::Object &object = InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
object = *project.CreateObject("MyExtension::Sprite", "RenamedObject1");
|
||||
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(&objectsContainer.GetObject("RenamedObject1") == &object);
|
||||
}
|
||||
|
||||
SECTION("Removed objects are not found anymore") {
|
||||
InsertObject(project, objectsContainer, "Object1");
|
||||
InsertObject(project, objectsContainer, "Object2");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
objectsContainer.RemoveObject("Object1");
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object2"));
|
||||
|
||||
// Re-adding an object with the same name gives the new object.
|
||||
gd::Object &newObject1 = InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &newObject1);
|
||||
|
||||
objectsContainer.Clear();
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object2"));
|
||||
}
|
||||
|
||||
SECTION("Objects inserted by copy or in a folder are found") {
|
||||
gd::Object &object1 = InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
gd::Object &object2 = objectsContainer.InsertNewObjectInFolder(
|
||||
project, "MyExtension::Sprite", "Object2",
|
||||
objectsContainer.GetRootFolder(), 0);
|
||||
REQUIRE(&objectsContainer.GetObject("Object2") == &object2);
|
||||
|
||||
std::unique_ptr<gd::Object> otherObject =
|
||||
project.CreateObject("MyExtension::Sprite", "Object3");
|
||||
gd::Object &object3 = objectsContainer.InsertObject(*otherObject, 0);
|
||||
REQUIRE(&objectsContainer.GetObject("Object3") == &object3);
|
||||
REQUIRE(&object3 != otherObject.get());
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object1);
|
||||
}
|
||||
|
||||
SECTION("Moved objects are still found") {
|
||||
gd::Object &object1 = InsertObject(project, objectsContainer, "Object1");
|
||||
gd::Object &object2 = InsertObject(project, objectsContainer, "Object2");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
objectsContainer.MoveObject(0, 1);
|
||||
REQUIRE(&objectsContainer.GetObject(0) == &object2);
|
||||
REQUIRE(&objectsContainer.GetObject(1) == &object1);
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object1);
|
||||
REQUIRE(&objectsContainer.GetObject("Object2") == &object2);
|
||||
REQUIRE(objectsContainer.GetObjectPosition("Object1") == 1);
|
||||
}
|
||||
|
||||
SECTION("Objects moved to another container are found in it only") {
|
||||
gd::Object &object = InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
gd::ObjectsContainer otherObjectsContainer(gd::ObjectsContainer::Scene);
|
||||
REQUIRE(!otherObjectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
objectsContainer.MoveObjectFolderOrObjectToAnotherContainerInFolder(
|
||||
objectsContainer.GetRootFolder().GetObjectChild("Object1"),
|
||||
otherObjectsContainer, otherObjectsContainer.GetRootFolder(), 0);
|
||||
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(&otherObjectsContainer.GetObject("Object1") == &object);
|
||||
}
|
||||
|
||||
SECTION("Copied containers have their own objects") {
|
||||
gd::Object &object = InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
gd::ObjectsContainer copiedObjectsContainer(objectsContainer);
|
||||
REQUIRE(copiedObjectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(&copiedObjectsContainer.GetObject("Object1") != &object);
|
||||
|
||||
gd::ObjectsContainer assignedObjectsContainer(gd::ObjectsContainer::Global);
|
||||
InsertObject(project, assignedObjectsContainer, "Object2");
|
||||
REQUIRE(assignedObjectsContainer.HasObjectNamed("Object2"));
|
||||
assignedObjectsContainer = objectsContainer;
|
||||
REQUIRE(!assignedObjectsContainer.HasObjectNamed("Object2"));
|
||||
REQUIRE(assignedObjectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(&assignedObjectsContainer.GetObject("Object1") != &object);
|
||||
|
||||
// Renaming in a copy does not affect the original.
|
||||
copiedObjectsContainer.GetObject("Object1").SetName("RenamedObject1");
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object);
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("RenamedObject1"));
|
||||
REQUIRE(copiedObjectsContainer.HasObjectNamed("RenamedObject1"));
|
||||
}
|
||||
|
||||
SECTION("Unserialized objects are found") {
|
||||
InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
gd::SerializerElement element;
|
||||
objectsContainer.SerializeObjectsTo(element);
|
||||
element.GetChild(0).SetAttribute("name", "Object2");
|
||||
|
||||
objectsContainer.UnserializeObjectsFrom(project, element);
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Object2"));
|
||||
REQUIRE(&objectsContainer.GetObject("Object2") ==
|
||||
&objectsContainer.GetObject(0));
|
||||
}
|
||||
|
||||
SECTION("Empty container and empty names") {
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Object1"));
|
||||
REQUIRE(!objectsContainer.HasObjectNamed(""));
|
||||
|
||||
InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(!objectsContainer.HasObjectNamed(""));
|
||||
|
||||
gd::Object &unnamedObject = InsertObject(project, objectsContainer, "");
|
||||
REQUIRE(&objectsContainer.GetObject("") == &unnamedObject);
|
||||
}
|
||||
|
||||
SECTION("Names with non-ASCII characters") {
|
||||
gd::Object &object = InsertObject(project, objectsContainer, "Objet_é_日本");
|
||||
REQUIRE(objectsContainer.HasObjectNamed("Objet_é_日本"));
|
||||
REQUIRE(&objectsContainer.GetObject("Objet_é_日本") == &object);
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Objet_e_日本"));
|
||||
|
||||
object.SetName("Objet_è_日本");
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Objet_é_日本"));
|
||||
REQUIRE(&objectsContainer.GetObject("Objet_è_日本") == &object);
|
||||
}
|
||||
|
||||
SECTION("References stay valid while other objects are changed") {
|
||||
gd::Object &object1 = InsertObject(project, objectsContainer, "Object1");
|
||||
const gd::Object *found = &objectsContainer.GetObject("Object1");
|
||||
REQUIRE(found == &object1);
|
||||
|
||||
// Enough insertions to force reallocations of the underlying vector.
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
InsertObject(project, objectsContainer, "Other" + gd::String::From(i));
|
||||
}
|
||||
objectsContainer.RemoveObject("Other3");
|
||||
objectsContainer.MoveObject(0, 50);
|
||||
objectsContainer.GetObject("Other4").SetName("RenamedOther4");
|
||||
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == found);
|
||||
REQUIRE(&objectsContainer.GetObject("Other99") ==
|
||||
&objectsContainer.GetObject(objectsContainer.GetObjectsCount() - 1));
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Other3"));
|
||||
REQUIRE(!objectsContainer.HasObjectNamed("Other4"));
|
||||
REQUIRE(objectsContainer.HasObjectNamed("RenamedOther4"));
|
||||
}
|
||||
|
||||
SECTION("Duplicated object renamed afterwards (as done by the IDE)") {
|
||||
gd::Object &object1 = InsertObject(project, objectsContainer, "Object1");
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object1);
|
||||
|
||||
// The IDE inserts a copy (which has the same name), then renames it.
|
||||
gd::Object &duplicatedObject =
|
||||
objectsContainer.InsertObject(objectsContainer.GetObject("Object1"), 0);
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &duplicatedObject);
|
||||
|
||||
duplicatedObject.SetName("Object1Copy");
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object1);
|
||||
REQUIRE(&objectsContainer.GetObject("Object1Copy") == &duplicatedObject);
|
||||
}
|
||||
|
||||
SECTION("A copied container outlives the original") {
|
||||
std::unique_ptr<gd::ObjectsContainer> copiedObjectsContainer;
|
||||
gd::ObjectsContainer assignedObjectsContainer(gd::ObjectsContainer::Global);
|
||||
InsertObject(project, assignedObjectsContainer, "OldObject");
|
||||
REQUIRE(assignedObjectsContainer.HasObjectNamed("OldObject"));
|
||||
{
|
||||
gd::ObjectsContainer originalObjectsContainer(
|
||||
gd::ObjectsContainer::Scene);
|
||||
InsertObject(project, originalObjectsContainer, "Object1");
|
||||
// Build the index of the original before copying it.
|
||||
REQUIRE(originalObjectsContainer.HasObjectNamed("Object1"));
|
||||
|
||||
copiedObjectsContainer.reset(
|
||||
new gd::ObjectsContainer(originalObjectsContainer));
|
||||
assignedObjectsContainer = originalObjectsContainer;
|
||||
}
|
||||
|
||||
REQUIRE(copiedObjectsContainer->HasObjectNamed("Object1"));
|
||||
REQUIRE(&copiedObjectsContainer->GetObject("Object1") ==
|
||||
&copiedObjectsContainer->GetObject(0));
|
||||
REQUIRE(!assignedObjectsContainer.HasObjectNamed("OldObject"));
|
||||
REQUIRE(&assignedObjectsContainer.GetObject("Object1") ==
|
||||
&assignedObjectsContainer.GetObject(0));
|
||||
}
|
||||
|
||||
SECTION("Lookups through an ObjectsContainersList follow renames") {
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &globalObject =
|
||||
InsertObject(project, project.GetObjects(), "GlobalObject");
|
||||
gd::Object &sceneObject =
|
||||
InsertObject(project, layout.GetObjects(), "SceneObject");
|
||||
auto objectsContainersList = gd::ObjectsContainersList::
|
||||
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
|
||||
// The variables container identifies which object was found.
|
||||
auto findVariables = [&](const gd::String &name) {
|
||||
return objectsContainersList.GetObjectOrGroupVariablesContainer(name);
|
||||
};
|
||||
|
||||
REQUIRE(findVariables("GlobalObject") == &globalObject.GetVariables());
|
||||
REQUIRE(findVariables("SceneObject") == &sceneObject.GetVariables());
|
||||
REQUIRE(findVariables("UnknownObject") == nullptr);
|
||||
REQUIRE(!objectsContainersList.HasObjectNamed("UnknownObject"));
|
||||
|
||||
sceneObject.SetName("RenamedSceneObject");
|
||||
globalObject.SetName("SceneObject");
|
||||
REQUIRE(!objectsContainersList.HasObjectNamed("GlobalObject"));
|
||||
REQUIRE(findVariables("RenamedSceneObject") == &sceneObject.GetVariables());
|
||||
REQUIRE(findVariables("SceneObject") == &globalObject.GetVariables());
|
||||
|
||||
layout.GetObjects().RemoveObject("RenamedSceneObject");
|
||||
REQUIRE(!objectsContainersList.HasObjectNamed("RenamedSceneObject"));
|
||||
REQUIRE(findVariables("RenamedSceneObject") == nullptr);
|
||||
}
|
||||
|
||||
SECTION("The first object wins when two objects have the same name") {
|
||||
gd::Object &object1 = InsertObject(project, objectsContainer, "Object1");
|
||||
gd::Object &object2 = InsertObject(project, objectsContainer, "Object2");
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object1);
|
||||
|
||||
object2.SetName("Object1");
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object1);
|
||||
|
||||
objectsContainer.MoveObject(1, 0);
|
||||
REQUIRE(&objectsContainer.GetObject("Object1") == &object2);
|
||||
}
|
||||
}
|
||||
@@ -825,3 +825,129 @@ TEST_CASE("ObjectContainersList (GetAnimationNamesOfObject)", "[common]") {
|
||||
REQUIRE(animationNames.size() == 2);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ObjectContainersList (scene group shadowing a global group)",
|
||||
"[common]") {
|
||||
// A scene group having the same name as a global group is an inconsistency
|
||||
// that the editor tries to avoid, but it can exist in projects. In this
|
||||
// case, the scene group shadows the global one (the global group is
|
||||
// considered as not existing in this scene), consistently across all the
|
||||
// helpers - including ExpandObjectName, which is used to generate the code
|
||||
// of events.
|
||||
|
||||
SECTION("An empty scene group shadows a global group having objects") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
gd::Object &globalObject = project.GetObjects().InsertNewObject(
|
||||
project, "MyExtension::Sprite", "GlobalObject", 0);
|
||||
globalObject.AddNewBehavior(project, "MyExtension::MyBehavior",
|
||||
"MyBehavior");
|
||||
globalObject.GetVariables().InsertNew("MyVariable", 0);
|
||||
auto &globalGroup =
|
||||
project.GetObjects().GetObjectGroups().InsertNew("Group", 0);
|
||||
globalGroup.AddObject(globalObject.GetName());
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
layout.GetObjects().GetObjectGroups().InsertNew("Group", 0);
|
||||
|
||||
auto objectsContainersList = gd::ObjectsContainersList::
|
||||
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
|
||||
|
||||
REQUIRE(objectsContainersList.GetTypeOfObject("Group") == "");
|
||||
REQUIRE(objectsContainersList.GetBehaviorsOfObject("Group", true).empty());
|
||||
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
|
||||
"Group", "MyBehavior", true) == "");
|
||||
REQUIRE(!objectsContainersList.HasBehaviorInObjectOrGroup("Group",
|
||||
"MyBehavior"));
|
||||
REQUIRE(objectsContainersList
|
||||
.GetBehaviorNamesInObjectOrGroup(
|
||||
"Group", "MyExtension::MyBehavior", true)
|
||||
.empty());
|
||||
REQUIRE(objectsContainersList.HasObjectOrGroupWithVariableNamed(
|
||||
"Group", "MyVariable") ==
|
||||
gd::ObjectsContainersList::VariableExistence::GroupIsEmpty);
|
||||
REQUIRE(objectsContainersList.ExpandObjectName("Group", "").empty());
|
||||
}
|
||||
|
||||
SECTION("A scene group having objects shadows a global group") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
gd::Object &globalObject = project.GetObjects().InsertNewObject(
|
||||
project, "MyExtension::Sprite", "GlobalObject", 0);
|
||||
globalObject.AddNewBehavior(project, "MyExtension::MyBehavior",
|
||||
"MyBehavior");
|
||||
auto &globalGroup =
|
||||
project.GetObjects().GetObjectGroups().InsertNew("Group", 0);
|
||||
globalGroup.AddObject(globalObject.GetName());
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
gd::Object &sceneObject = layout.GetObjects().InsertNewObject(
|
||||
project, "MyExtension::FakeObjectWithDefaultBehavior", "SceneObject",
|
||||
0);
|
||||
sceneObject.AddNewBehavior(project, "MyExtension::MyOtherBehavior",
|
||||
"MyOtherBehavior");
|
||||
auto &sceneGroup =
|
||||
layout.GetObjects().GetObjectGroups().InsertNew("Group", 0);
|
||||
sceneGroup.AddObject(sceneObject.GetName());
|
||||
|
||||
auto objectsContainersList = gd::ObjectsContainersList::
|
||||
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
|
||||
|
||||
REQUIRE(objectsContainersList.GetTypeOfObject("Group") ==
|
||||
"MyExtension::FakeObjectWithDefaultBehavior");
|
||||
|
||||
const auto behaviors =
|
||||
objectsContainersList.GetBehaviorsOfObject("Group", true);
|
||||
REQUIRE(std::find(behaviors.begin(), behaviors.end(), "MyOtherBehavior") !=
|
||||
behaviors.end());
|
||||
REQUIRE(std::find(behaviors.begin(), behaviors.end(), "MyBehavior") ==
|
||||
behaviors.end());
|
||||
|
||||
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
|
||||
"Group", "MyOtherBehavior", true) ==
|
||||
"MyExtension::MyOtherBehavior");
|
||||
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
|
||||
"Group", "MyBehavior", true) == "");
|
||||
REQUIRE(objectsContainersList.HasBehaviorInObjectOrGroup(
|
||||
"Group", "MyOtherBehavior"));
|
||||
REQUIRE(!objectsContainersList.HasBehaviorInObjectOrGroup("Group",
|
||||
"MyBehavior"));
|
||||
|
||||
const auto expandedObjects =
|
||||
objectsContainersList.ExpandObjectName("Group", "");
|
||||
REQUIRE(expandedObjects.size() == 1);
|
||||
REQUIRE(expandedObjects[0] == "SceneObject");
|
||||
}
|
||||
|
||||
SECTION("A global group is used when the scene has no group with its name") {
|
||||
gd::Platform platform;
|
||||
gd::Project project;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
|
||||
gd::Object &globalObject = project.GetObjects().InsertNewObject(
|
||||
project, "MyExtension::Sprite", "GlobalObject", 0);
|
||||
globalObject.AddNewBehavior(project, "MyExtension::MyBehavior",
|
||||
"MyBehavior");
|
||||
auto &globalGroup =
|
||||
project.GetObjects().GetObjectGroups().InsertNew("Group", 0);
|
||||
globalGroup.AddObject(globalObject.GetName());
|
||||
|
||||
gd::Layout &layout = project.InsertNewLayout("Scene", 0);
|
||||
|
||||
auto objectsContainersList = gd::ObjectsContainersList::
|
||||
MakeNewObjectsContainersListForProjectAndLayout(project, layout);
|
||||
|
||||
REQUIRE(objectsContainersList.GetTypeOfObject("Group") ==
|
||||
"MyExtension::Sprite");
|
||||
const auto behaviors =
|
||||
objectsContainersList.GetBehaviorsOfObject("Group", true);
|
||||
REQUIRE(behaviors.size() == 1);
|
||||
REQUIRE(behaviors[0] == "MyBehavior");
|
||||
REQUIRE(objectsContainersList.GetTypeOfBehaviorInObjectOrGroup(
|
||||
"Group", "MyBehavior", true) == "MyExtension::MyBehavior");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
/**
|
||||
* @file Tests covering common features of GDevelop Core.
|
||||
*/
|
||||
#include "GDCore/IDE/PropertyFunctionGenerator.h"
|
||||
#include "GDCore/IDE/ExtensionEditor/PropertyFunctionGenerator.h"
|
||||
#include "DummyPlatform.h"
|
||||
#include "GDCore/Events/Builtin/StandardEvent.h"
|
||||
#include "GDCore/Extensions/Metadata/ValueTypeMetadata.h"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
|
||||
#include "GDCore/CommonTools.h"
|
||||
#include "GDCore/Project/VariablesContainer.h"
|
||||
#include "GDCore/Serialization/SerializerElement.h"
|
||||
#include "catch.hpp"
|
||||
|
||||
TEST_CASE("Variable", "[common][variables]") {
|
||||
@@ -171,4 +172,48 @@ TEST_CASE("Variable", "[common][variables]") {
|
||||
REQUIRE(variable.GetChild("MyChild").GetPersistentUuid() == childUuid);
|
||||
REQUIRE(variable.GetChild("MyNewChild").GetPersistentUuid() != "");
|
||||
}
|
||||
SECTION("Serialization keeps the editor-only \"mixed values\" marker") {
|
||||
// The editor merges the variables of the objects of a group into a
|
||||
// temporary container, marking the variables that don't have the same
|
||||
// value (or type) on all objects as having "mixed values". This container
|
||||
// is copied and snapshotted through serialization, so the marker must
|
||||
// survive a serialization round trip - including for the variables that
|
||||
// have no value and no children to read (a structure marked as having
|
||||
// mixed values, as the marker clears its children, or a variable with
|
||||
// entirely mixed types).
|
||||
gd::Variable numberVariable;
|
||||
numberVariable.SetValue(123);
|
||||
numberVariable.MarkAsMixedValues();
|
||||
|
||||
gd::Variable structureVariable;
|
||||
structureVariable.GetChild("MyChild").SetValue(456);
|
||||
structureVariable.MarkAsMixedValues();
|
||||
|
||||
gd::Variable mixedTypesVariable;
|
||||
mixedTypesVariable.CastTo(gd::Variable::Type::MixedTypes);
|
||||
|
||||
gd::SerializerElement element;
|
||||
numberVariable.SerializeTo(element.AddChild("number"));
|
||||
structureVariable.SerializeTo(element.AddChild("structure"));
|
||||
mixedTypesVariable.SerializeTo(element.AddChild("mixedTypes"));
|
||||
|
||||
gd::Variable unserializedNumberVariable;
|
||||
unserializedNumberVariable.UnserializeFrom(element.GetChild("number"));
|
||||
REQUIRE(unserializedNumberVariable.GetType() == gd::Variable::Type::Number);
|
||||
REQUIRE(unserializedNumberVariable.HasMixedValues());
|
||||
|
||||
gd::Variable unserializedStructureVariable;
|
||||
unserializedStructureVariable.UnserializeFrom(
|
||||
element.GetChild("structure"));
|
||||
REQUIRE(unserializedStructureVariable.GetType() ==
|
||||
gd::Variable::Type::Structure);
|
||||
REQUIRE(unserializedStructureVariable.HasMixedValues());
|
||||
|
||||
gd::Variable unserializedMixedTypesVariable;
|
||||
unserializedMixedTypesVariable.UnserializeFrom(
|
||||
element.GetChild("mixedTypes"));
|
||||
REQUIRE(unserializedMixedTypesVariable.GetType() ==
|
||||
gd::Variable::Type::MixedTypes);
|
||||
REQUIRE(unserializedMixedTypesVariable.HasMixedValues());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1583,6 +1583,31 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
|
||||
"RenamedObjectWithMyBehavior.GetObjectNumber() + RenamedObjectWithMyBehavior.MyVariable + RenamedObjectWithMyBehavior.MyStructureVariable.Child");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Global object in scene events") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &layout = project.InsertNewLayout("Scene", 0);
|
||||
auto &globalObject = project.GetObjects().InsertNewObject(
|
||||
project, "MyExtension::Sprite", "GlobalObject", 0);
|
||||
|
||||
const auto &objectParameterInstruction =
|
||||
CreateInstructionWithObjectParameter(
|
||||
project, layout.GetEvents(), globalObject.GetName());
|
||||
const auto &objectExpressionInstruction =
|
||||
CreateInstructionWithNumberParameter(
|
||||
project, layout.GetEvents(), "GlobalObject.GetObjectNumber()");
|
||||
|
||||
gd::WholeProjectRefactorer::GlobalObjectOrGroupRenamed(
|
||||
project, "GlobalObject", "RenamedGlobalObject",
|
||||
/* isObjectGroup=*/false);
|
||||
|
||||
REQUIRE(objectParameterInstruction.GetParameter(0).GetPlainString() ==
|
||||
"RenamedGlobalObject");
|
||||
REQUIRE(objectExpressionInstruction.GetParameter(0).GetPlainString() ==
|
||||
"RenamedGlobalObject.GetObjectNumber()");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Group renamed (in layout)") {
|
||||
@@ -2457,6 +2482,77 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Free function moved (instructions update)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsFunction(
|
||||
project, eventsExtension, "MyEventsExtension", "MyOtherEventsExtension",
|
||||
"MyEventsFunction",
|
||||
"MyRenamedEventsFunction");
|
||||
|
||||
for (auto *eventsList : GetEventsLists(project)) {
|
||||
// Check that events function calls in instructions have been renamed
|
||||
REQUIRE(
|
||||
GetEventFirstActionType(eventsList->GetEvent(FreeFunctionAction)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsFunction");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Free function moved (expressions updated)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsFunction(
|
||||
project, eventsExtension, "MyEventsExtension", "MyOtherEventsExtension",
|
||||
"MyEventsFunctionExpression",
|
||||
"MyRenamedFunctionExpression");
|
||||
|
||||
for (auto *eventsList : GetEventsLists(project)) {
|
||||
// Check that events function calls in expressions have been renamed
|
||||
REQUIRE(GetEventFirstActionFirstParameterString(
|
||||
eventsList->GetEvent(FreeFunctionWithExpression)) ==
|
||||
"1 + MyOtherEventsExtension::MyRenamedFunctionExpression(123, 456)");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Free function moved (expression and condition updated)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsFunction(
|
||||
project, eventsExtension, "MyEventsExtension", "MyOtherEventsExtension",
|
||||
"MyEventsFunctionExpressionAndCondition",
|
||||
"MyRenamedFunctionExpressionAndCondition");
|
||||
|
||||
for (auto *eventsList : GetEventsLists(project)) {
|
||||
// Check that events function calls in expressions have been renamed
|
||||
REQUIRE(GetEventFirstActionFirstParameterString(
|
||||
eventsList->GetEvent(FreeExpressionFromExpressionAndCondition)) ==
|
||||
"2 + MyOtherEventsExtension::MyRenamedFunctionExpressionAndCondition(111, 222)");
|
||||
|
||||
// Check that events function calls in instructions have been renamed
|
||||
REQUIRE(GetEventFirstConditionType(
|
||||
eventsList->GetEvent(FreeConditionFromExpressionAndCondition)) ==
|
||||
"MyOtherEventsExtension::MyRenamedFunctionExpressionAndCondition");
|
||||
|
||||
// Check that the action still refer to the right ExpressionAndCondition.
|
||||
REQUIRE(eventsExtension.GetEventsFunctions()
|
||||
.GetEventsFunction("MyEventsFunctionActionWithOperator")
|
||||
.GetGetterName() ==
|
||||
"MyRenamedFunctionExpressionAndCondition");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("(Free function) number parameter renamed (in expressions)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
@@ -3056,6 +3152,223 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check that this works when behaviors are attached to a child-object.
|
||||
SECTION("Events based behavior renamed (other behaviors properties update)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
auto &eventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
// Set up another events based behavior having a "required behavior"
|
||||
// property referring to the behavior.
|
||||
auto &otherEventsExtension =
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
auto &otherEventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().InsertNew(
|
||||
"MyOtherEventsBasedBehavior");
|
||||
auto &otherEventsBasedBehaviorFirstProperty =
|
||||
otherEventsBasedBehavior.GetPropertyDescriptors()
|
||||
.InsertNew("SomeRequiredBehavior")
|
||||
.SetType("Behavior")
|
||||
.AddExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
|
||||
|
||||
// Also add another "required behavior" property referring to another
|
||||
// unrelated behavior.
|
||||
auto &otherEventsBasedBehaviorSecondProperty =
|
||||
otherEventsBasedBehavior.GetPropertyDescriptors()
|
||||
.InsertNew("SomeRequiredBehavior")
|
||||
.SetType("Behavior")
|
||||
.AddExtraInfo("SomeOtherExtension::SomeOtherBehavior");
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, eventsExtension, "MyEventsExtension", "MyOtherEventsExtension",
|
||||
"MyEventsBasedBehavior", "MyRenamedEventsBasedBehavior");
|
||||
|
||||
// Check the other events-based behavior has its property updated.
|
||||
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().size() == 1);
|
||||
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().at(0) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
|
||||
// Check the other events-based behavior has its other property left
|
||||
// untouched.
|
||||
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().size() == 1);
|
||||
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().at(0) ==
|
||||
"SomeOtherExtension::SomeOtherBehavior");
|
||||
}
|
||||
|
||||
SECTION("Events based behavior moved (instructions update)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
auto &eventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, eventsExtension, "MyEventsExtension", "MyOtherEventsExtension",
|
||||
"MyEventsBasedBehavior", "MyRenamedEventsBasedBehavior");
|
||||
|
||||
// Check that the type of the behavior was changed in the behaviors of
|
||||
// objects. Name is *not* changed.
|
||||
REQUIRE(project.GetLayout("Scene")
|
||||
.GetObjects()
|
||||
.GetObject("ObjectWithMyBehavior")
|
||||
.GetBehavior("MyBehavior")
|
||||
.GetTypeName() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
REQUIRE(project.GetObjects()
|
||||
.GetObject("GlobalObjectWithMyBehavior")
|
||||
.GetBehavior("MyBehavior")
|
||||
.GetTypeName() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
REQUIRE(project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsBasedObjects()
|
||||
.Get("MyOtherEventsBasedObject")
|
||||
.GetObjects()
|
||||
.GetObject("ObjectWithMyBehavior")
|
||||
.GetBehavior("MyBehavior")
|
||||
.GetTypeName() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
|
||||
for (auto *eventsList : GetEventsLists(project)) {
|
||||
// Check if events-based behavior methods have been renamed in
|
||||
// instructions
|
||||
REQUIRE(GetEventFirstActionType(eventsList->GetEvent(BehaviorAction)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior::"
|
||||
"MyBehaviorEventsFunction");
|
||||
REQUIRE(GetEventFirstConditionType(
|
||||
eventsList->GetEvent(BehaviorConditionFromExpressionAndCondition)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior::"
|
||||
"MyBehaviorEventsFunctionExpressionAndCondition");
|
||||
REQUIRE(GetEventFirstActionType(
|
||||
eventsList->GetEvent(BehaviorActionWithOperator)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior::"
|
||||
"MyBehaviorEventsFunctionActionWithOperator");
|
||||
|
||||
// Check if events-based behaviors properties have been renamed in
|
||||
// instructions
|
||||
REQUIRE(GetEventFirstActionType(
|
||||
eventsList->GetEvent(BehaviorPropertyAction)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior::"
|
||||
"SetPropertyMyProperty");
|
||||
REQUIRE(GetEventFirstActionType(
|
||||
eventsList->GetEvent(BehaviorSharedPropertyAction)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior::"
|
||||
"SetSharedPropertyMySharedProperty");
|
||||
|
||||
// Check events-based behavior methods have *not* been renamed in
|
||||
// expressions
|
||||
REQUIRE(GetEventFirstActionFirstParameterString(
|
||||
eventsList->GetEvent(BehaviorExpression)) ==
|
||||
"1 + ObjectWithMyBehavior.MyBehavior::"
|
||||
"MyBehaviorEventsFunctionExpression(123, 456, 789)");
|
||||
REQUIRE(GetEventFirstActionFirstParameterString(
|
||||
eventsList->GetEvent(BehaviorExpressionFromExpressionAndCondition)) ==
|
||||
"5 + ObjectWithMyBehavior.MyBehavior::"
|
||||
"MyBehaviorEventsFunctionExpressionAndCondition(111, 222)");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Events based behavior moved (in parameters)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
auto &eventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, eventsExtension, "MyEventsExtension", "MyOtherEventsExtension",
|
||||
"MyEventsBasedBehavior", "MyRenamedEventsBasedBehavior");
|
||||
|
||||
// Free function
|
||||
auto &myEventsFunction =
|
||||
project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsFunctions()
|
||||
.GetEventsFunction("MyEventsFunction");
|
||||
REQUIRE(myEventsFunction.GetParameters().GetParameter(2).GetExtraInfo() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
|
||||
// Behavior function
|
||||
{
|
||||
auto &myBehaviorEventsFunction =
|
||||
project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsBasedBehaviors()
|
||||
.Get("MyEventsBasedBehavior")
|
||||
.GetEventsFunctions()
|
||||
.GetEventsFunction("MyBehaviorEventsFunction");
|
||||
REQUIRE(myBehaviorEventsFunction.GetParameters()
|
||||
.GetParameter(3)
|
||||
.GetExtraInfo() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
}
|
||||
|
||||
// Object function
|
||||
{
|
||||
auto &myBehaviorEventsFunction =
|
||||
project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsBasedObjects()
|
||||
.Get("MyEventsBasedObject")
|
||||
.GetEventsFunctions()
|
||||
.GetEventsFunction("MyObjectEventsFunction");
|
||||
REQUIRE(myBehaviorEventsFunction.GetParameters()
|
||||
.GetParameter(2)
|
||||
.GetExtraInfo() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check that this works when behaviors are attached to a child-object.
|
||||
SECTION("Events based behavior moved (other behaviors properties update)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
auto &eventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
|
||||
|
||||
// Set up another events based behavior having a "required behavior"
|
||||
// property referring to the behavior.
|
||||
auto &otherEventsExtension =
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
auto &otherEventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().InsertNew(
|
||||
"MyOtherEventsBasedBehavior");
|
||||
auto &otherEventsBasedBehaviorFirstProperty =
|
||||
otherEventsBasedBehavior.GetPropertyDescriptors()
|
||||
.InsertNew("SomeRequiredBehavior")
|
||||
.SetType("Behavior")
|
||||
.AddExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
|
||||
|
||||
// Also add another "required behavior" property referring to another
|
||||
// unrelated behavior.
|
||||
auto &otherEventsBasedBehaviorSecondProperty =
|
||||
otherEventsBasedBehavior.GetPropertyDescriptors()
|
||||
.InsertNew("SomeRequiredBehavior")
|
||||
.SetType("Behavior")
|
||||
.AddExtraInfo("SomeOtherExtension::SomeOtherBehavior");
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsBasedBehavior(
|
||||
project, eventsExtension, "MyEventsExtension", "MyOtherEventsExtension",
|
||||
"MyEventsBasedBehavior", "MyRenamedEventsBasedBehavior");
|
||||
|
||||
// Check the other events-based behavior has its property updated.
|
||||
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().size() == 1);
|
||||
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().at(0) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
|
||||
// Check the other events-based behavior has its other property left
|
||||
// untouched.
|
||||
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().size() == 1);
|
||||
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().at(0) ==
|
||||
"SomeOtherExtension::SomeOtherBehavior");
|
||||
}
|
||||
|
||||
SECTION("Events based object renamed (instructions update)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
@@ -3185,50 +3498,141 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
|
||||
.GetObjectType() ==
|
||||
"MyEventsExtension::MyRenamedEventsBasedObject");
|
||||
}
|
||||
// TODO: Check that this works when behaviors are attached to a child-object.
|
||||
SECTION("Events based behavior renamed (other behaviors properties update)") {
|
||||
|
||||
SECTION("Events based object moved (instructions update)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
auto &eventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
|
||||
auto &eventsBasedObject =
|
||||
eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject");
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
// Set up another events based behavior having a "required behavior"
|
||||
// property referring to the behavior.
|
||||
auto &otherEventsExtension =
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
auto &otherEventsBasedBehavior =
|
||||
eventsExtension.GetEventsBasedBehaviors().InsertNew(
|
||||
"MyOtherEventsBasedBehavior");
|
||||
auto &otherEventsBasedBehaviorFirstProperty =
|
||||
otherEventsBasedBehavior.GetPropertyDescriptors()
|
||||
.InsertNew("SomeRequiredBehavior")
|
||||
.SetType("Behavior")
|
||||
.AddExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
|
||||
gd::WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
project, eventsExtension, "MyEventsExtension",
|
||||
"MyOtherEventsExtension", "MyEventsBasedObject",
|
||||
"MyRenamedEventsBasedObject");
|
||||
|
||||
// Also add another "required behavior" property referring to another
|
||||
// unrelated behavior.
|
||||
auto &otherEventsBasedBehaviorSecondProperty =
|
||||
otherEventsBasedBehavior.GetPropertyDescriptors()
|
||||
.InsertNew("SomeRequiredBehavior")
|
||||
.SetType("Behavior")
|
||||
.AddExtraInfo("SomeOtherExtension::SomeOtherBehavior");
|
||||
// Check that the type of the object was changed. Name is *not* changed.
|
||||
REQUIRE(project.GetLayout("Scene")
|
||||
.GetObjects()
|
||||
.GetObject("MyCustomObject")
|
||||
.GetType() == "MyOtherEventsExtension::MyRenamedEventsBasedObject");
|
||||
REQUIRE(project.GetObjects()
|
||||
.GetObject("MyGlobalCustomObject")
|
||||
.GetType() == "MyOtherEventsExtension::MyRenamedEventsBasedObject");
|
||||
REQUIRE(project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsBasedObjects()
|
||||
.Get("MyOtherEventsBasedObject")
|
||||
.GetObjects()
|
||||
.GetObject("MyCustomObject")
|
||||
.GetType() == "MyOtherEventsExtension::MyRenamedEventsBasedObject");
|
||||
|
||||
gd::WholeProjectRefactorer::RenameEventsBasedBehavior(
|
||||
project, eventsExtension, "MyEventsBasedBehavior",
|
||||
"MyRenamedEventsBasedBehavior");
|
||||
for (auto *eventsList : GetEventsLists(project)) {
|
||||
// Check if events-based object methods have been renamed in
|
||||
// instructions
|
||||
REQUIRE(GetEventFirstActionType(eventsList->GetEvent(ObjectAction)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject::"
|
||||
"MyObjectEventsFunction");
|
||||
REQUIRE(GetEventFirstConditionType(
|
||||
eventsList->GetEvent(ObjectConditionFromExpressionAndCondition)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject::"
|
||||
"MyObjectEventsFunctionExpressionAndCondition");
|
||||
REQUIRE(GetEventFirstActionType(
|
||||
eventsList->GetEvent(ObjectActionWithOperator)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject::"
|
||||
"MyObjectEventsFunctionActionWithOperator");
|
||||
|
||||
// Check the other events-based behavior has its property updated.
|
||||
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().size() == 1);
|
||||
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().at(0) ==
|
||||
"MyEventsExtension::MyRenamedEventsBasedBehavior");
|
||||
// Check if events-based object properties have been renamed in
|
||||
// instructions
|
||||
REQUIRE(
|
||||
GetEventFirstActionType(eventsList->GetEvent(ObjectPropertyAction)) ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject::"
|
||||
"SetPropertyMyProperty");
|
||||
|
||||
// Check the other events-based behavior has its other property left
|
||||
// untouched.
|
||||
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().size() == 1);
|
||||
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().at(0) ==
|
||||
"SomeOtherExtension::SomeOtherBehavior");
|
||||
// Check events-based object methods have *not* been renamed in
|
||||
// expressions
|
||||
REQUIRE(GetEventFirstActionFirstParameterString(
|
||||
eventsList->GetEvent(ObjectExpression)) ==
|
||||
"1 + MyCustomObject."
|
||||
"MyObjectEventsFunctionExpression(123, 456, 789)");
|
||||
REQUIRE(GetEventFirstActionFirstParameterString(
|
||||
eventsList->GetEvent(ObjectExpressionFromExpressionAndCondition)) ==
|
||||
"5 + MyCustomObject."
|
||||
"MyObjectEventsFunctionExpressionAndCondition(111, 222)");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Events based object moved (in parameters)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
auto &eventsBasedObject =
|
||||
eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject");
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
project, eventsExtension, "MyEventsExtension",
|
||||
"MyOtherEventsExtension", "MyEventsBasedObject",
|
||||
"MyRenamedEventsBasedObject");
|
||||
|
||||
// Free function
|
||||
auto &myEventsFunction =
|
||||
project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsFunctions()
|
||||
.GetEventsFunction("MyEventsFunction");
|
||||
REQUIRE(myEventsFunction.GetParameters().GetParameter(1).GetExtraInfo() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject");
|
||||
|
||||
// Behavior function
|
||||
{
|
||||
auto &myBehaviorEventsFunction =
|
||||
project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsBasedBehaviors()
|
||||
.Get("MyEventsBasedBehavior")
|
||||
.GetEventsFunctions()
|
||||
.GetEventsFunction("MyBehaviorEventsFunction");
|
||||
REQUIRE(myBehaviorEventsFunction.GetParameters()
|
||||
.GetParameter(2)
|
||||
.GetExtraInfo() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject");
|
||||
}
|
||||
|
||||
// Object function
|
||||
{
|
||||
auto &myBehaviorEventsFunction =
|
||||
project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsBasedObjects()
|
||||
.Get("MyEventsBasedObject")
|
||||
.GetEventsFunctions()
|
||||
.GetEventsFunction("MyObjectEventsFunction");
|
||||
REQUIRE(myBehaviorEventsFunction.GetParameters()
|
||||
.GetParameter(1)
|
||||
.GetExtraInfo() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject");
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("Events based object moved (in behavior object type)") {
|
||||
gd::Project project;
|
||||
gd::Platform platform;
|
||||
SetupProjectWithDummyPlatform(project, platform);
|
||||
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
|
||||
auto &eventsBasedObject =
|
||||
eventsExtension.GetEventsBasedObjects().Get("MyEventsBasedObject");
|
||||
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
|
||||
|
||||
gd::WholeProjectRefactorer::MoveEventsBasedObject(
|
||||
project, eventsExtension, "MyEventsExtension",
|
||||
"MyOtherEventsExtension", "MyEventsBasedObject",
|
||||
"MyRenamedEventsBasedObject");
|
||||
|
||||
REQUIRE(project.GetEventsFunctionsExtension("MyEventsExtension")
|
||||
.GetEventsBasedBehaviors()
|
||||
.Get("MyEventsBasedBehavior")
|
||||
.GetObjectType() ==
|
||||
"MyOtherEventsExtension::MyRenamedEventsBasedObject");
|
||||
}
|
||||
|
||||
SECTION("(Events based behavior) events action renamed") {
|
||||
|
||||
@@ -19,7 +19,9 @@ namespace gdjs {
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.BrightnessContrastShader
|
||||
gdjs.PixiFiltersTools.clampThreeShaderOutput(
|
||||
THREE_ADDONS.BrightnessContrastShader
|
||||
)
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,9 @@ namespace gdjs {
|
||||
|
||||
constructor() {
|
||||
this.shaderPass = new THREE_ADDONS.ShaderPass(
|
||||
THREE_ADDONS.HueSaturationShader
|
||||
gdjs.PixiFiltersTools.clampThreeShaderOutput(
|
||||
THREE_ADDONS.HueSaturationShader
|
||||
)
|
||||
);
|
||||
this._isEnabled = false;
|
||||
}
|
||||
|
||||
@@ -72,6 +72,9 @@ namespace gdjs {
|
||||
this.updateTexture();
|
||||
this._center = new Float32Array([runtimeObject.x, runtimeObject.y]);
|
||||
this._defaultVertexBuffer = new Float32Array(8);
|
||||
// The object's angle is not applied yet at construction time (it is set
|
||||
// afterwards, via setAngle), so the initial quad is the axis-aligned
|
||||
// square. _updateBuffers rebuilds it with the rotation applied.
|
||||
this._vertexBuffer = new Float32Array([
|
||||
runtimeObject.x - this._radius,
|
||||
runtimeObject.y + this._radius,
|
||||
@@ -326,6 +329,7 @@ namespace gdjs {
|
||||
center: this._center,
|
||||
radius: this._radius,
|
||||
color: this._color,
|
||||
angle: gdjs.toRad(this._object.getAngle()),
|
||||
};
|
||||
if (this._texture) {
|
||||
// @ts-ignore
|
||||
@@ -409,21 +413,24 @@ namespace gdjs {
|
||||
|
||||
const computedVertices = this._computeLightVertices();
|
||||
if (!computedVertices.length) {
|
||||
// Draw the same rotated quad the mesh falls back to when there are
|
||||
// no obstacles around (see _updateBuffers), so the debug overlay
|
||||
// matches the actual light boundary. _updateBuffers recomputes the
|
||||
// same values into this buffer right after, so reusing it is safe.
|
||||
const corners = this._defaultVertexBuffer;
|
||||
this._computeRotatedSquareVertices(
|
||||
gdjs.toRad(this._object.getAngle()),
|
||||
corners
|
||||
);
|
||||
debugGraphics.clear();
|
||||
debugGraphics
|
||||
.lineStyle(1, 16711680, 1)
|
||||
.moveTo(this._object.x, this._object.y)
|
||||
.lineTo(this._object.x - this._radius, this._object.y + this._radius)
|
||||
.lineTo(this._object.x + this._radius, this._object.y + this._radius)
|
||||
.moveTo(this._object.x, this._object.y)
|
||||
.lineTo(this._object.x + this._radius, this._object.y + this._radius)
|
||||
.lineTo(this._object.x + this._radius, this._object.y - this._radius)
|
||||
.moveTo(this._object.x, this._object.y)
|
||||
.lineTo(this._object.x + this._radius, this._object.y - this._radius)
|
||||
.lineTo(this._object.x - this._radius, this._object.y - this._radius)
|
||||
.moveTo(this._object.x, this._object.y)
|
||||
.lineTo(this._object.x - this._radius, this._object.y - this._radius)
|
||||
.lineTo(this._object.x - this._radius, this._object.y + this._radius);
|
||||
debugGraphics.lineStyle(1, 16711680, 1);
|
||||
for (let i = 0; i < 8; i += 2) {
|
||||
const next = (i + 2) % 8;
|
||||
debugGraphics
|
||||
.moveTo(this._object.x, this._object.y)
|
||||
.lineTo(corners[i], corners[i + 1])
|
||||
.lineTo(corners[next], corners[next + 1]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const vertices = new Array(2 * computedVertices.length + 2);
|
||||
@@ -451,24 +458,46 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the given buffer (4 corners, as x/y pairs) with the corners of the
|
||||
* light's square *rotated by the given angle* — the same rotated square
|
||||
* the shader samples the texture from (see texturedFragmentShader). At
|
||||
* angle 0, this is exactly the axis-aligned square
|
||||
* [x-radius, x+radius] x [y-radius, y+radius].
|
||||
*/
|
||||
_computeRotatedSquareVertices(angleRad: float, output: Float32Array) {
|
||||
const cosA = Math.cos(angleRad);
|
||||
const sinA = Math.sin(angleRad);
|
||||
const cx = this._object.x;
|
||||
const cy = this._object.y;
|
||||
const a = this._radius * (cosA + sinA);
|
||||
const b = this._radius * (cosA - sinA);
|
||||
output[0] = cx - a;
|
||||
output[1] = cy + b;
|
||||
output[2] = cx + b;
|
||||
output[3] = cy + a;
|
||||
output[4] = cx + a;
|
||||
output[5] = cy - b;
|
||||
output[6] = cx - b;
|
||||
output[7] = cy - a;
|
||||
}
|
||||
|
||||
_updateBuffers() {
|
||||
if (!this._light) {
|
||||
return;
|
||||
}
|
||||
this._center[0] = this._object.x;
|
||||
this._center[1] = this._object.y;
|
||||
const angleRad = gdjs.toRad(this._object.getAngle());
|
||||
this._light.shader.uniforms.angle = angleRad;
|
||||
const vertices = this._computeLightVertices();
|
||||
|
||||
// Fallback to simple quad when there are no obstacles around.
|
||||
// Fallback to simple quad when there are no obstacles around: the
|
||||
// light's rotated square, so it exactly matches the area the shader
|
||||
// samples the texture from, without clipping its corners nor
|
||||
// over-sizing the mesh.
|
||||
if (vertices.length === 0) {
|
||||
this._defaultVertexBuffer[0] = this._object.x - this._radius;
|
||||
this._defaultVertexBuffer[1] = this._object.y + this._radius;
|
||||
this._defaultVertexBuffer[2] = this._object.x + this._radius;
|
||||
this._defaultVertexBuffer[3] = this._object.y + this._radius;
|
||||
this._defaultVertexBuffer[4] = this._object.x + this._radius;
|
||||
this._defaultVertexBuffer[5] = this._object.y - this._radius;
|
||||
this._defaultVertexBuffer[6] = this._object.x - this._radius;
|
||||
this._defaultVertexBuffer[7] = this._object.y - this._radius;
|
||||
this._computeRotatedSquareVertices(angleRad, this._defaultVertexBuffer);
|
||||
this._light.shader.uniforms.center = this._center;
|
||||
this._light.geometry
|
||||
.getBuffer('aVertexPosition')
|
||||
@@ -546,18 +575,35 @@ namespace gdjs {
|
||||
* @returns the vertices of mesh.
|
||||
*/
|
||||
_computeLightVertices(): Array<FloatPoint> {
|
||||
// An untextured light renders as a circle (see defaultFragmentShader),
|
||||
// which never lights anything beyond ±radius whatever the angle, so
|
||||
// the ±radius box is an exact obstacle search area for it.
|
||||
// A textured light's lit region is the light's square *rotated by the
|
||||
// object's angle* (see the fallback quad in _updateBuffers and
|
||||
// texturedFragmentShader), whose axis-aligned bounding box extends up
|
||||
// to radius * (|cos| + |sin|) from the center. Search obstacles in
|
||||
// that whole box — searching only ±radius would miss obstacles near
|
||||
// the rotated square's corners and let the light shine through them.
|
||||
// At angle 0 both are exactly ±radius, as before rotation support.
|
||||
let searchHalfExtent = this._radius;
|
||||
if (this._texture !== null) {
|
||||
const angleRad = gdjs.toRad(this._object.getAngle());
|
||||
searchHalfExtent =
|
||||
this._radius *
|
||||
(Math.abs(Math.cos(angleRad)) + Math.abs(Math.sin(angleRad)));
|
||||
}
|
||||
const lightObstacles = this._lightObstaclesTemp;
|
||||
if (this._manager) {
|
||||
this._manager.getAllObstaclesAround(
|
||||
this._object,
|
||||
this._radius,
|
||||
searchHalfExtent,
|
||||
lightObstacles
|
||||
);
|
||||
}
|
||||
const searchAreaLeft = this._object.getX() - this._radius;
|
||||
const searchAreaTop = this._object.getY() - this._radius;
|
||||
const searchAreaRight = this._object.getX() + this._radius;
|
||||
const searchAreaBottom = this._object.getY() + this._radius;
|
||||
const searchAreaLeft = this._object.getX() - searchHalfExtent;
|
||||
const searchAreaTop = this._object.getY() - searchHalfExtent;
|
||||
const searchAreaRight = this._object.getX() + searchHalfExtent;
|
||||
const searchAreaBottom = this._object.getY() + searchHalfExtent;
|
||||
|
||||
// Bail out early if there are no obstacles.
|
||||
if (lightObstacles.length === 0) {
|
||||
@@ -590,10 +636,16 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
let maxX = this._object.x + this._radius;
|
||||
let minX = this._object.x - this._radius;
|
||||
let maxY = this._object.y + this._radius;
|
||||
let minY = this._object.y - this._radius;
|
||||
// Seed the self-boundary ("wall" that unoccluded rays terminate on)
|
||||
// with the axis-aligned bounding box of the light's rotated square —
|
||||
// the same box used to search obstacles above. At angle 0 this is
|
||||
// exactly [x-radius, x+radius] x [y-radius, y+radius], same as before;
|
||||
// at other angles it grows just enough to avoid clipping the corners
|
||||
// of the rotated texture, without over-sizing the search area.
|
||||
let minX = searchAreaLeft;
|
||||
let maxX = searchAreaRight;
|
||||
let minY = searchAreaTop;
|
||||
let maxY = searchAreaBottom;
|
||||
const flattenVertices = this._flattenVerticesTemp;
|
||||
flattenVertices.length = 0;
|
||||
for (let i = 1; i < obstaclePolygons.length; i++) {
|
||||
@@ -766,9 +818,17 @@ namespace gdjs {
|
||||
|
||||
void main() {
|
||||
float l = length(vPos - center);
|
||||
float intensity = 0.0;
|
||||
if(l < radius)
|
||||
intensity = clamp((radius - l)*(radius - l)/(radius*radius), 0.0, 1.0);
|
||||
// Discard fragments outside the light's radius rather than writing a
|
||||
// transparent color. PixiJS's stencil-based masking (used when this
|
||||
// mesh is assigned as another object's mask, e.g. via an object
|
||||
// masking extension) only looks at which fragments were rasterized,
|
||||
// not their color/alpha - a plain color write of (0,0,0,1) would
|
||||
// still count as "inside" the mask, making the light's whole
|
||||
// quad/mesh footprint act as the mask instead of its actual visible
|
||||
// circular shape. discard removes the fragment from the pipeline
|
||||
// entirely, so only the true circle is considered "inside".
|
||||
if (l >= radius) discard;
|
||||
float intensity = clamp((radius - l)*(radius - l)/(radius*radius), 0.0, 1.0);
|
||||
gl_FragColor = vec4(color*intensity, 1.0);
|
||||
}`;
|
||||
static texturedFragmentShader = `
|
||||
@@ -776,15 +836,36 @@ namespace gdjs {
|
||||
uniform vec2 center;
|
||||
uniform float radius;
|
||||
uniform vec3 color;
|
||||
uniform float angle;
|
||||
uniform sampler2D uSampler;
|
||||
varying vec2 vPos;
|
||||
|
||||
void main() {
|
||||
vec2 topleft = vec2(center.x - radius, center.y - radius);
|
||||
vec2 texCoord = (vPos - topleft)/(2.0 * radius);
|
||||
gl_FragColor = (texCoord.x > 0.0 && texCoord.x < 1.0 && texCoord.y > 0.0 && texCoord.y < 1.0)
|
||||
? vec4(color, 1.0) * texture2D(uSampler, texCoord)
|
||||
: vec4(0.0, 0.0, 0.0, 0.0);
|
||||
vec2 relativePos = vPos - center;
|
||||
float cosA = cos(angle);
|
||||
float sinA = sin(angle);
|
||||
// Rotate the sampled point back by the object's angle so that the
|
||||
// texture appears to rotate together with the light object.
|
||||
vec2 rotatedPos = vec2(
|
||||
relativePos.x * cosA + relativePos.y * sinA,
|
||||
-relativePos.x * sinA + relativePos.y * cosA
|
||||
);
|
||||
vec2 topleft = vec2(-radius, -radius);
|
||||
vec2 texCoord = (rotatedPos - topleft)/(2.0 * radius);
|
||||
|
||||
// See the comment in defaultFragmentShader above: discard (rather than
|
||||
// just writing a transparent color) any fragment outside the texture's
|
||||
// square, and any fragment whose texture pixel is itself fully
|
||||
// transparent, so stencil-based masking respects the texture's actual
|
||||
// (rotated) silhouette instead of the mesh's raw quad.
|
||||
if (texCoord.x <= 0.0 || texCoord.x >= 1.0 || texCoord.y <= 0.0 || texCoord.y >= 1.0) {
|
||||
discard;
|
||||
}
|
||||
vec4 texColor = texture2D(uSampler, texCoord);
|
||||
if (texColor.a <= 0.0) {
|
||||
discard;
|
||||
}
|
||||
gl_FragColor = vec4(color, 1.0) * texColor;
|
||||
}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,10 @@
|
||||
* Utility function for adding light object for tests.
|
||||
* @param {gdjs.RuntimeScene} runtimeScene
|
||||
* @param {number} radius
|
||||
* @param {string} [texture] Texture resource name ('' for an untextured light).
|
||||
* @returns {gdjs.LightRuntimeObject}
|
||||
*/
|
||||
const addLightObject = (runtimeScene, radius) => {
|
||||
const addLightObject = (runtimeScene, radius, texture = '') => {
|
||||
const lightObj = new gdjs.LightRuntimeObject(runtimeScene, {
|
||||
name: 'lightObject',
|
||||
type: 'Lighting::LightObject',
|
||||
@@ -18,7 +19,7 @@ const addLightObject = (runtimeScene, radius) => {
|
||||
content: {
|
||||
radius: radius,
|
||||
color: '#b4b4b4',
|
||||
texture: '',
|
||||
texture: texture,
|
||||
debugMode: false,
|
||||
},
|
||||
});
|
||||
@@ -193,4 +194,44 @@ describe('Light with obstacles around it', function () {
|
||||
expect(indexBuffer[index]).to.be(val);
|
||||
});
|
||||
});
|
||||
|
||||
it('Rotated untextured light ignores obstacles beyond its circular radius.', function () {
|
||||
// An untextured light renders as a circle that never lights anything
|
||||
// beyond ±radius whatever the angle, so an obstacle outside the
|
||||
// ±radius box ([100, 300] x [100, 300]) must not trigger raycasting.
|
||||
light.setPosition(200, 200);
|
||||
light.setAngle(45);
|
||||
obstacle.setPosition(320, 175);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
light.update();
|
||||
|
||||
expect(light._renderer._computeLightVertices().length).to.be(0);
|
||||
});
|
||||
|
||||
it('Rotated textured light is occluded by an obstacle beyond the unrotated radius box.', function () {
|
||||
// A textured light's lit area is the light's square rotated by the
|
||||
// object's angle: at 45°, it extends up to radius * sqrt(2) ≈ 141.42
|
||||
// from the center along the axes. Place the obstacle outside the
|
||||
// unrotated ±radius box ([100, 300] x [100, 300]) but inside the lit
|
||||
// area: it must still be found and occlude the light.
|
||||
const texturedLight = addLightObject(runtimeScene, 100, 'texture.png');
|
||||
texturedLight.setPosition(200, 200);
|
||||
texturedLight.setAngle(45);
|
||||
obstacle.setPosition(320, 175);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
texturedLight.update();
|
||||
|
||||
const vertices = texturedLight._renderer._computeLightVertices();
|
||||
expect(vertices.length).to.be.greaterThan(0);
|
||||
// Rays cast towards the obstacle stop on its left edge (x = 320)
|
||||
// instead of reaching the boundary of the lit area.
|
||||
expect(
|
||||
vertices.some(
|
||||
(vertex) =>
|
||||
Math.abs(vertex[0] - 320) < 0.5 &&
|
||||
vertex[1] >= 175 &&
|
||||
vertex[1] <= 225
|
||||
)
|
||||
).to.be(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -86,6 +86,9 @@ namespace gdjs {
|
||||
|
||||
this._manager =
|
||||
gdjs.NavMeshObstaclesManager.getManager(instanceContainer);
|
||||
}
|
||||
|
||||
override onCreated(): void {
|
||||
this._manager.addCharacter(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,7 @@ namespace gdjs {
|
||||
* @category Behaviors > NavMesh pathfinding
|
||||
*/
|
||||
export class NavMeshObstaclesManager {
|
||||
instanceContainer: gdjs.RuntimeInstanceContainer;
|
||||
obstacles = new Set<NavMeshObstacleRuntimeBehavior>();
|
||||
characters = new Set<NavMeshCharacterRuntimeBehavior>();
|
||||
is3D = false;
|
||||
@@ -56,6 +57,7 @@ namespace gdjs {
|
||||
debuggerRenderer: gdjs.NavMeshDebuggerRenderer | null = null;
|
||||
|
||||
constructor(instanceContainer: gdjs.RuntimeInstanceContainer, sharedData) {
|
||||
this.instanceContainer = instanceContainer;
|
||||
if (!sharedData) {
|
||||
// It can happens when there is no object with the character behavior.
|
||||
return;
|
||||
@@ -134,7 +136,7 @@ namespace gdjs {
|
||||
this.addPolygonsFor(object, positions, indices);
|
||||
}
|
||||
}
|
||||
if (!this.is3D && positions.length > 0) {
|
||||
if (!this.is3D) {
|
||||
this.addGroundFor2D(positions, indices);
|
||||
this.navMeshConfig.walkableClimb = 0.1;
|
||||
this.navMeshConfig.walkableHeight = 2;
|
||||
@@ -208,17 +210,34 @@ namespace gdjs {
|
||||
positions: Array<float>,
|
||||
indices: Array<integer>
|
||||
): void {
|
||||
let minX = Number.POSITIVE_INFINITY;
|
||||
let minY = Number.POSITIVE_INFINITY;
|
||||
let maxX = Number.NEGATIVE_INFINITY;
|
||||
let maxY = Number.NEGATIVE_INFINITY;
|
||||
for (let index = 0; index + 2 < positions.length; index += 3) {
|
||||
const x = positions[index];
|
||||
const y = positions[index + 2];
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
const game = this.instanceContainer.getGame();
|
||||
let minX = 0;
|
||||
let minY = 0;
|
||||
let maxX = game.getGameResolutionWidth();
|
||||
let maxY = game.getGameResolutionHeight();
|
||||
if (positions.length > 0) {
|
||||
minX = Number.POSITIVE_INFINITY;
|
||||
minY = Number.POSITIVE_INFINITY;
|
||||
maxX = Number.NEGATIVE_INFINITY;
|
||||
maxY = Number.NEGATIVE_INFINITY;
|
||||
for (let index = 0; index + 2 < positions.length; index += 3) {
|
||||
const x = positions[index];
|
||||
const y = positions[index + 2];
|
||||
minX = Math.min(minX, x);
|
||||
minY = Math.min(minY, y);
|
||||
maxX = Math.max(maxX, x);
|
||||
maxY = Math.max(maxY, y);
|
||||
}
|
||||
const gameSize = Math.max(
|
||||
game.getGameResolutionWidth(),
|
||||
game.getGameResolutionHeight()
|
||||
);
|
||||
// Extends the area in case it's not closed by obstacles
|
||||
// and characters may be expected to go around them.
|
||||
minX -= gameSize;
|
||||
minY -= gameSize;
|
||||
maxX += gameSize;
|
||||
maxY += gameSize;
|
||||
}
|
||||
const width = maxX - minX;
|
||||
const height = (maxY - minY) * this.inverseSpeedScaleY;
|
||||
|
||||
@@ -112,6 +112,7 @@ describe('gdjs.NavMeshCharacterRuntimeBehavior', function () {
|
||||
return 20;
|
||||
};
|
||||
runtimeScene.addObject(character);
|
||||
character.onCreated();
|
||||
return character;
|
||||
};
|
||||
|
||||
|
||||
@@ -219,6 +219,13 @@ module.exports = {
|
||||
physics2Behavior.getProperties = function (behaviorContent) {
|
||||
var behaviorProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
// The shape decides which dimensions are meaningful and how they
|
||||
// should be labelled, so these properties adapt themselves to it.
|
||||
const shape = behaviorContent.getChild('shape').getStringValue();
|
||||
const isCircleShape = shape === 'Circle';
|
||||
const isEdgeShape = shape === 'Edge';
|
||||
const isPolygonShape = shape === 'Polygon';
|
||||
|
||||
behaviorProperties
|
||||
.getOrCreate('bodyType')
|
||||
.setValue(behaviorContent.getChild('bodyType').getStringValue())
|
||||
@@ -283,12 +290,18 @@ module.exports = {
|
||||
.getOrCreate('shape')
|
||||
.setValue(behaviorContent.getChild('shape').getStringValue())
|
||||
.setType('Choice')
|
||||
.setLabel('Shape')
|
||||
.setLabel(_('Shape'))
|
||||
.setDescription(
|
||||
_(
|
||||
'The shape used for collisions. It gives their meaning to the dimension properties. A polygon is defined by its "Vertices" and positioned according to "Polygon origin".'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.addChoice('Box', _('Box'))
|
||||
.addChoice('Circle', _('Circle'))
|
||||
.addChoice('Edge', _('Edge'))
|
||||
.addChoice('Polygon', _('Polygon'));
|
||||
.addChoice('Polygon', _('Polygon'))
|
||||
.setHasImpactOnOtherProperties(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeDimensionA')
|
||||
.setValue(
|
||||
@@ -299,9 +312,17 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Dimension A')
|
||||
.setLabel(
|
||||
isCircleShape ? _('Radius') : isEdgeShape ? _('Length') : _('Width')
|
||||
)
|
||||
.setDescription(
|
||||
_(
|
||||
'Width of the box, radius of the circle or length of the edge. Use 0 to follow the object size.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
// A polygon is defined by its vertices instead of dimensions.
|
||||
.setHidden(isPolygonShape);
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeDimensionB')
|
||||
.setValue(
|
||||
@@ -311,10 +332,20 @@ module.exports = {
|
||||
.toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Dimension B')
|
||||
.setMeasurementUnit(
|
||||
isEdgeShape
|
||||
? gd.MeasurementUnit.getDegreeAngle()
|
||||
: gd.MeasurementUnit.getPixel()
|
||||
)
|
||||
.setLabel(isEdgeShape ? _('Angle') : _('Height'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Height of the box, or angle of the edge in degrees. Not used by a circle. Use 0 to follow the object height.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
// A circle only needs a radius and a polygon uses its vertices.
|
||||
.setHidden(isPolygonShape || isCircleShape);
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeOffsetX')
|
||||
.setValue(
|
||||
@@ -322,9 +353,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Offset X')
|
||||
.setLabel(_('Shape offset X'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the collision shape relative to the object center, on the X axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeOffsetY')
|
||||
.setValue(
|
||||
@@ -332,9 +368,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Offset Y')
|
||||
.setLabel(_('Shape offset Y'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the collision shape relative to the object center, on the Y axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('polygonOrigin')
|
||||
.setValue(
|
||||
@@ -343,12 +384,16 @@ module.exports = {
|
||||
: 'Center'
|
||||
)
|
||||
.setType('Choice')
|
||||
.setLabel('Polygon Origin')
|
||||
.setLabel(_('Polygon origin'))
|
||||
.setDescription(
|
||||
_('Point of the object the polygon vertices are relative to.')
|
||||
)
|
||||
.addChoice('Center', _('Center'))
|
||||
.addChoice('Origin', _('Origin'))
|
||||
.addChoice('TopLeft', _('TopLeft'))
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
// Only used by the "Polygon" shape.
|
||||
.setHidden(!isPolygonShape);
|
||||
behaviorProperties
|
||||
.getOrCreate('vertices')
|
||||
.setValue(
|
||||
@@ -434,17 +479,31 @@ module.exports = {
|
||||
behaviorProperties
|
||||
.getOrCreate('layers')
|
||||
.setValue(behaviorContent.getChild('layers').getIntValue().toString(10))
|
||||
.setType('Number')
|
||||
.setLabel('Layers')
|
||||
.setType('Bitmask')
|
||||
.addExtraInfo('bitCount=16')
|
||||
.setLabel(_('Layers'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Layers the object belongs to, as a bitmask: layer 1 is 1, layer 2 is 2, layer 3 is 4, and so on up to layer 16 which is 32768.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setGroup(_('Collision filtering'))
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('masks')
|
||||
.setValue(behaviorContent.getChild('masks').getIntValue().toString(10))
|
||||
.setType('Number')
|
||||
.setLabel('Masks')
|
||||
.setType('Bitmask')
|
||||
.addExtraInfo('bitCount=16')
|
||||
.setLabel(_('Masks'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Layers the object can collide with, as a bitmask: layer 1 is 1, layer 2 is 2, layer 3 is 4, and so on up to layer 16 which is 32768.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setGroup(_('Collision filtering'))
|
||||
.setAdvanced(true);
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
|
||||
@@ -285,6 +285,15 @@ module.exports = {
|
||||
behavior.getProperties = function (behaviorContent) {
|
||||
const behaviorProperties = new gd.MapStringPropertyDescriptor();
|
||||
|
||||
// The shape decides which dimensions are meaningful and how they
|
||||
// should be labelled, and the body type decides which collision
|
||||
// layers are used, so these properties adapt themselves to them.
|
||||
const shape = behaviorContent.getChild('shape').getStringValue();
|
||||
const isBoxShape = shape === 'Box';
|
||||
const isMeshShape = shape === 'Mesh';
|
||||
const isStaticBody =
|
||||
behaviorContent.getChild('bodyType').getStringValue() === 'Static';
|
||||
|
||||
behaviorProperties
|
||||
.getOrCreate('object3D')
|
||||
.setValue(behaviorContent.getChild('object3D').getStringValue())
|
||||
@@ -343,13 +352,19 @@ module.exports = {
|
||||
.getOrCreate('shape')
|
||||
.setValue(behaviorContent.getChild('shape').getStringValue())
|
||||
.setType('Choice')
|
||||
.setLabel('Shape')
|
||||
.setLabel(_('Shape'))
|
||||
.setDescription(
|
||||
_(
|
||||
'The shape used for collisions. It gives their meaning to the dimension properties. A capsule or a cylinder is extended along the axis given by "Shape orientation" and a mesh uses the model given by "Simplified 3D model".'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.addChoice('Box', _('Box'))
|
||||
.addChoice('Capsule', _('Capsule'))
|
||||
.addChoice('Sphere', _('Sphere'))
|
||||
.addChoice('Cylinder', _('Cylinder'))
|
||||
.addChoice('Mesh', _('Mesh (works for Static only)'));
|
||||
.addChoice('Mesh', _('Mesh (works for Static only)'))
|
||||
.setHasImpactOnOtherProperties(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('meshShapeResourceName')
|
||||
.setValue(
|
||||
@@ -359,8 +374,8 @@ module.exports = {
|
||||
.addExtraInfo('model3D')
|
||||
.setLabel(_('Simplified 3D model'))
|
||||
.setDescription(_("Leave empty to use object's one"))
|
||||
// Hidden as required to be changed in the full editor.
|
||||
.setHidden(true)
|
||||
// Only used by the "Mesh" shape.
|
||||
.setHidden(!isMeshShape)
|
||||
.setHasImpactOnOtherProperties(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeOrientation')
|
||||
@@ -368,11 +383,16 @@ module.exports = {
|
||||
behaviorContent.getChild('shapeOrientation').getStringValue()
|
||||
)
|
||||
.setType('Choice')
|
||||
.setLabel('Shape orientation')
|
||||
.setLabel(_('Shape orientation'))
|
||||
.setDescription(
|
||||
_('Axis along which the capsule or cylinder is extended.')
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.addChoice('Z', _('Z'))
|
||||
.addChoice('Y', _('Y'))
|
||||
.addChoice('X', _('X'));
|
||||
.addChoice('X', _('X'))
|
||||
// Only capsules and cylinders can be oriented.
|
||||
.setHidden(shape !== 'Capsule' && shape !== 'Cylinder');
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeDimensionA')
|
||||
.setValue(
|
||||
@@ -383,9 +403,15 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Dimension A')
|
||||
.setLabel(isBoxShape ? _('Width') : _('Radius'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Width of the box, or radius of the sphere, capsule or cylinder. Use 0 to follow the object size.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
// The "Mesh" shape uses a 3D model instead of dimensions.
|
||||
.setHidden(isMeshShape);
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeDimensionB')
|
||||
.setValue(
|
||||
@@ -396,9 +422,15 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Dimension B')
|
||||
.setLabel(isBoxShape ? _('Height') : _('Depth'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Height of the box, or size of the capsule or cylinder along its orientation axis. Not used by a sphere. Use 0 to follow the object size.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
// A sphere only needs a radius and a mesh uses a 3D model.
|
||||
.setHidden(isMeshShape || shape === 'Sphere');
|
||||
behaviorProperties
|
||||
.getOrCreate('shapeDimensionC')
|
||||
.setValue(
|
||||
@@ -409,9 +441,15 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape Dimension C')
|
||||
.setLabel(_('Depth'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Depth of the box. Only used by a box. Use 0 to follow the object depth.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
// Only a box has a third dimension to configure.
|
||||
.setHidden(!isBoxShape);
|
||||
if (!behaviorContent.hasChild('shapeOffsetX')) {
|
||||
behaviorContent.addChild('shapeOffsetX').setDoubleValue(0);
|
||||
}
|
||||
@@ -425,10 +463,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape offset X')
|
||||
.setLabel(_('Shape offset X'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the collision shape relative to the object center, on the X axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setAdvanced(true)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
if (!behaviorContent.hasChild('shapeOffsetY')) {
|
||||
behaviorContent.addChild('shapeOffsetY').setDoubleValue(0);
|
||||
}
|
||||
@@ -442,9 +484,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape offset Y')
|
||||
.setLabel(_('Shape offset Y'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the collision shape relative to the object center, on the Y axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
if (!behaviorContent.hasChild('shapeOffsetZ')) {
|
||||
behaviorContent.addChild('shapeOffsetZ').setDoubleValue(0);
|
||||
}
|
||||
@@ -458,10 +505,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Shape offset Z')
|
||||
.setLabel(_('Shape offset Z'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the collision shape relative to the object center, on the Z axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setAdvanced(true)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
if (!behaviorContent.hasChild('massCenterOffsetX')) {
|
||||
behaviorContent.addChild('massCenterOffsetX').setDoubleValue(0);
|
||||
}
|
||||
@@ -475,10 +526,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Center of mass offset X')
|
||||
.setLabel(_('Center of mass X'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the center of mass relative to the object center, on the X axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setAdvanced(true)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
if (!behaviorContent.hasChild('massCenterOffsetY')) {
|
||||
behaviorContent.addChild('massCenterOffsetY').setDoubleValue(0);
|
||||
}
|
||||
@@ -492,10 +547,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Center of mass offset Y')
|
||||
.setLabel(_('Center of mass Y'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the center of mass relative to the object center, on the Y axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setAdvanced(true)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
if (!behaviorContent.hasChild('massCenterOffsetZ')) {
|
||||
behaviorContent.addChild('massCenterOffsetZ').setDoubleValue(0);
|
||||
}
|
||||
@@ -509,10 +568,14 @@ module.exports = {
|
||||
)
|
||||
.setType('Number')
|
||||
.setMeasurementUnit(gd.MeasurementUnit.getPixel())
|
||||
.setLabel('Center of mass offset Z')
|
||||
.setLabel(_('Center of mass Z'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Offset of the center of mass relative to the object center, on the Z axis.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setAdvanced(true)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('density')
|
||||
.setValue(
|
||||
@@ -627,19 +690,39 @@ module.exports = {
|
||||
.setValue(
|
||||
behaviorContent.getChild('layers').getIntValue().toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Layers')
|
||||
.setType('Bitmask')
|
||||
// Static objects use the layers 1 to 4 and moving ones the layers
|
||||
// 5 to 8, so that they never register in the wrong layer group.
|
||||
.addExtraInfo('bitCount=4')
|
||||
.addExtraInfo(isStaticBody ? 'firstBit=0' : 'firstBit=4')
|
||||
.setLabel(_('Layers'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Layers the object belongs to, as a bitmask. Static objects use the layers 1 to 4 (1, 2, 4 and 8) and moving ones the layers 5 to 8 (16, 32, 64 and 128).'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setGroup(_('Collision filtering'))
|
||||
.setAdvanced(true);
|
||||
behaviorProperties
|
||||
.getOrCreate('masks')
|
||||
.setValue(
|
||||
behaviorContent.getChild('masks').getIntValue().toString(10)
|
||||
)
|
||||
.setType('Number')
|
||||
.setLabel('Masks')
|
||||
.setType('Bitmask')
|
||||
.addExtraInfo('bitCount=8')
|
||||
.setLabel(_('Masks'))
|
||||
.setDescription(
|
||||
_(
|
||||
'Layers the object can collide with, as a bitmask: layer 1 is 1, layer 2 is 2, layer 3 is 4, and so on up to layer 8 which is 128. Not used by static objects, which accept every collision.'
|
||||
)
|
||||
)
|
||||
.setQuickCustomizationVisibility(gd.QuickCustomization.Hidden)
|
||||
.setHidden(true); // Hidden as required to be changed in the full editor.
|
||||
.setGroup(_('Collision filtering'))
|
||||
.setAdvanced(true)
|
||||
// Static objects accept every collision: it's the mask of the moving
|
||||
// objects that matters.
|
||||
.setHidden(isStaticBody);
|
||||
|
||||
return behaviorProperties;
|
||||
};
|
||||
|
||||
@@ -226,7 +226,7 @@ module.exports = {
|
||||
'CheckSaveExistsInStorage',
|
||||
_('Check if a save exists in device storage'),
|
||||
_(
|
||||
'Check if a save with the given name exists in the device storage, and store the result (yes/no) in a variable. The check is asynchronous: use the condition "Save existence check completed" to know when the result is available.'
|
||||
'Check if a save with the given name exists in the device storage, and store the result (true/false) in a variable. The check is asynchronous: use the condition "Save existence check completed" to know when the result is available.'
|
||||
),
|
||||
_(
|
||||
'Check if save _PARAM1_ exists in device storage (result in _PARAM2_)'
|
||||
@@ -239,7 +239,7 @@ module.exports = {
|
||||
.addParameter('string', _('Storage name of the save to check'), '', false)
|
||||
.addParameter(
|
||||
'variable',
|
||||
_('Variable where to store the result (yes/no)'),
|
||||
_('Variable where to store the result (true/false)'),
|
||||
'',
|
||||
false
|
||||
)
|
||||
|
||||
@@ -9,6 +9,9 @@ namespace gdjs {
|
||||
!!attachment &&
|
||||
attachment instanceof spine.PointAttachment;
|
||||
|
||||
/** `renderMeshes` is private in the Spine typings, but it must be wrapped. */
|
||||
type SpineWithRenderMeshes = { renderMeshes: () => void };
|
||||
|
||||
/**
|
||||
* @category Renderers > Spine
|
||||
*/
|
||||
@@ -16,6 +19,9 @@ namespace gdjs {
|
||||
private _object: gdjs.SpineRuntimeObject;
|
||||
private _rendererObject: spine.Spine | PIXI.Container;
|
||||
private _isAnimationComplete = true;
|
||||
private _areMeshesOutdated = true;
|
||||
private _areScaledLocalBoundsOutdated = true;
|
||||
private _scaledLocalBounds = { x: 0, y: 0, width: 0, height: 0 };
|
||||
|
||||
/**
|
||||
* @param runtimeObject The object to render
|
||||
@@ -29,6 +35,7 @@ namespace gdjs {
|
||||
this._rendererObject = this.constructRendererObject();
|
||||
if (isSpine(this._rendererObject)) {
|
||||
this._rendererObject.autoUpdate = false;
|
||||
this._cacheMeshesUntilPoseChanges(this._rendererObject);
|
||||
}
|
||||
|
||||
this.updatePosition();
|
||||
@@ -42,6 +49,44 @@ namespace gdjs {
|
||||
.addRendererObject(this._rendererObject, runtimeObject.getZOrder());
|
||||
}
|
||||
|
||||
/**
|
||||
* `Spine.updateTransform` regenerates every mesh of the skeleton, which is
|
||||
* the most expensive thing a Spine object does. PixiJS calls it both when
|
||||
* the local bounds are read (to update the AABB) and when the scene graph
|
||||
* is rendered, but the meshes only depend on the skeleton pose: make the
|
||||
* extra calls a no-op until the pose changes.
|
||||
*/
|
||||
private _cacheMeshesUntilPoseChanges(spineObject: spine.Spine): void {
|
||||
const spineObjectWithRenderMeshes =
|
||||
spineObject as unknown as SpineWithRenderMeshes;
|
||||
const renderMeshes = spineObjectWithRenderMeshes.renderMeshes;
|
||||
|
||||
if (typeof renderMeshes !== 'function') {
|
||||
throw new Error(
|
||||
'Spine.renderMeshes is missing: the mesh caching of SpineRuntimeObjectPixiRenderer must be updated for this version of the Spine runtime.'
|
||||
);
|
||||
}
|
||||
|
||||
spineObjectWithRenderMeshes.renderMeshes = () => {
|
||||
if (!this._areMeshesOutdated) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._areMeshesOutdated = false;
|
||||
renderMeshes.call(spineObject);
|
||||
};
|
||||
|
||||
const previousAfterUpdateWorldTransforms =
|
||||
spineObject.afterUpdateWorldTransforms;
|
||||
|
||||
spineObject.afterUpdateWorldTransforms = (object) => {
|
||||
previousAfterUpdateWorldTransforms(object);
|
||||
|
||||
this._areMeshesOutdated = true;
|
||||
this._areScaledLocalBoundsOutdated = true;
|
||||
};
|
||||
}
|
||||
|
||||
updateAnimation(timeDelta: float) {
|
||||
if (!isSpine(this._rendererObject)) {
|
||||
return;
|
||||
@@ -53,15 +98,42 @@ namespace gdjs {
|
||||
return this._rendererObject;
|
||||
}
|
||||
|
||||
getOriginOffset(): PIXI.Point {
|
||||
if (!isSpine(this._rendererObject)) return new PIXI.Point(0, 0);
|
||||
/**
|
||||
* The local bounds of the skeleton, scaled to the object size. Reading them
|
||||
* walks every mesh of the skeleton, so they are only recomputed when the
|
||||
* pose or the scale changed. The returned object must not be modified.
|
||||
*
|
||||
* The Spine `boundsProvider` is not used for this: none of its
|
||||
* implementations returns the bounds of the current frame, and setting one
|
||||
* disables `interactiveChildren` and overwrites `hitArea`.
|
||||
*/
|
||||
private _getScaledLocalBounds(): {
|
||||
x: float;
|
||||
y: float;
|
||||
width: float;
|
||||
height: float;
|
||||
} {
|
||||
if (this._areScaledLocalBoundsOutdated) {
|
||||
this._areScaledLocalBoundsOutdated = false;
|
||||
|
||||
const localBounds = this._rendererObject.getLocalBounds(undefined, true);
|
||||
const localBounds = this._rendererObject.getLocalBounds(
|
||||
undefined,
|
||||
true
|
||||
);
|
||||
const scaleX = this._rendererObject.scale.x;
|
||||
const scaleY = this._rendererObject.scale.y;
|
||||
const scaledLocalBounds = this._scaledLocalBounds;
|
||||
scaledLocalBounds.x = localBounds.x * scaleX;
|
||||
scaledLocalBounds.y = localBounds.y * scaleY;
|
||||
scaledLocalBounds.width = localBounds.width * scaleX;
|
||||
scaledLocalBounds.height = localBounds.height * scaleY;
|
||||
}
|
||||
|
||||
return new PIXI.Point(
|
||||
localBounds.x * this._rendererObject.scale.x,
|
||||
localBounds.y * this._rendererObject.scale.y
|
||||
);
|
||||
return this._scaledLocalBounds;
|
||||
}
|
||||
|
||||
getOriginOffset(): { x: float; y: float } {
|
||||
return this._getScaledLocalBounds();
|
||||
}
|
||||
|
||||
onDestroy(): void {
|
||||
@@ -83,6 +155,7 @@ namespace gdjs {
|
||||
this._rendererObject.scale.y = this._object.isFlippedY()
|
||||
? -scaleY
|
||||
: scaleY;
|
||||
this._areScaledLocalBoundsOutdated = true;
|
||||
}
|
||||
|
||||
updatePosition(): void {
|
||||
@@ -99,31 +172,33 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getWidth(): float {
|
||||
return this._rendererObject.width;
|
||||
return this._getScaledLocalBounds().width;
|
||||
}
|
||||
|
||||
getHeight(): float {
|
||||
return this._rendererObject.height;
|
||||
return this._getScaledLocalBounds().height;
|
||||
}
|
||||
|
||||
setWidth(width: float): void {
|
||||
this._rendererObject.width = width;
|
||||
this._areScaledLocalBoundsOutdated = true;
|
||||
}
|
||||
|
||||
setHeight(height: float): void {
|
||||
this._rendererObject.height = height;
|
||||
this._areScaledLocalBoundsOutdated = true;
|
||||
}
|
||||
|
||||
getUnscaledWidth(): float {
|
||||
return Math.abs(
|
||||
(this._rendererObject.width * this._object._originalScale) /
|
||||
(this.getWidth() * this._object._originalScale) /
|
||||
this._rendererObject.scale.x
|
||||
);
|
||||
}
|
||||
|
||||
getUnscaledHeight(): float {
|
||||
return Math.abs(
|
||||
(this._rendererObject.height * this._object._originalScale) /
|
||||
(this.getHeight() * this._object._originalScale) /
|
||||
this._rendererObject.scale.y
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1128,7 +1128,7 @@ const defineSimpleTileMap = function (extension, _, gd) {
|
||||
objectSimpleTileMap
|
||||
)
|
||||
.setCategory('General')
|
||||
.setAssetStoreTag('Tilemap')
|
||||
.setAssetStoreTag('tilemap')
|
||||
.setOpenFullEditorLabel(_('Edit tileset and collisions'))
|
||||
.addDefaultBehavior('TileMap::TileMapBehavior')
|
||||
.addDefaultBehavior('ResizableCapability::ResizableBehavior')
|
||||
|
||||
@@ -83,7 +83,7 @@ module.exports = {
|
||||
'',
|
||||
'JsPlatform/Extensions/tween_behavior32.png'
|
||||
)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('From value'))
|
||||
.addParameter('expression', _('To value'))
|
||||
@@ -115,7 +115,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Initial value'), '', false)
|
||||
.addParameter('expression', _('Final value'), '', false)
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
|
||||
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
|
||||
@@ -142,7 +142,7 @@ module.exports = {
|
||||
.addParameter('scenevar', _('The variable to tween'), '', false)
|
||||
.addParameter('expression', _('Final value'), '', false)
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
|
||||
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
|
||||
@@ -166,7 +166,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
|
||||
.addParameter('scenevar', _('The variable to tween'), '', false)
|
||||
.addParameter('expression', _('Final value'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
@@ -192,7 +192,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('From value'), '', false)
|
||||
.addParameter('expression', _('To value'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter('yesorno', _('Exponential interpolation'), '', false)
|
||||
@@ -221,7 +221,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('From value'), '', false)
|
||||
.addParameter('expression', _('To value'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter('yesorno', _('Exponential interpolation'), '', false)
|
||||
@@ -253,7 +253,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Target Y position'), '', false)
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
|
||||
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
|
||||
@@ -276,7 +276,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Target X position'), '', false)
|
||||
.addParameter('expression', _('Target Y position'), '', false)
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
@@ -303,7 +303,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Target zoom'), '', false)
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
|
||||
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
|
||||
@@ -325,7 +325,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
|
||||
.addParameter('expression', _('Target zoom'), '', false)
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
@@ -352,7 +352,7 @@ module.exports = {
|
||||
.addParameter('expression', _('Target rotation (in degrees)'), '', false)
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.getCodeExtraInformation()
|
||||
.setIncludeFile('Extensions/TweenBehavior/TweenManager.js')
|
||||
.addIncludeFile('Extensions/TweenBehavior/tweentools.js')
|
||||
@@ -374,7 +374,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'sceneTween')
|
||||
.addParameter('expression', _('Target rotation (in degrees)'), '', false)
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
@@ -402,7 +402,7 @@ module.exports = {
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('layerEffectName', _('Effect name'))
|
||||
.addParameter('layerEffectParameterName', _('Property name'))
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
@@ -430,7 +430,7 @@ module.exports = {
|
||||
.addParameter('layer', _('Layer'), '', true)
|
||||
.addParameter('layerEffectName', _('Effect name'))
|
||||
.addParameter('layerEffectParameterName', _('Property name'))
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.getCodeExtraInformation()
|
||||
@@ -652,7 +652,7 @@ module.exports = {
|
||||
.addParameter('objectvar', _('Object variable'), '', false)
|
||||
.addParameter('expression', _('From value'), '', false)
|
||||
.addParameter('expression', _('To value'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -686,7 +686,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('objectvar', _('Object variable'), '', false)
|
||||
.addParameter('expression', _('To value'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -718,7 +718,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('objectvar', _('Object variable'), '', false)
|
||||
.addParameter('expression', _('To value'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -750,7 +750,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('From value'), '', false)
|
||||
.addParameter('expression', _('To value'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter('yesorno', _('Exponential interpolation'), '', false)
|
||||
@@ -784,7 +784,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To X'), '', false)
|
||||
.addParameter('expression', _('To Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -814,7 +814,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To X'), '', false)
|
||||
.addParameter('expression', _('To Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -847,7 +847,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To X'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -878,7 +878,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To X'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -911,7 +911,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To Z'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -943,7 +943,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('3D capability'), 'Scene3D::Base3DBehavior')
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To Z'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -974,7 +974,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To width'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1003,7 +1003,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To width'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1034,7 +1034,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To height'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1063,7 +1063,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To height'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1096,7 +1096,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To depth'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1128,7 +1128,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('3D capability'), 'Scene3D::Base3DBehavior')
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To depth'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1161,7 +1161,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1192,7 +1192,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1223,7 +1223,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1252,7 +1252,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1284,7 +1284,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('3D capability'), 'Scene3D::Base3DBehavior')
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1316,7 +1316,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('3D capability'), 'Scene3D::Base3DBehavior')
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To angle (in degrees)'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1350,7 +1350,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale X'), '', false)
|
||||
.addParameter('expression', _('To scale Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1386,7 +1386,7 @@ module.exports = {
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale X'), '', false)
|
||||
.addParameter('expression', _('To scale Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1419,7 +1419,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1454,7 +1454,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale X'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1487,7 +1487,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale X'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1522,7 +1522,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1555,7 +1555,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To scale Y'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1590,7 +1590,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To character size'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1621,7 +1621,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To character size'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1654,7 +1654,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To opacity'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1688,7 +1688,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('expression', _('To opacity'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1726,7 +1726,7 @@ module.exports = {
|
||||
.addParameter('expression', _('To value'), '', false)
|
||||
.addParameter('objectEffectName', _('Effect name'))
|
||||
.addParameter('objectEffectParameterName', _('Property name'))
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1764,7 +1764,7 @@ module.exports = {
|
||||
.addParameter('color', _('To color'), '', false)
|
||||
.addParameter('objectEffectName', _('Effect name'))
|
||||
.addParameter('objectEffectParameterName', _('Property name'))
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1797,7 +1797,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('color', _('To color'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1838,7 +1838,7 @@ module.exports = {
|
||||
.addParameter('behavior', _('Behavior'), 'TweenBehavior', false)
|
||||
.addParameter('identifier', _('Tween Identifier'), 'objectTween')
|
||||
.addParameter('color', _('To color'), '', false)
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1897,7 +1897,7 @@ module.exports = {
|
||||
false
|
||||
)
|
||||
.setDefaultValue('-1')
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in milliseconds)'), '', false)
|
||||
.addParameter(
|
||||
@@ -1944,7 +1944,7 @@ module.exports = {
|
||||
false
|
||||
)
|
||||
.setDefaultValue('-1')
|
||||
.addParameter('stringWithSelector', _('Easing'), easingChoices, false)
|
||||
.addParameter('easing', _('Easing'), easingChoices, false)
|
||||
.setDefaultValue('linear')
|
||||
.addParameter('expression', _('Duration (in seconds)'), '', false)
|
||||
.addParameter(
|
||||
|
||||
@@ -603,6 +603,7 @@ namespace gdjs {
|
||||
protected onFinish: () => void;
|
||||
protected timeSource: TimeSource;
|
||||
protected isPaused = false;
|
||||
protected isFinished = false;
|
||||
protected tweenInformation: TweenInformation;
|
||||
|
||||
constructor(
|
||||
@@ -632,6 +633,13 @@ namespace gdjs {
|
||||
this.elapsedTime + this.timeSource.getElapsedTime() / 1000,
|
||||
this.totalDuration
|
||||
);
|
||||
// A tween with a duration of 0 (or less) finishes on its first step.
|
||||
// The tween is not considered finished at creation, so that
|
||||
// `_updateValue` is called at least once to apply the targeted value
|
||||
// and call `onFinish`.
|
||||
if (this.elapsedTime >= this.totalDuration) {
|
||||
this.isFinished = true;
|
||||
}
|
||||
this._updateValue();
|
||||
}
|
||||
|
||||
@@ -643,11 +651,12 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
hasFinished(): boolean {
|
||||
return this.elapsedTime === this.totalDuration;
|
||||
return this.isFinished;
|
||||
}
|
||||
|
||||
stop(jumpToDest: boolean): void {
|
||||
this.elapsedTime = this.totalDuration;
|
||||
this.isFinished = true;
|
||||
if (jumpToDest) {
|
||||
this._updateValue();
|
||||
}
|
||||
@@ -662,12 +671,17 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getProgress(): float {
|
||||
if (this.totalDuration <= 0) {
|
||||
// Avoid a division by 0 for tweens with a duration of 0.
|
||||
return this.isFinished ? 1 : 0;
|
||||
}
|
||||
return this.elapsedTime / this.totalDuration;
|
||||
}
|
||||
|
||||
// To be used for network synchronization.
|
||||
updateElapsedTime(newElapsedTime: float): void {
|
||||
this.elapsedTime = newElapsedTime;
|
||||
this.isFinished = newElapsedTime >= this.totalDuration;
|
||||
}
|
||||
|
||||
abstract getNetworkSyncData(): TweenInstanceNetworkSyncData<T>;
|
||||
|
||||
@@ -28,6 +28,24 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
});
|
||||
|
||||
it('can play a tween with a duration of 0', () => {
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(runtimeScene, 'MyTween', 600, '', 'linear', 0);
|
||||
|
||||
// Tween actions don't change the value directly.
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(200);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(false);
|
||||
|
||||
// The tween reaches the end on its first step.
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(tween.sceneTweenIsPlaying(runtimeScene, 'MyTween')).to.be(false);
|
||||
expect(tween.sceneTweenHasFinished(runtimeScene, 'MyTween')).to.be(true);
|
||||
expect(camera.getCameraRotation(runtimeScene, '', 0)).to.be(600);
|
||||
expect(tween.getValue(runtimeScene, 'MyTween')).to.be(600);
|
||||
expect(tween.getProgress(runtimeScene, 'MyTween')).to.be(1);
|
||||
});
|
||||
|
||||
it('can play a tween till the end', () => {
|
||||
camera.setCameraRotation(runtimeScene, 200, '', 0);
|
||||
tween.tweenCameraRotation2(
|
||||
|
||||
@@ -244,6 +244,42 @@ describe('gdjs.TweenRuntimeBehavior', () => {
|
||||
expect(object.getX()).to.be(123);
|
||||
});
|
||||
|
||||
it('can play a tween with a duration of 0', () => {
|
||||
object.setPosition(200, 300);
|
||||
|
||||
behavior.addObjectPositionXTween2('MyTween', 600, 'linear', 0, false);
|
||||
|
||||
// Tween actions don't change the value directly.
|
||||
expect(object.getX()).to.be(200);
|
||||
expect(behavior.getValue('MyTween')).to.be(200);
|
||||
expect(behavior.isPlaying('MyTween')).to.be(true);
|
||||
expect(behavior.hasFinished('MyTween')).to.be(false);
|
||||
|
||||
// The tween reaches the end on its first step.
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(behavior.isPlaying('MyTween')).to.be(false);
|
||||
expect(behavior.hasFinished('MyTween')).to.be(true);
|
||||
expect(object.getX()).to.be(600);
|
||||
expect(behavior.getValue('MyTween')).to.be(600);
|
||||
expect(behavior.getProgress('MyTween')).to.be(1);
|
||||
|
||||
// The value is not set to the targeted value over and over
|
||||
// after the tween is finished.
|
||||
object.setX(123);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(object.getX()).to.be(123);
|
||||
});
|
||||
|
||||
it('can remove the object at the end of a tween with a duration of 0', () => {
|
||||
object.setPosition(200, 300);
|
||||
|
||||
behavior.addObjectPositionXTween2('MyTween', 600, 'linear', 0, true);
|
||||
|
||||
expect(runtimeScene.getInstancesCountOnScene('Object')).to.be(1);
|
||||
runtimeScene.renderAndStep(1000 / 60);
|
||||
expect(runtimeScene.getInstancesCountOnScene('Object')).to.be(0);
|
||||
});
|
||||
|
||||
it('can remove the object at the end', () => {
|
||||
object.setPosition(200, 300);
|
||||
|
||||
|
||||
@@ -413,8 +413,7 @@ bool MetadataDeclarationHelper::IsBehaviorLifecycleEventsFunction(
|
||||
*/
|
||||
bool MetadataDeclarationHelper::IsObjectLifecycleEventsFunction(
|
||||
const gd::String &functionName) {
|
||||
return functionName == "onCreated" || functionName == "doStepPostEvents" ||
|
||||
functionName == "onDestroy" || functionName == "onHotReloading";
|
||||
return gd::EventsBasedObject::IsObjectLifecycleEventsFunction(functionName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -109,7 +109,8 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
/*includeCaptureManager*/ false,
|
||||
/*includeInAppTutorialMessage*/ false,
|
||||
exportedProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
includesFiles,
|
||||
resourcesFiles);
|
||||
|
||||
// Export files for free function, object and behaviors
|
||||
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
|
||||
@@ -135,11 +136,14 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//...and export it
|
||||
gd::SerializerElement noRuntimeGameOptions;
|
||||
//...and export it, with the setup options passed to the gdjs.RuntimeGame.
|
||||
gd::SerializerElement runtimeGameOptions;
|
||||
runtimeGameOptions.AddChild("runtimeFilesBaseUrl")
|
||||
.SetStringValue(
|
||||
ExporterHelper::GetExportedRuntimeFilesBaseUrl(fs, gdjsRoot));
|
||||
std::vector<gd::InGameEditorResourceMetadata> noInGameEditorResources;
|
||||
helper.ExportProjectData(fs, exportedProject, codeOutputDir + "/data.js",
|
||||
noRuntimeGameOptions, false, noInGameEditorResources);
|
||||
runtimeGameOptions, false, noInGameEditorResources);
|
||||
includesFiles.push_back(codeOutputDir + "/data.js");
|
||||
|
||||
helper.ExportIncludesAndLibs(includesFiles, exportDir, false);
|
||||
@@ -157,7 +161,7 @@ bool Exporter::ExportWholePixiProject(const ExportOptions &options) {
|
||||
includesFiles,
|
||||
usedExtensionsResult.GetUsedSourceFiles(),
|
||||
/*nonRuntimeScriptsCacheBurst=*/0,
|
||||
"")) {
|
||||
"gdjs.runtimeGameOptions")) {
|
||||
gd::LogError(_("Error during export:\n") + lastError);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -193,7 +193,8 @@ bool ExporterHelper::ExportProjectForPixiPreview(
|
||||
/*includeInAppTutorialMessage*/
|
||||
!options.inAppTutorialMessageInPreview.empty(),
|
||||
immutableProject.GetLoadingScreen().GetGDevelopLogoStyle(),
|
||||
includesFiles);
|
||||
includesFiles,
|
||||
resourcesFiles);
|
||||
|
||||
// Export files for free function, object and behaviors
|
||||
for (const auto &includeFile : usedExtensionsResult.GetUsedIncludeFiles()) {
|
||||
@@ -375,6 +376,8 @@ void ExporterHelper::SerializeRuntimeGameOptions(
|
||||
gd::SerializerElement &runtimeGameOptions) {
|
||||
// Create the setup options passed to the gdjs.RuntimeGame
|
||||
runtimeGameOptions.AddChild("isPreview").SetBoolValue(true);
|
||||
runtimeGameOptions.AddChild("runtimeFilesBaseUrl")
|
||||
.SetStringValue(GetExportedRuntimeFilesBaseUrl(fs, gdjsRoot));
|
||||
|
||||
auto &initialRuntimeGameStatus =
|
||||
runtimeGameOptions.AddChild("initialRuntimeGameStatus");
|
||||
@@ -864,6 +867,20 @@ bool ExporterHelper::ExportCordovaFiles(const gd::Project &project,
|
||||
.FindAndReplace("*", "");
|
||||
};
|
||||
|
||||
auto makeOrientationPreference = [&project]() {
|
||||
const gd::String &orientation = project.GetOrientation();
|
||||
if (orientation != "landscape" && orientation != "portrait") {
|
||||
return gd::String("");
|
||||
}
|
||||
|
||||
// Declare the orientation at build time so that the native app only
|
||||
// supports it. This is required on iPad: an app declaring support for all
|
||||
// orientations is a "multitasking" app, for which iOS ignores any
|
||||
// orientation lock requested at runtime.
|
||||
return "<preference name=\"Orientation\" value=\"" + orientation +
|
||||
"\" />";
|
||||
};
|
||||
|
||||
gd::String str =
|
||||
fs.ReadFile(gdjsRoot + "/Runtime/Cordova/config.xml")
|
||||
.FindAndReplace("GDJS_PROJECTNAME",
|
||||
@@ -874,7 +891,9 @@ bool ExporterHelper::ExportCordovaFiles(const gd::Project &project,
|
||||
gd::Serializer::ToEscapedXMLString(project.GetPackageName()))
|
||||
.FindAndReplace("GDJS_PROJECTVERSION", project.GetVersion())
|
||||
.FindAndReplace("<!-- GDJS_ICONS_ANDROID -->", makeIconsAndroid())
|
||||
.FindAndReplace("<!-- GDJS_ICONS_IOS -->", makeIconsIos());
|
||||
.FindAndReplace("<!-- GDJS_ICONS_IOS -->", makeIconsIos())
|
||||
.FindAndReplace("<!-- GDJS_ORIENTATION -->",
|
||||
makeOrientationPreference());
|
||||
|
||||
gd::String plugins = "";
|
||||
auto dependenciesAndExtensions =
|
||||
@@ -1154,7 +1173,8 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
bool includeCaptureManager,
|
||||
bool includeInAppTutorialMessage,
|
||||
gd::String gdevelopLogoStyle,
|
||||
std::vector<gd::String> &includesFiles) {
|
||||
std::vector<gd::String> &includesFiles,
|
||||
std::vector<gd::String> &resourcesFiles) {
|
||||
// First, do not forget common includes (they must be included before events
|
||||
// generated code files).
|
||||
InsertUnique(includesFiles, "libs/jshashtable.js");
|
||||
@@ -1244,8 +1264,12 @@ void ExporterHelper::AddLibsInclude(bool pixiRenderers,
|
||||
if (pixiInThreeRenderers || isInGameEdition) {
|
||||
InsertUnique(includesFiles, "pixi-renderers/three.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/ThreeAddons.js");
|
||||
InsertUnique(includesFiles, "pixi-renderers/draco/gltf/draco_decoder.wasm");
|
||||
InsertUnique(includesFiles,
|
||||
// The Draco decoder files are not included with a script tag: they are
|
||||
// fetched by the DRACOLoader (of ThreeAddons.js) when a 3D model
|
||||
// compressed with Draco must be read.
|
||||
InsertUnique(resourcesFiles,
|
||||
"pixi-renderers/draco/gltf/draco_decoder.wasm");
|
||||
InsertUnique(resourcesFiles,
|
||||
"pixi-renderers/draco/gltf/draco_wasm_wrapper.js");
|
||||
// Extensions in JS may use it.
|
||||
InsertUnique(includesFiles, "Extensions/3D/Scene3DTools.js");
|
||||
@@ -1372,6 +1396,19 @@ bool ExporterHelper::ExportScenesEventsCode(
|
||||
return true;
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::GetExportedRuntimeFilesBaseUrl(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot) {
|
||||
// Same convention as in `GetExportedIncludeFilename`: the game engine files
|
||||
// keep, relative to the "<GDJS Root>/Runtime" folder, the same relative
|
||||
// path when exported. Some file systems use a URL for `gdjsRoot`, and keep
|
||||
// absolute URLs pointing to the server the game engine files are served
|
||||
// from.
|
||||
gd::String runtimeFilesBaseUrl = gdjsRoot + "/Runtime/";
|
||||
fs.MakeRelative(runtimeFilesBaseUrl, gdjsRoot + "/Runtime/");
|
||||
|
||||
return runtimeFilesBaseUrl.empty() ? "./" : runtimeFilesBaseUrl;
|
||||
}
|
||||
|
||||
gd::String ExporterHelper::GetExportedIncludeFilename(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst) {
|
||||
|
||||
@@ -544,7 +544,9 @@ class ExporterHelper {
|
||||
gd::String exportDir);
|
||||
|
||||
/**
|
||||
* \brief Add libraries files to the list of includes.
|
||||
* \brief Add libraries files to the list of includes, and the files they
|
||||
* need at runtime to the list of resources files (these are copied next to
|
||||
* the game, but not included with a script tag).
|
||||
*/
|
||||
void AddLibsInclude(bool pixiRenderers,
|
||||
bool pixiInThreeRenderers,
|
||||
@@ -555,7 +557,8 @@ class ExporterHelper {
|
||||
bool includeCaptureManager,
|
||||
bool includeInAppTutorialMessage,
|
||||
gd::String gdevelopLogoStyle,
|
||||
std::vector<gd::String> &includesFiles);
|
||||
std::vector<gd::String> &includesFiles,
|
||||
std::vector<gd::String> &resourcesFiles);
|
||||
|
||||
/**
|
||||
* \brief Remove include files that are Pixi renderers.
|
||||
@@ -727,6 +730,19 @@ class ExporterHelper {
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot,
|
||||
const gd::String &include, unsigned int nonRuntimeScriptsCacheBurst = 0);
|
||||
|
||||
/**
|
||||
* \brief Returns the base URL (or relative path) from which the game engine
|
||||
* files are served in the exported game or preview.
|
||||
*
|
||||
* Most of the time, they are copied next to the game index.html (in which
|
||||
* case this is "./"), but some previews serve them from a server.
|
||||
*
|
||||
* \param fs The abstract file system to use
|
||||
* \param gdjsRoot The root directory of GDJS, used to copy runtime files.
|
||||
*/
|
||||
static gd::String GetExportedRuntimeFilesBaseUrl(
|
||||
gd::AbstractFileSystem &fs, const gd::String &gdjsRoot);
|
||||
|
||||
/**
|
||||
* \brief Change the directory where code files are generated.
|
||||
*
|
||||
|
||||
@@ -46,8 +46,10 @@
|
||||
|
||||
<preference name="SwiftVersion" value="5.3" />
|
||||
|
||||
<!-- Required by admob-plus plugin > version 2 -->
|
||||
<preference name="deployment-target" value="13.0" />
|
||||
<!-- Apple requires a minimum OS version of 15.0 for App Store uploads
|
||||
(ITMS-90068, enforced from Spring 2027) and Xcode 27 refuses to build
|
||||
anything lower. -->
|
||||
<preference name="deployment-target" value="15.0" />
|
||||
|
||||
<!-- GDJS_ICONS_IOS -->
|
||||
</platform>
|
||||
@@ -56,6 +58,8 @@
|
||||
|
||||
<preference name="BackgroundColor" value="0xff000000" />
|
||||
|
||||
<!-- GDJS_ORIENTATION -->
|
||||
|
||||
<!-- Android Fullscreen -->
|
||||
<preference name="Fullscreen" value="true" />
|
||||
|
||||
|
||||
@@ -265,6 +265,48 @@ namespace gdjs {
|
||||
// Keep the pickers intact for interaction - they're invisible anyway
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the near-infinite white guide lines shown by the Three.js TransformControls
|
||||
* when an axis is hovered or dragged (helpers named X/Y/Z for translate/scale,
|
||||
* AXIS for rotate). They cross the whole scene, which is distracting -
|
||||
* especially when manipulating small objects.
|
||||
*/
|
||||
function patchAxisGuideLinesOnTransformControlsGizmos(
|
||||
controls: THREE_ADDONS.TransformControls
|
||||
) {
|
||||
const gizmo = (controls as any)._gizmo;
|
||||
if (!gizmo || !gizmo.helper) return;
|
||||
|
||||
// Only the axis guide lines are removed: the other helpers (START/END/DELTA,
|
||||
// giving a discreet feedback of the movement while dragging) are kept.
|
||||
const guideLineNamesByMode = {
|
||||
translate: ['X', 'Y', 'Z'],
|
||||
rotate: ['AXIS'],
|
||||
scale: ['X', 'Y', 'Z'],
|
||||
};
|
||||
|
||||
Object.keys(guideLineNamesByMode).forEach((mode) => {
|
||||
const helperGroup = gizmo.helper[mode];
|
||||
if (!helperGroup || !helperGroup.children) return;
|
||||
|
||||
const guideLines = helperGroup.children.filter((child) =>
|
||||
guideLineNamesByMode[mode].includes(child.name)
|
||||
);
|
||||
|
||||
guideLines.forEach((guideLine) => {
|
||||
helperGroup.remove(guideLine);
|
||||
// Geometries and materials are cloned per handle by TransformControls,
|
||||
// so they can safely be disposed.
|
||||
if (guideLine.geometry) guideLine.geometry.dispose();
|
||||
if (Array.isArray(guideLine.material)) {
|
||||
guideLine.material.forEach((material) => material.dispose());
|
||||
} else if (guideLine.material) {
|
||||
guideLine.material.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const getSvgIconUrl = (game: RuntimeGame, resourceName: string) => {
|
||||
const resource = game.getResourceLoader().getResource(resourceName);
|
||||
if (!resource) return '';
|
||||
@@ -549,6 +591,22 @@ namespace gdjs {
|
||||
const snap = (value: float, size: float, offset: float) =>
|
||||
size ? offset + size * Math.round((value - offset) / size) : value;
|
||||
|
||||
/**
|
||||
* Convert a scale ratio to the ratio giving a size whose far edge
|
||||
* (origin + size) is on the grid. The size is kept at least 1.
|
||||
*/
|
||||
const getScaleSnappedOnGrid = (
|
||||
origin: float,
|
||||
initialSize: float,
|
||||
scale: float,
|
||||
snapPosition: (position: float) => float
|
||||
): float => {
|
||||
if (initialSize <= 0) return scale;
|
||||
const snappedEdge = snapPosition(origin + initialSize * Math.abs(scale));
|
||||
const snappedSize = Math.max(1, snappedEdge - origin);
|
||||
return snappedSize / initialSize;
|
||||
};
|
||||
|
||||
class Selection {
|
||||
private _selectedObjects: Array<gdjs.RuntimeObject> = [];
|
||||
|
||||
@@ -2352,6 +2410,9 @@ namespace gdjs {
|
||||
patchNegativeAxisHandlesOnTransformControlsGizmos(
|
||||
threeTransformControls
|
||||
);
|
||||
patchAxisGuideLinesOnTransformControlsGizmos(
|
||||
threeTransformControls
|
||||
);
|
||||
|
||||
threeTransformControls.rotation.order = 'ZYX';
|
||||
threeTransformControls.scale.y = -1;
|
||||
@@ -2379,6 +2440,9 @@ namespace gdjs {
|
||||
let initialObjectX = 0;
|
||||
let initialObjectY = 0;
|
||||
let initialObjectZ = 0;
|
||||
let initialObjectWidth = 0;
|
||||
let initialObjectHeight = 0;
|
||||
let initialObjectDepth = 0;
|
||||
const initialDummyPosition = new THREE.Vector3();
|
||||
const initialDummyRotation = new THREE.Euler();
|
||||
const initialDummyScale = new THREE.Vector3();
|
||||
@@ -2398,6 +2462,11 @@ namespace gdjs {
|
||||
initialObjectZ = is3D(lastEditableSelectedObject)
|
||||
? lastEditableSelectedObject.getZ()
|
||||
: 0;
|
||||
initialObjectWidth = lastEditableSelectedObject.getWidth();
|
||||
initialObjectHeight = lastEditableSelectedObject.getHeight();
|
||||
initialObjectDepth = is3D(lastEditableSelectedObject)
|
||||
? lastEditableSelectedObject.getDepth()
|
||||
: 0;
|
||||
initialDummyPosition.copy(dummyThreeObject.position);
|
||||
initialDummyRotation.copy(dummyThreeObject.rotation);
|
||||
initialDummyScale.copy(dummyThreeObject.scale);
|
||||
@@ -2477,6 +2546,71 @@ namespace gdjs {
|
||||
threeTransformControls.axis.length === 1
|
||||
? 1
|
||||
: 0.2;
|
||||
let scaleX =
|
||||
1 +
|
||||
(dummyThreeObject.scale.x / initialDummyScale.x - 1) *
|
||||
scaleDamping;
|
||||
let scaleY =
|
||||
1 +
|
||||
(dummyThreeObject.scale.y / initialDummyScale.y - 1) *
|
||||
scaleDamping;
|
||||
let scaleZ =
|
||||
1 +
|
||||
(dummyThreeObject.scale.z / initialDummyScale.z - 1) *
|
||||
scaleDamping;
|
||||
if (
|
||||
this._transformControlsMode === 'scale' &&
|
||||
threeTransformControls.axis &&
|
||||
this._editorGrid.isSpanningEnabled(inputManager)
|
||||
) {
|
||||
// The scale gizmo is anchored on the object origin, so snap the
|
||||
// opposite edge on the grid (like the 2D editor resize handles).
|
||||
const getSnappedScaleX = () =>
|
||||
getScaleSnappedOnGrid(
|
||||
initialObjectX,
|
||||
initialObjectWidth,
|
||||
scaleX,
|
||||
(x) => this._editorGrid.getSnappedX(x)
|
||||
);
|
||||
const getSnappedScaleY = () =>
|
||||
getScaleSnappedOnGrid(
|
||||
initialObjectY,
|
||||
initialObjectHeight,
|
||||
scaleY,
|
||||
(y) => this._editorGrid.getSnappedY(y)
|
||||
);
|
||||
const getSnappedScaleZ = () =>
|
||||
getScaleSnappedOnGrid(
|
||||
initialObjectZ,
|
||||
initialObjectDepth,
|
||||
scaleZ,
|
||||
(z) => this._editorGrid.getSnappedZ(z)
|
||||
);
|
||||
if (threeTransformControls.axis === 'XYZ') {
|
||||
// Uniform scaling: like the 2D proportional resize, snap the
|
||||
// biggest side and apply the same ratio to the others.
|
||||
const uniformScale =
|
||||
initialObjectWidth >= initialObjectHeight &&
|
||||
initialObjectWidth >= initialObjectDepth
|
||||
? getSnappedScaleX()
|
||||
: initialObjectHeight >= initialObjectDepth
|
||||
? getSnappedScaleY()
|
||||
: getSnappedScaleZ();
|
||||
scaleX = uniformScale;
|
||||
scaleY = uniformScale;
|
||||
scaleZ = uniformScale;
|
||||
} else {
|
||||
if (threeTransformControls.axis.includes('X')) {
|
||||
scaleX = getSnappedScaleX();
|
||||
}
|
||||
if (threeTransformControls.axis.includes('Y')) {
|
||||
scaleY = getSnappedScaleY();
|
||||
}
|
||||
if (threeTransformControls.axis.includes('Z')) {
|
||||
scaleZ = getSnappedScaleZ();
|
||||
}
|
||||
}
|
||||
}
|
||||
this._selectionControlsMovementTotalDelta = {
|
||||
translationX,
|
||||
translationY,
|
||||
@@ -2490,18 +2624,9 @@ namespace gdjs {
|
||||
rotationZ: -gdjs.toDegrees(
|
||||
dummyThreeObject.rotation.z - initialDummyRotation.z
|
||||
),
|
||||
scaleX:
|
||||
1 +
|
||||
(dummyThreeObject.scale.x / initialDummyScale.x - 1) *
|
||||
scaleDamping,
|
||||
scaleY:
|
||||
1 +
|
||||
(dummyThreeObject.scale.y / initialDummyScale.y - 1) *
|
||||
scaleDamping,
|
||||
scaleZ:
|
||||
1 +
|
||||
(dummyThreeObject.scale.z / initialDummyScale.z - 1) *
|
||||
scaleDamping,
|
||||
scaleX,
|
||||
scaleY,
|
||||
scaleZ,
|
||||
};
|
||||
|
||||
this._hasSelectionActuallyMoved =
|
||||
@@ -2585,7 +2710,8 @@ namespace gdjs {
|
||||
this._editorGrid.setTreeScene(threeScene);
|
||||
}
|
||||
this._editorGrid.setVisible(
|
||||
this._transformControlsMode === 'translate'
|
||||
this._transformControlsMode === 'translate' ||
|
||||
this._transformControlsMode === 'scale'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -4107,8 +4233,8 @@ namespace gdjs {
|
||||
}
|
||||
|
||||
getSnappedZ(z: float): float {
|
||||
const { gridDepth, gridOffsetY } = this;
|
||||
return snap(z, gridDepth || 0, gridOffsetY);
|
||||
const { gridDepth, gridOffsetZ } = this;
|
||||
return snap(z, gridDepth || 0, gridOffsetZ);
|
||||
}
|
||||
|
||||
isSpanningEnabled(
|
||||
|
||||
@@ -38,7 +38,13 @@ namespace gdjs {
|
||||
this._loader = new THREE_ADDONS.GLTFLoader();
|
||||
|
||||
this._dracoLoader = new THREE_ADDONS.DRACOLoader();
|
||||
this._dracoLoader.setDecoderPath('./pixi-renderers/draco/gltf/');
|
||||
// The Draco decoder files are shipped with the game engine files.
|
||||
const runtimeFilesBaseUrl =
|
||||
resourceLoader.getRuntimeGame().getAdditionalOptions()
|
||||
.runtimeFilesBaseUrl || './';
|
||||
this._dracoLoader.setDecoderPath(
|
||||
runtimeFilesBaseUrl + 'pixi-renderers/draco/gltf/'
|
||||
);
|
||||
this._loader.setDRACOLoader(this._dracoLoader);
|
||||
|
||||
/**
|
||||
@@ -153,7 +159,11 @@ namespace gdjs {
|
||||
this._loadedThreeModels.clear();
|
||||
this._downloadedArrayBuffers.clear();
|
||||
this._loader = null;
|
||||
this._dracoLoader = null;
|
||||
if (this._dracoLoader) {
|
||||
// Release the web workers used to decode the compressed 3D models.
|
||||
this._dracoLoader.dispose();
|
||||
this._dracoLoader = null;
|
||||
}
|
||||
|
||||
if (this._invalidModel) {
|
||||
this._invalidModel.cameras = [];
|
||||
|
||||
@@ -312,6 +312,12 @@ namespace gdjs {
|
||||
* (`durationMs - loadingMs` close to it means the test is at risk of
|
||||
* timing out on a slower machine). */
|
||||
timeoutMs: number;
|
||||
/** Time during which the game was frozen by the browser because the
|
||||
* page was hidden (tab in the background, window minimized or covered
|
||||
* by another one): animation frames stop, so the game stops stepping.
|
||||
* Excluded from the `timeoutMs` budget - being looked away from is
|
||||
* not a reason for a test to fail. */
|
||||
hiddenStallMs: number;
|
||||
gameTimeMs: number;
|
||||
assertions: Array<GameplayTestAssertion>;
|
||||
errors: Array<string>;
|
||||
@@ -541,6 +547,17 @@ namespace gdjs {
|
||||
/** Time spent waiting for loading (game boot, scene assets) so far:
|
||||
* excluded from the `_timeoutMs` budget, reported as `loadingMs`. */
|
||||
_loadingTimeMs: number = 0;
|
||||
/** Time already spent frozen by the browser because the page was
|
||||
* hidden, excluded from the `_timeoutMs` budget (see
|
||||
* `_installPageVisibilityTracking`). */
|
||||
_hiddenStallTimeMs: number = 0;
|
||||
/** When the page became hidden, while it still is, or null. */
|
||||
_hiddenSinceMs: number | null = null;
|
||||
/** When the last game frame was stepped: tells apart a page that is
|
||||
* hidden AND frozen from one that is only hidden (in the desktop app,
|
||||
* background throttling is disabled and the game keeps running). */
|
||||
_lastFrameStepTimeMs: number = 0;
|
||||
_uninstallPageVisibilityTracking: () => void = () => {};
|
||||
_maxFrames: integer;
|
||||
/** Last time the stepping loop yielded to the browser (see
|
||||
* `_maybeYield`). */
|
||||
@@ -650,6 +667,60 @@ namespace gdjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Track the time during which the game is frozen because the page is
|
||||
* hidden, so that it can be excluded from the `_timeoutMs` budget.
|
||||
*
|
||||
* Browsers stop `requestAnimationFrame` in a hidden page (a tab in
|
||||
* the background, a minimized window, or - with Chrome's occlusion
|
||||
* detection - a window entirely covered by another one). The test
|
||||
* loop steps frames from animation frames, so the game simply stops:
|
||||
* without this, a test would be reported as timing out just because
|
||||
* the user looked at something else while it ran.
|
||||
*
|
||||
* Being hidden is not enough to conclude the game was frozen: the
|
||||
* desktop app disables background throttling, and the game then keeps
|
||||
* running while hidden. So only the time after the last stepped frame
|
||||
* is counted as a stall.
|
||||
*/
|
||||
_installPageVisibilityTracking(): void {
|
||||
if (typeof document === 'undefined') return;
|
||||
|
||||
const onVisibilityChanged = () => {
|
||||
if (document.visibilityState === 'hidden') {
|
||||
this._hiddenSinceMs = Date.now();
|
||||
} else {
|
||||
this._hiddenStallTimeMs += this._getOngoingHiddenStallMs();
|
||||
this._hiddenSinceMs = null;
|
||||
}
|
||||
};
|
||||
if (document.visibilityState === 'hidden') {
|
||||
this._hiddenSinceMs = Date.now();
|
||||
}
|
||||
document.addEventListener('visibilitychange', onVisibilityChanged);
|
||||
this._uninstallPageVisibilityTracking = () => {
|
||||
document.removeEventListener('visibilitychange', onVisibilityChanged);
|
||||
this._uninstallPageVisibilityTracking = () => {};
|
||||
};
|
||||
}
|
||||
|
||||
/** The stall of the hidden period currently in progress, if any. */
|
||||
private _getOngoingHiddenStallMs(): number {
|
||||
if (this._hiddenSinceMs === null) return 0;
|
||||
// Frames stepped while hidden are frames the game was not frozen
|
||||
// for: the stall only starts after the last of them.
|
||||
const stallStartTimeMs = Math.max(
|
||||
this._hiddenSinceMs,
|
||||
this._lastFrameStepTimeMs
|
||||
);
|
||||
return Math.max(0, Date.now() - stallStartTimeMs);
|
||||
}
|
||||
|
||||
/** Total time the game was frozen because the page was hidden. */
|
||||
_getHiddenStallTimeMs(): number {
|
||||
return this._hiddenStallTimeMs + this._getOngoingHiddenStallMs();
|
||||
}
|
||||
|
||||
private _checkGuards(): void {
|
||||
if (this._stopped) {
|
||||
throw new GameplayTestStoppedError('The test was stopped.');
|
||||
@@ -660,12 +731,15 @@ namespace gdjs {
|
||||
);
|
||||
}
|
||||
if (
|
||||
Date.now() - this._startTimeMs - this._loadingTimeMs >
|
||||
Date.now() -
|
||||
this._startTimeMs -
|
||||
this._loadingTimeMs -
|
||||
this._getHiddenStallTimeMs() >
|
||||
this._timeoutMs
|
||||
) {
|
||||
throw new GameplayTestTimeoutError(
|
||||
`The test timed out after ${this._timeoutMs}ms ` +
|
||||
'(wall-clock, loading time excluded).'
|
||||
'(wall-clock, loading and time spent hidden excluded).'
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -856,6 +930,7 @@ namespace gdjs {
|
||||
this._framesExecuted++;
|
||||
this._gameTimeMs += dtMs;
|
||||
const stepTimeMs = Date.now() - stepStartTimeMs;
|
||||
this._lastFrameStepTimeMs = stepStartTimeMs + stepTimeMs;
|
||||
this._totalStepTimeMs += stepTimeMs;
|
||||
if (stepTimeMs > this._worstStepTimeMs) {
|
||||
this._worstStepTimeMs = stepTimeMs;
|
||||
@@ -1348,6 +1423,48 @@ namespace gdjs {
|
||||
return this._runtimeGame.getGameResolutionHeight();
|
||||
}
|
||||
|
||||
/** Undo the game window size override (see `setGameResolutionSize`). */
|
||||
_uninstallGameWindowSizeOverride: () => void = () => {};
|
||||
|
||||
/**
|
||||
* Change the game resolution, as if the game window was resized to
|
||||
* this size. Useful to test layouts made for other screen sizes,
|
||||
* before asserting positions or taking a screenshot.
|
||||
*
|
||||
* This is the ONLY way the game resolution changes during a test:
|
||||
* a gameplay test always runs in a game window of the project's game
|
||||
* resolution, and moving, resizing or minimizing the gameplay test
|
||||
* frame in the editor is never visible to the game (the frame is
|
||||
* only a zoomed view of that fixed window).
|
||||
*
|
||||
* The engine sees a game window of this size until the end of the
|
||||
* test: a game adapting its resolution to the game window size would
|
||||
* otherwise recompute it from the real window, ignoring the size
|
||||
* requested here.
|
||||
*/
|
||||
setGameResolutionSize(width: float, height: float): void {
|
||||
this._uninstallGameWindowSizeOverride();
|
||||
const original = {
|
||||
getWindowInnerWidth: gdjs.RuntimeGameRenderer.getWindowInnerWidth,
|
||||
getWindowInnerHeight: gdjs.RuntimeGameRenderer.getWindowInnerHeight,
|
||||
};
|
||||
gdjs.RuntimeGameRenderer.getWindowInnerWidth = () => width;
|
||||
gdjs.RuntimeGameRenderer.getWindowInnerHeight = () => height;
|
||||
this._uninstallGameWindowSizeOverride = () => {
|
||||
gdjs.RuntimeGameRenderer.getWindowInnerWidth =
|
||||
original.getWindowInnerWidth;
|
||||
gdjs.RuntimeGameRenderer.getWindowInnerHeight =
|
||||
original.getWindowInnerHeight;
|
||||
this._uninstallGameWindowSizeOverride = () => {};
|
||||
};
|
||||
|
||||
this._runtimeGame.setGameResolutionSize(width, height);
|
||||
// The paused main loop would only adapt the cameras on its next
|
||||
// animation frame: do it now, so the next stepped frame is laid out
|
||||
// for the new resolution.
|
||||
this._runtimeGame.getSceneStack().onGameResolutionResized();
|
||||
}
|
||||
|
||||
/**
|
||||
* Release all pressed keys, mouse buttons and touches.
|
||||
*/
|
||||
@@ -2854,6 +2971,7 @@ namespace gdjs {
|
||||
durationMs: this._startTimeMs ? Date.now() - this._startTimeMs : 0,
|
||||
loadingMs: Math.round(this._loadingTimeMs),
|
||||
timeoutMs: this._timeoutMs,
|
||||
hiddenStallMs: Math.round(this._getHiddenStallTimeMs()),
|
||||
gameTimeMs: Math.round(this._gameTimeMs),
|
||||
assertions: this._assertions,
|
||||
errors: errors.slice(0, MAX_ERRORS),
|
||||
@@ -3015,6 +3133,10 @@ namespace gdjs {
|
||||
|
||||
harness._installPointerLockShim();
|
||||
harness._installSoundLog();
|
||||
// Only from now on: the boot wait above is already excluded from the
|
||||
// `timeoutMs` budget, as loading time.
|
||||
harness._lastFrameStepTimeMs = Date.now();
|
||||
harness._installPageVisibilityTracking();
|
||||
|
||||
// Capture the logs of the game itself (in addition to the `console`
|
||||
// passed to the script).
|
||||
@@ -3151,8 +3273,10 @@ namespace gdjs {
|
||||
// Ignore errors during cleanup.
|
||||
}
|
||||
inputManager.onFrameEnded = originalOnFrameEnded;
|
||||
harness._uninstallPageVisibilityTracking();
|
||||
harness._uninstallPointerLockShim();
|
||||
harness._uninstallSoundLog();
|
||||
harness._uninstallGameWindowSizeOverride();
|
||||
gdjs.Logger.setLoggerOutput(existingLoggerOutput);
|
||||
if (payload.freezeWhenFinished) {
|
||||
// Keep the game paused (the main loop keeps rendering the last
|
||||
|
||||
@@ -18,6 +18,28 @@ namespace gdjs {
|
||||
return arr.indexOf(value) !== -1 ? value : min;
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrap a Three.js shader so that its output colors are never negative.
|
||||
* Negative colors become NaN in the sRGB conversion done by the output pass,
|
||||
* which is rendered as garbage by some mobile GPUs (e.g. Mali).
|
||||
*/
|
||||
export const clampThreeShaderOutput = <
|
||||
T extends { fragmentShader: string },
|
||||
>(
|
||||
shader: T
|
||||
): T => ({
|
||||
...shader,
|
||||
fragmentShader:
|
||||
shader.fragmentShader.replace(
|
||||
/void\s+main\s*\(\s*\)/,
|
||||
'void unclampedMain()'
|
||||
) +
|
||||
'\nvoid main() {\n' +
|
||||
' unclampedMain();\n' +
|
||||
' gl_FragColor.rgb = max(gl_FragColor.rgb, 0.0);\n' +
|
||||
'}\n',
|
||||
});
|
||||
|
||||
const _filterCreators: {
|
||||
[filterName: string]: FilterCreator;
|
||||
} = {};
|
||||
|
||||
+209
-1
@@ -1,9 +1,33 @@
|
||||
namespace gdjs {
|
||||
const logger = new gdjs.Logger('Profiler');
|
||||
|
||||
/**
|
||||
* @category Debugging > Profiler
|
||||
*/
|
||||
export type ProfilerStats = {
|
||||
framesCount: integer;
|
||||
/**
|
||||
* The number of shader programs held by the 3D renderer at the end of the
|
||||
* run (0 when the game does not render in 3D).
|
||||
*/
|
||||
shaderProgramsCount: integer;
|
||||
/**
|
||||
* The number of shader programs the 3D renderer had to compile *during*
|
||||
* the run. Anything above 0 once the game is running means frames were
|
||||
* spent compiling shaders instead of drawing - see
|
||||
* `Profiler.recordShaderPrograms`.
|
||||
*/
|
||||
shaderProgramCompilationsCount: integer;
|
||||
/** The number of captured frames that compiled at least one shader. */
|
||||
framesWithShaderCompilationCount: integer;
|
||||
/** Average number of 3D draw calls per captured frame. */
|
||||
averageDrawCallsCount: float;
|
||||
/** Average number of 3D triangles drawn per captured frame. */
|
||||
averageTrianglesCount: float;
|
||||
/** Geometries the 3D renderer was holding at the end of the run. */
|
||||
geometriesCount: integer;
|
||||
/** Textures the 3D renderer was holding at the end of the run. */
|
||||
texturesCount: integer;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -56,6 +80,33 @@ namespace gdjs {
|
||||
/** A function to get the current time. If available, corresponds to performance.now(). */
|
||||
_getTimeNow: () => float;
|
||||
|
||||
/**
|
||||
* The renderer cache keys already seen, or null until the first measured
|
||||
* frame (whose programs are start-up cost, not part of the run).
|
||||
*/
|
||||
_seenShaderCacheKeys: Set<string> | null = null;
|
||||
|
||||
/** The number of shader programs held by the 3D renderer on the last frame. */
|
||||
_shaderProgramsCount: integer = 0;
|
||||
|
||||
/** How many shader programs were compiled since profiling started. */
|
||||
_shaderProgramCompilationsCount: integer = 0;
|
||||
|
||||
/** How many frames compiled at least one shader program. */
|
||||
_framesWithShaderCompilationCount: integer = 0;
|
||||
|
||||
/** Draw calls summed over the frames measured, to average afterwards. */
|
||||
_drawCallsSum: integer = 0;
|
||||
|
||||
/** Triangles summed over the frames measured, to average afterwards. */
|
||||
_trianglesSum: integer = 0;
|
||||
|
||||
/** How many frames the two sums above cover. */
|
||||
_rendererInfoFramesCount: integer = 0;
|
||||
|
||||
_geometriesCount: integer = 0;
|
||||
_texturesCount: integer = 0;
|
||||
|
||||
constructor() {
|
||||
while (this._framesMeasures.length < this._maxFramesCount) {
|
||||
this._framesMeasures.push({
|
||||
@@ -249,11 +300,168 @@ namespace gdjs {
|
||||
return frameTimes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record what the 3D renderer did on this frame: its cheap per-frame
|
||||
* counters, and the shader programs it is holding.
|
||||
*
|
||||
* The renderer compiles a separate program for every distinct combination
|
||||
* of the things that shape a shader - material features, the number of
|
||||
* lights of each kind, fog, shadow filtering, clipping planes, tone
|
||||
* mapping, instancing, and more. Whenever the game moves any of them onto
|
||||
* a combination that has not been seen before, that program is compiled
|
||||
* and linked on the spot: the cost lands on a single frame, and never
|
||||
* again for that combination. It shows up as unexplained stutter early in
|
||||
* a playthrough, which is why it is worth measuring rather than guessing.
|
||||
*
|
||||
* Rather than assume a cause, this compares the cache keys the renderer
|
||||
* itself uses to tell programs apart, and reports which fields differ from
|
||||
* the closest program already seen.
|
||||
*
|
||||
* @param rendererInfo The current `WebGLRenderer.info`.
|
||||
*/
|
||||
record3DRendererInfo(rendererInfo: {
|
||||
programs?: Array<{ cacheKey?: string; type?: string }> | null;
|
||||
render?: { calls: number; triangles: number };
|
||||
memory?: { geometries: number; textures: number };
|
||||
}): void {
|
||||
if (rendererInfo.render) {
|
||||
// These counters are reset by the renderer on every frame, so reading
|
||||
// them after rendering gives this frame's numbers.
|
||||
this._drawCallsSum += rendererInfo.render.calls;
|
||||
this._trianglesSum += rendererInfo.render.triangles;
|
||||
this._rendererInfoFramesCount++;
|
||||
}
|
||||
if (rendererInfo.memory) {
|
||||
this._geometriesCount = rendererInfo.memory.geometries;
|
||||
this._texturesCount = rendererInfo.memory.textures;
|
||||
}
|
||||
|
||||
const programs = rendererInfo.programs || null;
|
||||
const programsCount = programs ? programs.length : 0;
|
||||
this._shaderProgramsCount = programsCount;
|
||||
|
||||
if (!this._seenShaderCacheKeys) {
|
||||
// First measured frame: whatever is already compiled is start-up
|
||||
// cost, not something that happened during the run.
|
||||
this._seenShaderCacheKeys = new Set<string>();
|
||||
if (programs) {
|
||||
for (const program of programs) {
|
||||
this._seenShaderCacheKeys.add(program.cacheKey || '');
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!programs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newPrograms = programs.filter(
|
||||
(program) => !this._seenShaderCacheKeys!.has(program.cacheKey || '')
|
||||
);
|
||||
if (!newPrograms.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const descriptions = newPrograms.map((program) => {
|
||||
const description = Profiler._describeNewShaderProgram(
|
||||
program,
|
||||
this._seenShaderCacheKeys!
|
||||
);
|
||||
this._seenShaderCacheKeys!.add(program.cacheKey || '');
|
||||
return description;
|
||||
});
|
||||
|
||||
this._shaderProgramCompilationsCount += newPrograms.length;
|
||||
this._framesWithShaderCompilationCount++;
|
||||
|
||||
logger.warn(
|
||||
'Compiled ' +
|
||||
newPrograms.length +
|
||||
' new shader program(s) on frame ' +
|
||||
this._framesCount +
|
||||
' (' +
|
||||
programsCount +
|
||||
' in total), which costs a dropped frame. ' +
|
||||
descriptions.join(' ')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Describe a newly compiled shader program by what makes it different from
|
||||
* the most similar program already compiled.
|
||||
*
|
||||
* The renderer's cache keys are comma separated lists of the values that
|
||||
* decide the shader, always in the same order, so comparing them field by
|
||||
* field points straight at what the game changed - without this having to
|
||||
* know what any given field means.
|
||||
*/
|
||||
private static _describeNewShaderProgram(
|
||||
program: { cacheKey?: string; type?: string },
|
||||
seenCacheKeys: Set<string>
|
||||
): string {
|
||||
const name = program.type || 'unknown material';
|
||||
const cacheKey = program.cacheKey;
|
||||
if (!cacheKey) {
|
||||
return '"' + name + '" (no cache key to compare).';
|
||||
}
|
||||
|
||||
const fields = cacheKey.split(',');
|
||||
let closestDifferences: Array<string> | null = null;
|
||||
for (const seenCacheKey of seenCacheKeys) {
|
||||
const seenFields = seenCacheKey.split(',');
|
||||
if (seenFields.length !== fields.length) {
|
||||
// A different shader entirely, not the same one reconfigured.
|
||||
continue;
|
||||
}
|
||||
const differences: Array<string> = [];
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
if (fields[i] !== seenFields[i]) {
|
||||
differences.push(
|
||||
'field ' + i + ': "' + seenFields[i] + '" -> "' + fields[i] + '"'
|
||||
);
|
||||
}
|
||||
}
|
||||
if (
|
||||
differences.length &&
|
||||
(!closestDifferences ||
|
||||
differences.length < closestDifferences.length)
|
||||
) {
|
||||
closestDifferences = differences;
|
||||
}
|
||||
}
|
||||
|
||||
if (!closestDifferences) {
|
||||
return '"' + name + '" is a shader that was not used before.';
|
||||
}
|
||||
return (
|
||||
'"' +
|
||||
name +
|
||||
'" differs from the closest program already compiled in ' +
|
||||
closestDifferences.length +
|
||||
' field(s): ' +
|
||||
closestDifferences.slice(0, 4).join(', ') +
|
||||
(closestDifferences.length > 4 ? ', ...' : '') +
|
||||
'.'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stats measured during the frames captured.
|
||||
*/
|
||||
getStats(): ProfilerStats {
|
||||
return { framesCount: this._framesCount };
|
||||
const rendererFrames = this._rendererInfoFramesCount || 1;
|
||||
return {
|
||||
framesCount: this._framesCount,
|
||||
shaderProgramsCount: this._shaderProgramsCount,
|
||||
shaderProgramCompilationsCount: this._shaderProgramCompilationsCount,
|
||||
framesWithShaderCompilationCount:
|
||||
this._framesWithShaderCompilationCount,
|
||||
averageDrawCallsCount: this._drawCallsSum / rendererFrames,
|
||||
averageTrianglesCount: this._trianglesSum / rendererFrames,
|
||||
geometriesCount: this._geometriesCount,
|
||||
texturesCount: this._texturesCount,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -108,6 +108,12 @@ namespace gdjs {
|
||||
|
||||
inGameEditorSettings?: InGameEditorSettings;
|
||||
|
||||
/**
|
||||
* The base URL (or relative path) from which the game engine files are
|
||||
* served. Most of the time, they are next to the game index.html ("./"),
|
||||
* but some previews serve them from a server.
|
||||
*/
|
||||
runtimeFilesBaseUrl?: string;
|
||||
/** Script files, used for hot-reloading. */
|
||||
scriptFiles?: Array<RuntimeGameOptionsScriptFile>;
|
||||
/** if true, export is a partial preview without reloading libraries. */
|
||||
|
||||
@@ -446,6 +446,14 @@ namespace gdjs {
|
||||
if (this._profiler) {
|
||||
this._profiler.end('render');
|
||||
}
|
||||
if (this._profiler) {
|
||||
const threeRenderer = this._runtimeGame
|
||||
.getRenderer()
|
||||
.getThreeRenderer();
|
||||
if (threeRenderer) {
|
||||
this._profiler.record3DRendererInfo(threeRenderer.info);
|
||||
}
|
||||
}
|
||||
if (this._profiler) {
|
||||
this._profiler.endFrame();
|
||||
}
|
||||
|
||||
@@ -2819,6 +2819,10 @@ interface WholeProjectRefactorer {
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString oldName,
|
||||
[Const] DOMString newName);
|
||||
void STATIC_UpdateExtensionNameInExtension(
|
||||
[Ref] Project project,
|
||||
[Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString sourceExtensionName);
|
||||
void STATIC_UpdateExtensionNameInEventsBasedBehavior(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
@@ -2834,6 +2838,13 @@ interface WholeProjectRefactorer {
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString oldName,
|
||||
[Const] DOMString newName);
|
||||
void STATIC_MoveEventsFunction(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString oldExtensionName,
|
||||
[Const] DOMString newExtensionName,
|
||||
[Const] DOMString oldFunctionName,
|
||||
[Const] DOMString newFunctionName);
|
||||
void STATIC_RenameBehaviorEventsFunction(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
@@ -2912,6 +2923,13 @@ interface WholeProjectRefactorer {
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString oldName,
|
||||
[Const] DOMString newName);
|
||||
void STATIC_MoveEventsBasedBehavior(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString oldExtensionName,
|
||||
[Const] DOMString newExtensionName,
|
||||
[Const] DOMString oldBehaviorName,
|
||||
[Const] DOMString newBehaviorName);
|
||||
void STATIC_UpdateBehaviorNameInEventsBasedBehavior(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
@@ -2920,8 +2938,15 @@ interface WholeProjectRefactorer {
|
||||
void STATIC_RenameEventsBasedObject(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString oldName,
|
||||
[Const] DOMString newName);
|
||||
[Const] DOMString oldObjectName,
|
||||
[Const] DOMString newObjectName);
|
||||
void STATIC_MoveEventsBasedObject(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
[Const] DOMString oldExtensionName,
|
||||
[Const] DOMString newExtensionName,
|
||||
[Const] DOMString oldObjectName,
|
||||
[Const] DOMString newObjectName);
|
||||
void STATIC_UpdateObjectNameInEventsBasedObject(
|
||||
[Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
|
||||
@@ -3103,6 +3128,10 @@ interface InstructionValidator {
|
||||
[Const, Ref] Instruction instruction,
|
||||
[Const, Ref] InstructionMetadata metadata,
|
||||
long parameterIndex);
|
||||
[Const, Value] DOMString STATIC_GetObjectNameForParameter(
|
||||
[Const, Ref] ProjectScopedContainers projectScopedContainers,
|
||||
[Const, Ref] Instruction instruction,
|
||||
[Const] DOMString parameterType);
|
||||
};
|
||||
|
||||
interface ObjectTools {
|
||||
@@ -3130,6 +3159,20 @@ interface PropertyFunctionGenerator {
|
||||
|
||||
};
|
||||
|
||||
interface ChildObjectForwardFunctionGenerator {
|
||||
void STATIC_GenerateChildObjectForwardFunctions(
|
||||
[Const, Ref] Project project,
|
||||
[Const, Ref] EventsFunctionsExtension extension,
|
||||
[Ref] EventsBasedObject eventsBasedObject,
|
||||
[Const] DOMString childObjectName);
|
||||
boolean STATIC_HasAnyChildCustomObject(
|
||||
[Const, Ref] Project project,
|
||||
[Ref] EventsBasedObject eventsBasedObject);
|
||||
[Value] VectorString STATIC_GetChildCustomObjectNames(
|
||||
[Const, Ref] Project project,
|
||||
[Ref] EventsBasedObject eventsBasedObject);
|
||||
};
|
||||
|
||||
interface UsedExtensionsResult {
|
||||
[Const, Ref] SetString GetUsedExtensions();
|
||||
};
|
||||
@@ -3279,6 +3322,7 @@ interface ExpressionValidator {
|
||||
[Const, Ref] Platform platform,
|
||||
[Const, Ref] ProjectScopedContainers projectScopedContainers,
|
||||
[Const] DOMString rootType,
|
||||
[Const] DOMString rootObjectName,
|
||||
[Const] DOMString extraInfo);
|
||||
|
||||
[Const, Ref] VectorExpressionParserError GetAllErrors();
|
||||
|
||||
@@ -53,7 +53,9 @@
|
||||
#include <GDCore/IDE/Events/ExampleExtensionUsagesFinder.h>
|
||||
#include <GDCore/IDE/EventsFunctionTools.h>
|
||||
#include <GDCore/IDE/EventsBasedObjectVariantHelper.h>
|
||||
#include <GDCore/IDE/EventsFunctionsExtensionExtractor.h>
|
||||
#include <GDCore/IDE/ExtensionEditor/ChildObjectForwardFunctionGenerator.h>
|
||||
#include <GDCore/IDE/ExtensionEditor/EventsFunctionsExtensionExtractor.h>
|
||||
#include <GDCore/IDE/ExtensionEditor/PropertyFunctionGenerator.h>
|
||||
#include <GDCore/IDE/ObjectRefactorer.h>
|
||||
#include <GDCore/IDE/Project/ArbitraryResourceWorker.h>
|
||||
#include <GDCore/IDE/Project/ArbitraryObjectsWorker.h>
|
||||
@@ -65,7 +67,6 @@
|
||||
#include <GDCore/IDE/Project/ResourcesRenamer.h>
|
||||
#include <GDCore/IDE/Project/EventsBasedObjectDependencyFinder.h>
|
||||
#include <GDCore/IDE/ProjectBrowserHelper.h>
|
||||
#include <GDCore/IDE/PropertyFunctionGenerator.h>
|
||||
#include <GDCore/IDE/UnfilledRequiredBehaviorPropertyProblem.h>
|
||||
#include <GDCore/IDE/VariableInstructionSwitcher.h>
|
||||
#include <GDCore/IDE/WholeProjectRefactorer.h>
|
||||
@@ -745,6 +746,7 @@ typedef std::vector<gd::PropertyDescriptorChoice> VectorPropertyDescriptorChoice
|
||||
#define STATIC_FillBehaviorParameters FillBehaviorParameters
|
||||
#define STATIC_ValidateParameter ValidateParameter
|
||||
#define STATIC_IsParameterValid IsParameterValid
|
||||
#define STATIC_GetObjectNameForParameter GetObjectNameForParameter
|
||||
#define STATIC_FixInvalidRequiredBehaviorProperties \
|
||||
FixInvalidRequiredBehaviorProperties
|
||||
#define STATIC_RemoveLayerInScene RemoveLayerInScene
|
||||
@@ -762,6 +764,9 @@ typedef std::vector<gd::PropertyDescriptorChoice> VectorPropertyDescriptorChoice
|
||||
#define STATIC_CanGenerateGetterAndSetter CanGenerateGetterAndSetter
|
||||
#define STATIC_GenerateConditionSkeleton GenerateConditionSkeleton
|
||||
#define STATIC_GenerateExpressionSkeleton GenerateExpressionSkeleton
|
||||
#define STATIC_GenerateChildObjectForwardFunctions GenerateChildObjectForwardFunctions
|
||||
#define STATIC_HasAnyChildCustomObject HasAnyChildCustomObject
|
||||
#define STATIC_GetChildCustomObjectNames GetChildCustomObjectNames
|
||||
#define STATIC_UpdateReturnActionType UpdateReturnActionType
|
||||
#define STATIC_CreateRectangle CreateRectangle
|
||||
#define STATIC_SanityCheckBehaviorProperty SanityCheckBehaviorProperty
|
||||
@@ -820,11 +825,13 @@ typedef std::vector<gd::PropertyDescriptorChoice> VectorPropertyDescriptorChoice
|
||||
#define STATIC_ComplyVariantsToEventsBasedObject ComplyVariantsToEventsBasedObject
|
||||
#define STATIC_FindAllChildrenCustomObjectType FindAllChildrenCustomObjectType
|
||||
#define STATIC_RenameEventsFunctionsExtension RenameEventsFunctionsExtension
|
||||
#define STATIC_UpdateExtensionNameInExtension UpdateExtensionNameInExtension
|
||||
#define STATIC_UpdateExtensionNameInEventsBasedBehavior \
|
||||
UpdateExtensionNameInEventsBasedBehavior
|
||||
#define STATIC_UpdateExtensionNameInEventsBasedObject \
|
||||
UpdateExtensionNameInEventsBasedObject
|
||||
#define STATIC_RenameEventsFunction RenameEventsFunction
|
||||
#define STATIC_MoveEventsFunction MoveEventsFunction
|
||||
#define STATIC_RenameBehaviorEventsFunction RenameBehaviorEventsFunction
|
||||
#define STATIC_RenameObjectEventsFunction RenameObjectEventsFunction
|
||||
#define STATIC_RenameParameter RenameParameter
|
||||
@@ -842,8 +849,10 @@ typedef std::vector<gd::PropertyDescriptorChoice> VectorPropertyDescriptorChoice
|
||||
#define STATIC_ChangeEventsBasedBehaviorPropertyType ChangeEventsBasedBehaviorPropertyType
|
||||
#define STATIC_ChangeEventsBasedObjectPropertyType ChangeEventsBasedObjectPropertyType
|
||||
#define STATIC_RenameEventsBasedBehavior RenameEventsBasedBehavior
|
||||
#define STATIC_MoveEventsBasedBehavior MoveEventsBasedBehavior
|
||||
#define STATIC_UpdateBehaviorNameInEventsBasedBehavior UpdateBehaviorNameInEventsBasedBehavior
|
||||
#define STATIC_RenameEventsBasedObject RenameEventsBasedObject
|
||||
#define STATIC_MoveEventsBasedObject MoveEventsBasedObject
|
||||
#define STATIC_UpdateObjectNameInEventsBasedObject UpdateObjectNameInEventsBasedObject
|
||||
#define STATIC_RenameLayout RenameLayout
|
||||
#define STATIC_RenameExternalLayout RenameExternalLayout
|
||||
|
||||
@@ -2483,6 +2483,87 @@ describe('libGD.js', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.ObjectsContainersList', function () {
|
||||
it('lets a scene object group shadow a global object group with the same name', function () {
|
||||
const project = gd.ProjectHelper.createNewGDJSProject();
|
||||
const globalObject = project
|
||||
.getObjects()
|
||||
.insertNewObject(project, 'Sprite', 'GlobalObject', 0);
|
||||
globalObject.addNewBehavior(
|
||||
project,
|
||||
'DraggableBehavior::Draggable',
|
||||
'Draggable'
|
||||
);
|
||||
const globalGroup = project
|
||||
.getObjects()
|
||||
.getObjectGroups()
|
||||
.insertNew('Group', 0);
|
||||
globalGroup.addObject('GlobalObject');
|
||||
|
||||
const layout = project.insertNewLayout('Scene', 0);
|
||||
// A scene group with the same name as the global group, empty for now.
|
||||
const sceneGroup = layout
|
||||
.getObjects()
|
||||
.getObjectGroups()
|
||||
.insertNew('Group', 0);
|
||||
|
||||
const objectsContainersList =
|
||||
gd.ObjectsContainersList.makeNewObjectsContainersListForProjectAndLayout(
|
||||
project,
|
||||
layout
|
||||
);
|
||||
|
||||
// The empty scene group is the only one considered: no type, no behaviors.
|
||||
expect(objectsContainersList.getTypeOfObject('Group')).toBe('');
|
||||
expect(
|
||||
objectsContainersList.getBehaviorsOfObject('Group', true).size()
|
||||
).toBe(0);
|
||||
expect(
|
||||
objectsContainersList.getTypeOfBehaviorInObjectOrGroup(
|
||||
'Group',
|
||||
'Draggable',
|
||||
true
|
||||
)
|
||||
).toBe('');
|
||||
|
||||
// Once the scene group has an object, only this object is considered.
|
||||
const sceneObject = layout
|
||||
.getObjects()
|
||||
.insertNewObject(project, 'TextObject::Text', 'SceneObject', 0);
|
||||
sceneObject.addNewBehavior(
|
||||
project,
|
||||
'PlatformBehavior::PlatformerObjectBehavior',
|
||||
'PlatformerObject'
|
||||
);
|
||||
sceneGroup.addObject('SceneObject');
|
||||
|
||||
expect(objectsContainersList.getTypeOfObject('Group')).toBe(
|
||||
'TextObject::Text'
|
||||
);
|
||||
const behaviors = objectsContainersList
|
||||
.getBehaviorsOfObject('Group', true)
|
||||
.toJSArray();
|
||||
expect(behaviors).toContain('PlatformerObject');
|
||||
expect(behaviors).not.toContain('Draggable');
|
||||
expect(
|
||||
objectsContainersList.getTypeOfBehaviorInObjectOrGroup(
|
||||
'Group',
|
||||
'PlatformerObject',
|
||||
true
|
||||
)
|
||||
).toBe('PlatformBehavior::PlatformerObjectBehavior');
|
||||
expect(
|
||||
objectsContainersList.getTypeOfBehaviorInObjectOrGroup(
|
||||
'Group',
|
||||
'Draggable',
|
||||
true
|
||||
)
|
||||
).toBe('');
|
||||
|
||||
project.delete();
|
||||
});
|
||||
});
|
||||
|
||||
describe('gd.Instruction', function () {
|
||||
it('initial values', function () {
|
||||
let instr = new gd.Instruction();
|
||||
@@ -3954,6 +4035,7 @@ describe('libGD.js', function () {
|
||||
layout
|
||||
),
|
||||
type,
|
||||
'',
|
||||
''
|
||||
);
|
||||
expressionNode.visit(expressionValidator);
|
||||
|
||||
@@ -34,6 +34,7 @@ describe('libGD.js - GDJS related tests', function () {
|
||||
<platform name="ios">
|
||||
<!-- GDJS_ICONS_IOS -->
|
||||
</platform>
|
||||
<!-- GDJS_ORIENTATION -->
|
||||
<!-- GDJS_EXTENSION_CORDOVA_DEPENDENCY -->
|
||||
</widget>`;
|
||||
const fakePackageJsonContent = `
|
||||
@@ -133,6 +134,7 @@ describe('libGD.js - GDJS related tests', function () {
|
||||
<platform name="ios">
|
||||
|
||||
</platform>
|
||||
<preference name="Orientation" value="landscape" />
|
||||
|
||||
</widget>`
|
||||
);
|
||||
@@ -148,6 +150,42 @@ describe('libGD.js - GDJS related tests', function () {
|
||||
}`
|
||||
);
|
||||
});
|
||||
it('does not declare an orientation in Cordova config.xml for the default orientation', () => {
|
||||
const project = gd.ProjectHelper.createNewGDJSProject();
|
||||
project.setName('My game');
|
||||
project.setVersion('1.2.3');
|
||||
project.setOrientation('default');
|
||||
|
||||
var fs = makeFakeAbstractFileSystem(gd, {
|
||||
'/fake-gdjs-root/Runtime/Cordova/www/index.html': fakeIndexHtmlContent,
|
||||
'/fake-gdjs-root/Runtime/Cordova/config.xml': fakeConfigXmlContent,
|
||||
'/fake-gdjs-root/Runtime/Cordova/package.json': fakePackageJsonContent,
|
||||
'/fake-gdjs-root/Runtime/Cordova/www/LICENSE.GDevelop.txt': '',
|
||||
});
|
||||
|
||||
const exporter = new gd.Exporter(fs, '/fake-gdjs-root');
|
||||
const exportOptions = new gd.ExportOptions(project, '/fake-export-dir');
|
||||
exportOptions.setTarget('cordova');
|
||||
expect(exporter.exportWholePixiProject(exportOptions)).toBe(true);
|
||||
exportOptions.delete();
|
||||
exporter.delete();
|
||||
|
||||
expect(fs.writeToFile).toHaveBeenCalledWith(
|
||||
'/fake-export-dir/config.xml',
|
||||
`
|
||||
<widget id="com.example.gamename" version="1.2.3">
|
||||
<name>My game</name>
|
||||
<platform name="android">
|
||||
|
||||
</platform>
|
||||
<platform name="ios">
|
||||
|
||||
</platform>
|
||||
|
||||
|
||||
</widget>`
|
||||
);
|
||||
});
|
||||
it('properly exports Cordova plugins config if required by an extension', () => {
|
||||
// Create a project with some content using the AdMob extension.
|
||||
const project = gd.ProjectHelper.createNewGDJSProject();
|
||||
@@ -191,6 +229,7 @@ describe('libGD.js - GDJS related tests', function () {
|
||||
<platform name="ios">
|
||||
|
||||
</platform>
|
||||
<preference name="Orientation" value="landscape" />
|
||||
<plugin name=\"gdevelop-cordova-admob-plus\" spec=\"0.43.0\">
|
||||
<variable name=\"APP_ID_ANDROID\" value=\"my android app id\" />
|
||||
</plugin>
|
||||
|
||||
Vendored
+13
-2
@@ -2175,9 +2175,11 @@ export class WholeProjectRefactorer extends EmscriptenObject {
|
||||
static applyRefactoringForObjectVariablesContainer(project: Project, objectVariablesContainer: VariablesContainer, initialInstancesContainer: InitialInstancesContainer, objectName: string, changeset: VariablesChangeset, originalSerializedVariables: SerializerElement): void;
|
||||
static applyRefactoringForGroupVariablesContainer(project: Project, globalObjectsContainer: ObjectsContainer, objectsContainer: ObjectsContainer, initialInstancesContainer: InitialInstancesContainer, groupVariablesContainer: VariablesContainer, objectGroup: ObjectGroup, changeset: VariablesChangeset, originalSerializedVariables: SerializerElement): void;
|
||||
static renameEventsFunctionsExtension(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static updateExtensionNameInExtension(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, sourceExtensionName: string): void;
|
||||
static updateExtensionNameInEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, sourceExtensionName: string): void;
|
||||
static updateExtensionNameInEventsBasedObject(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, sourceExtensionName: string): void;
|
||||
static renameEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static moveEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldExtensionName: string, newExtensionName: string, oldFunctionName: string, newFunctionName: string): void;
|
||||
static renameBehaviorEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, oldName: string, newName: string): void;
|
||||
static renameObjectEventsFunction(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, oldName: string, newName: string): void;
|
||||
static renameParameter(project: Project, projectScopedContainers: ProjectScopedContainers, eventsFunction: EventsFunction, parameterObjectsContainer: ObjectsContainer, oldName: string, newName: string): void;
|
||||
@@ -2191,8 +2193,10 @@ export class WholeProjectRefactorer extends EmscriptenObject {
|
||||
static renameEventsBasedObjectProperty(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, oldName: string, newName: string): void;
|
||||
static changeEventsBasedObjectPropertyType(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, propertyName: string): void;
|
||||
static renameEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static moveEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldExtensionName: string, newExtensionName: string, oldBehaviorName: string, newBehaviorName: string): void;
|
||||
static updateBehaviorNameInEventsBasedBehavior(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedBehavior: EventsBasedBehavior, sourceBehaviorName: string): void;
|
||||
static renameEventsBasedObject(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static renameEventsBasedObject(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldObjectName: string, newObjectName: string): void;
|
||||
static moveEventsBasedObject(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, oldExtensionName: string, newExtensionName: string, oldObjectName: string, newObjectName: string): void;
|
||||
static updateObjectNameInEventsBasedObject(project: Project, eventsFunctionsExtension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, sourceObjectName: string): void;
|
||||
static renameLayout(project: Project, oldName: string, newName: string): void;
|
||||
static renameExternalLayout(project: Project, oldName: string, newName: string): void;
|
||||
@@ -2248,6 +2252,7 @@ export class ParameterValidationResult extends EmscriptenObject {
|
||||
export class InstructionValidator extends EmscriptenObject {
|
||||
static validateParameter(platform: Platform, projectScopedContainers: ProjectScopedContainers, instruction: Instruction, metadata: InstructionMetadata, parameterIndex: number): ParameterValidationResult;
|
||||
static isParameterValid(platform: Platform, projectScopedContainers: ProjectScopedContainers, instruction: Instruction, metadata: InstructionMetadata, parameterIndex: number): boolean;
|
||||
static getObjectNameForParameter(projectScopedContainers: ProjectScopedContainers, instruction: Instruction, parameterType: string): string;
|
||||
}
|
||||
|
||||
export class ObjectTools extends EmscriptenObject {
|
||||
@@ -2267,6 +2272,12 @@ export class PropertyFunctionGenerator extends EmscriptenObject {
|
||||
static updateReturnActionType(project: Project, eventsFunction: EventsFunction): void;
|
||||
}
|
||||
|
||||
export class ChildObjectForwardFunctionGenerator extends EmscriptenObject {
|
||||
static generateChildObjectForwardFunctions(project: Project, extension: EventsFunctionsExtension, eventsBasedObject: EventsBasedObject, childObjectName: string): void;
|
||||
static hasAnyChildCustomObject(project: Project, eventsBasedObject: EventsBasedObject): boolean;
|
||||
static getChildCustomObjectNames(project: Project, eventsBasedObject: EventsBasedObject): VectorString;
|
||||
}
|
||||
|
||||
export class UsedExtensionsResult extends EmscriptenObject {
|
||||
getUsedExtensions(): SetString;
|
||||
}
|
||||
@@ -2380,7 +2391,7 @@ export class VectorExpressionParserError extends EmscriptenObject {
|
||||
export class ExpressionParser2NodeWorker extends EmscriptenObject {}
|
||||
|
||||
export class ExpressionValidator extends EmscriptenObject {
|
||||
constructor(platform: Platform, projectScopedContainers: ProjectScopedContainers, rootType: string, extraInfo: string);
|
||||
constructor(platform: Platform, projectScopedContainers: ProjectScopedContainers, rootType: string, rootObjectName: string, extraInfo: string);
|
||||
getAllErrors(): VectorExpressionParserError;
|
||||
getFatalErrors(): VectorExpressionParserError;
|
||||
getDeprecationWarnings(): VectorExpressionParserError;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdChildObjectForwardFunctionGenerator {
|
||||
static generateChildObjectForwardFunctions(project: gdProject, extension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, childObjectName: string): void;
|
||||
static hasAnyChildCustomObject(project: gdProject, eventsBasedObject: gdEventsBasedObject): boolean;
|
||||
static getChildCustomObjectNames(project: gdProject, eventsBasedObject: gdEventsBasedObject): gdVectorString;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
// Automatically generated by GDevelop.js/scripts/generate-types.js
|
||||
declare class gdExpressionValidator extends gdExpressionParser2NodeWorker {
|
||||
constructor(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, rootType: string, extraInfo: string): void;
|
||||
constructor(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, rootType: string, rootObjectName: string, extraInfo: string): void;
|
||||
getAllErrors(): gdVectorExpressionParserError;
|
||||
getFatalErrors(): gdVectorExpressionParserError;
|
||||
getDeprecationWarnings(): gdVectorExpressionParserError;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
declare class gdInstructionValidator {
|
||||
static validateParameter(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, metadata: gdInstructionMetadata, parameterIndex: number): gdParameterValidationResult;
|
||||
static isParameterValid(platform: gdPlatform, projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, metadata: gdInstructionMetadata, parameterIndex: number): boolean;
|
||||
static getObjectNameForParameter(projectScopedContainers: gdProjectScopedContainers, instruction: gdInstruction, parameterType: string): string;
|
||||
delete(): void;
|
||||
ptr: number;
|
||||
};
|
||||
@@ -5,9 +5,11 @@ declare class gdWholeProjectRefactorer {
|
||||
static applyRefactoringForObjectVariablesContainer(project: gdProject, objectVariablesContainer: gdVariablesContainer, initialInstancesContainer: gdInitialInstancesContainer, objectName: string, changeset: gdVariablesChangeset, originalSerializedVariables: gdSerializerElement): void;
|
||||
static applyRefactoringForGroupVariablesContainer(project: gdProject, globalObjectsContainer: gdObjectsContainer, objectsContainer: gdObjectsContainer, initialInstancesContainer: gdInitialInstancesContainer, groupVariablesContainer: gdVariablesContainer, objectGroup: gdObjectGroup, changeset: gdVariablesChangeset, originalSerializedVariables: gdSerializerElement): void;
|
||||
static renameEventsFunctionsExtension(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static updateExtensionNameInExtension(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, sourceExtensionName: string): void;
|
||||
static updateExtensionNameInEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, sourceExtensionName: string): void;
|
||||
static updateExtensionNameInEventsBasedObject(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, sourceExtensionName: string): void;
|
||||
static renameEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static moveEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldExtensionName: string, newExtensionName: string, oldFunctionName: string, newFunctionName: string): void;
|
||||
static renameBehaviorEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void;
|
||||
static renameObjectEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string): void;
|
||||
static renameParameter(project: gdProject, projectScopedContainers: gdProjectScopedContainers, eventsFunction: gdEventsFunction, parameterObjectsContainer: gdObjectsContainer, oldName: string, newName: string): void;
|
||||
@@ -21,8 +23,10 @@ declare class gdWholeProjectRefactorer {
|
||||
static renameEventsBasedObjectProperty(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, oldName: string, newName: string): void;
|
||||
static changeEventsBasedObjectPropertyType(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, propertyName: string): void;
|
||||
static renameEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static moveEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldExtensionName: string, newExtensionName: string, oldBehaviorName: string, newBehaviorName: string): void;
|
||||
static updateBehaviorNameInEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, sourceBehaviorName: string): void;
|
||||
static renameEventsBasedObject(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void;
|
||||
static renameEventsBasedObject(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldObjectName: string, newObjectName: string): void;
|
||||
static moveEventsBasedObject(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldExtensionName: string, newExtensionName: string, oldObjectName: string, newObjectName: string): void;
|
||||
static updateObjectNameInEventsBasedObject(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedObject: gdEventsBasedObject, sourceObjectName: string): void;
|
||||
static renameLayout(project: gdProject, oldName: string, newName: string): void;
|
||||
static renameExternalLayout(project: gdProject, oldName: string, newName: string): void;
|
||||
|
||||
@@ -214,6 +214,7 @@ declare class libGDevelop {
|
||||
ObjectTools: Class<gdObjectTools>;
|
||||
EventsBasedObjectDependencyFinder: Class<gdEventsBasedObjectDependencyFinder>;
|
||||
PropertyFunctionGenerator: Class<gdPropertyFunctionGenerator>;
|
||||
ChildObjectForwardFunctionGenerator: Class<gdChildObjectForwardFunctionGenerator>;
|
||||
UsedExtensionsResult: Class<gdUsedExtensionsResult>;
|
||||
UsedExtensionsFinder: Class<gdUsedExtensionsFinder>;
|
||||
UsedObjectTypeFinder: Class<gdUsedObjectTypeFinder>;
|
||||
|
||||
@@ -37,6 +37,8 @@ GDevelop is a powerful, no-code, open-source game engine. **GDevelop (BYOK Editi
|
||||
### 🛡️ 4. Upstream Synchronized & Secure
|
||||
- Continuously synchronized with upstream [4ian/GDevelop](https://github.com/4ian/GDevelop).
|
||||
- Automated PR-only sync workflow with workflow locking to protect against supply-chain attacks (e.g. Shai-Hulud).
|
||||
- Find GDevelop games on [gd.games](https://gd.games), the gaming platform for games powered by GDevelop.
|
||||
- See the [showcase of games](https://gdevelop.io/games) created with GDevelop and published on Steam, iOS (App Store), Android (Google Play), Itch.io, Newgrounds, CrazyGames, Poki...
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -207,7 +207,11 @@ declare module "react-dnd" {
|
||||
|
||||
declare type DragDropMonitor = {
|
||||
isDragging(): boolean,
|
||||
didDrop(): boolean,
|
||||
getItem(): any,
|
||||
getItemType(): mixed,
|
||||
getClientOffset(): ClientOffset | null,
|
||||
subscribeToStateChange(listener: () => void): () => void,
|
||||
...
|
||||
};
|
||||
|
||||
|
||||
@@ -166,9 +166,9 @@
|
||||
"start": "npm run import-resources && concurrently \"react-app-rewired start\" \"node scripts/watch-serve-GDJS-runtime.js\" -n \"editor,game engine\" --kill-others",
|
||||
"start:vite": "npm run import-resources && vite",
|
||||
"electron-app": "cd ../electron-app && npm run start",
|
||||
"build": "npm run import-resources && react-app-rewired build",
|
||||
"build": "npm run import-resources && react-app-rewired build && node scripts/check-build-output.js",
|
||||
"build:vite": "npm run import-resources && vite build",
|
||||
"build:dev": "npm run import-resources && REACT_APP_ENV=development react-app-rewired build && node scripts/copy-GDJS-Runtime-to-build.js",
|
||||
"build:dev": "npm run import-resources && REACT_APP_ENV=development react-app-rewired build && node scripts/check-build-output.js && node scripts/copy-GDJS-Runtime-to-build.js",
|
||||
"format": "prettier --write \"src/!(locales)/**/*.{js,ts,tsx}\"",
|
||||
"check-format": "prettier --list-different \"src/!(locales)/**/*.{js,ts,tsx}\"",
|
||||
"lint": "eslint src/ --max-warnings=0",
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
// @ts-check
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const buildPath = path.join(__dirname, '..', 'build');
|
||||
|
||||
/**
|
||||
* Verify that the build folder contains the files expected for a working
|
||||
* app. This is important because the build can, in some cases, "succeed"
|
||||
* while producing an empty or unusable build folder. For example, an error
|
||||
* in a web worker file (compiled by `worker-loader` in a child compilation)
|
||||
* is not reported as an error of the whole compilation - see
|
||||
* `Resource3DPreview.worker.js`.
|
||||
*/
|
||||
const checkBuildOutput = () => {
|
||||
const errors = [];
|
||||
|
||||
/** @param {string} directory @param {RegExp} pattern */
|
||||
const checkFileExists = (directory, pattern) => {
|
||||
const directoryPath = path.join(buildPath, directory);
|
||||
const files = fs.existsSync(directoryPath)
|
||||
? fs.readdirSync(directoryPath)
|
||||
: [];
|
||||
if (!files.some(file => pattern.test(file))) {
|
||||
errors.push(
|
||||
`Expected a file matching ${pattern} in "build/${directory}".`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
checkFileExists('', /^index\.html$/);
|
||||
checkFileExists('static/js', /^main\..*\.js$/);
|
||||
checkFileExists('static/css', /^main\..*\.css$/);
|
||||
// Workers are compiled separately by worker-loader: check they were
|
||||
// properly built too.
|
||||
checkFileExists('', /^Resource3DPreview\.worker\..*\.worker\.js$/);
|
||||
checkFileExists('', /^BackgroundSerializer\.worker\..*\.worker\.js$/);
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error(
|
||||
'❌ The build folder is incomplete - the build silently failed:'
|
||||
);
|
||||
errors.forEach(error => console.error(` - ${error}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log('✅ Build folder content looks complete.');
|
||||
};
|
||||
|
||||
checkBuildOutput();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user