Compare commits

...

156 Commits

Author SHA1 Message Date
D8H
402e54f706 Fix the Ease expression of the Tween extension (#5810)
Don't show in changelog
2023-10-20 11:07:19 +02:00
github-actions[bot]
dc29c27272 Update translations [skip ci] (#5803) 2023-10-20 09:01:47 +02:00
Florian Rival
462bb84c51 Fix exporting not shown on iOS (#5808) 2023-10-19 17:26:25 +02:00
AlexandreS
a775e79bae Catch instance rendering process in scene editor (#5806)
Don't show in changelog
2023-10-19 16:32:57 +02:00
Florian Rival
bc8c867f23 Fix parameters not accessible in expressions for 'Action With Operator' functions (#5805) 2023-10-19 15:55:55 +02:00
AlexandreS
f34bf2b7b4 Focus confirm delete button (#5804)
Don't show in changelog
2023-10-19 15:52:18 +02:00
Florian Rival
e2d482e1aa Fix long formulas/expressions going out of the screen in the events sheet 2023-10-19 14:51:21 +02:00
AlexandreS
0724ae34d9 Use electron remote shell to open project folder (#5801)
Don't show in changelog
2023-10-19 14:47:31 +02:00
github-actions[bot]
2fc1c2fd86 Update translations [skip ci] (#5774) 2023-10-19 12:19:58 +02:00
D8H
9a42ae11ad Tween extension rework (#5710)
Fix bugs and change implementation for a better maintainability.
2023-10-19 11:24:32 +02:00
Florian Rival
32773e06f6 Improve error message when exception at runtime (#5800) 2023-10-19 10:20:08 +02:00
Florian Rival
00508df014 Don't show an undeclared variable as an error in action/condition editor 2023-10-18 17:22:07 +02:00
Florian Rival
c3cf5b7002 Display an error message in game when an exception/crash is detected (#5799) 2023-10-18 17:19:11 +02:00
AlexandreS
df7fab84b8 Bump newIDE version (#5798) 2023-10-18 16:39:32 +02:00
AlexandreS
95a0012c5e Load texture in runtime even if filename does not have an extension (#5797)
Don't show in changelog
2023-10-18 16:38:25 +02:00
AlexandreS
4bf644f9f2 Add "Help > About GDevelop" menu option for non-Mac users (#5796)
---------

Co-authored-by: Tristan Rhodes <tristan.rhodes@gmail.com>
2023-10-18 13:01:02 +02:00
AlexandreS
79cad1da3a Fix error message not cleared after choosing a color in a color picker (#5794) 2023-10-18 12:47:57 +02:00
AlexandreS
ffc6f5786b Do not clear selection when panning with middle mouse button (#5795) 2023-10-18 12:24:08 +02:00
Florian Rival
07df65f8c2 Fix a potential crash by ensuring selection of instances does not contain deleted global objects (#5791)
- Also keep the selection of objects/folders when changing between scene tabs (unless they have been deleted)
2023-10-18 11:12:19 +02:00
D8H
61f8f0ed66 Fix Bitmap text resource action parameter type (#5793) 2023-10-18 10:55:20 +02:00
D8H
c24e2e5ace Use similar wording for angle conditions (#5792)
* Don't show in changelog
2023-10-17 18:45:17 +02:00
D8H
25c72a6d1f Optimize evaluation of several forces on 1 object (#5789) 2023-10-17 17:09:27 +02:00
AlexandreS
6153b23e7d Improve data sent to automatic error report (#5790)
Also, attempt to fix a crash at GDevelop editor opening.
2023-10-17 16:48:47 +02:00
Florian Rival
1b7ccfded3 Fix warning 2023-10-17 14:00:37 +02:00
Florian Rival
ec57a0a80d Improve the Share/Publish dialog: larger window, clearer sections and wording (#5785) 2023-10-17 11:28:29 +02:00
D8H
95a9e37aba Add animation time control for sprites and 3D models (#5777) 2023-10-17 10:02:49 +02:00
D8H
787274f7fa Fix 3D distance sorting issues by using a higher default near plane (#5784) 2023-10-17 09:55:23 +02:00
D8H
ceb24f0edb Unify text expressions conditions and actions between objects (#5778) 2023-10-16 18:17:50 +02:00
D8H
028bf7a70d Fix Physics behavior forces effect under 60 fps (#5783) 2023-10-16 17:50:52 +02:00
AlexandreS
08471d0356 Use componentWillReceiveProps rather than componentWillUpdate (#5782)
Don't show in changelog
2023-10-16 17:15:27 +02:00
AlexandreS
921add6665 Organize your objects into folders (#5655)
You can now create folders as a means to organize your objects when editing a scene or an external layout.

Thanks to @Silver-Streak, Matt Pin, @arthuro555, SnailMail, @TheGemDev, Peeble, Rasterisko for all the help designing and testing the feature.

Note that object tags have been dropped in this version since they were not practical to use thus not widespread used.
2023-10-16 17:04:20 +02:00
AlexandreS
dabff06891 Add error message when trying to login without connection to internet (#5780) 2023-10-16 11:37:43 +02:00
AlexandreS
967033c407 Fix scene loading for video resources (#5772)
Listen error event on base texture loading
Also makes file watcher listen on addition and removal of files.
2023-10-13 15:03:57 +02:00
Florian Rival
0809749ddc Hide expressions GetArgumentAsString and GetArgumentAsNumber as they are now replaced by just being able to write the parameter name in an expression (#5773) 2023-10-13 10:14:28 +02:00
github-actions[bot]
8a27801355 Update translations [skip ci] (#5766)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-10-12 18:30:18 +02:00
AlexandreS
20cba9d7ed Fix linter and types (#5770)
Don't show in changelog
2023-10-12 18:00:31 +02:00
AlexandreS
3878b93c94 Fix: Prevent loading of forbidden GIF file from crashing a game loading screen (#5769) 2023-10-12 17:01:10 +02:00
D8H
645d5331cb Add a setting to show warnings about deprecated instructions (#5755) 2023-10-12 16:55:02 +02:00
AlexandreS
91db750632 Reload resources in the editor when the resource changes (#5631)
GDevelop now refreshes resources (images, videos, 3D models, fonts, etc.) when a change is detected on the filesystem so that you can immediately see the changes.

This allows to efficiently use GDevelop alongside other tools (Tiled or LDtk for instance).

Notes:
- This logically only affects the desktop version for projects saved on the filesystem;
- You can deactivate it in the preferences.
2023-10-12 16:01:01 +02:00
Clément Pasteau
740485b54a Activate support for unicode characters (including emojis) for all names (objects, groups, variables, functions, parameters) on all projects (#5703) 2023-10-12 15:25:37 +02:00
Florian Rival
e960fe7c5b Fix unicode/emojis not supported in function parameter names (#5768) 2023-10-12 15:20:12 +02:00
AlexandreS
0633c7b474 Fix: Prevent root variable from having white spaces (#5767) 2023-10-12 15:13:18 +02:00
AlexandreS
704bfd40cb Fix example opening wrongfully opening "Create from scratch" dialog (#5765) 2023-10-12 14:34:24 +02:00
github-actions[bot]
310abe7a13 Update translations [skip ci] (#5759)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-10-12 14:11:12 +02:00
Daniel R
b0f9bed273 Add string condition operators: starts with, ends with and contains (#4970)
- Thanks @danired
2023-10-12 13:33:32 +02:00
Clément Pasteau
766a62ad9b Invalidate cache when updating Panel Sprite opacity (#5764)
* This fixes a bug where creating a panel sprite with low opacity would prevent changing its opacity afterwards
2023-10-12 13:27:08 +02:00
D8H
ec6f669d94 Add HSL adjustment, motion blur and shockwave effects (#5760) 2023-10-12 10:49:29 +02:00
D8H
36a2408be0 Fix particles angle when the speed is negative (#5762) 2023-10-12 10:48:23 +02:00
Clément Pasteau
1343210a2b Fix "Locate file" for resource to open the folder in front of the app (#5758) 2023-10-11 15:52:27 +02:00
github-actions[bot]
1e76fedd7b Update translations [skip ci] (#5757)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-11 15:15:25 +02:00
Clément Pasteau
3923641988 Bump to 5.2.176 (#5756) 2023-10-11 10:42:18 +02:00
github-actions[bot]
a7d488626c Update translations [skip ci] (#5751)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-11 10:42:04 +02:00
Clément Pasteau
33f20b9bde Fix adding multiple assets after navigating into folder, correctly taking the assets into account (#5752) 2023-10-11 10:23:03 +02:00
AlexandreS
6c4cd6343f Fix Ctrl+Z closing project on Azerty keyboards (#5753)
Use event which property rather than code property when possible
2023-10-11 10:03:56 +02:00
Clément Pasteau
f1c04df0ee Do not sort animations by name in the Animations Dropdown for consistency (#5754) 2023-10-10 18:49:44 +02:00
D8H
9b466ff574 Fix particle emitter rotation speed (#5747) 2023-10-10 18:42:28 +02:00
Clément Pasteau
2b0969600e Deactivate accessibility in the editor too (#5750)
Do not show in changelog
2023-10-10 17:19:32 +02:00
github-actions[bot]
7ed1eee8f9 Update translations [skip ci] (#5748)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-10 17:11:17 +02:00
Clément Pasteau
9b9b729b42 Improve error message and component when crash (#5749)
Do not show in changelog
2023-10-10 15:27:23 +02:00
github-actions[bot]
530969d4b0 Update translations [skip ci] (#5735)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-10-10 14:19:42 +02:00
Clément Pasteau
11ef87b9ee Force window events to not be passive, so they can be prevented on mobile. (#5745)
This fixes a bug where mouse events where triggered on mobile, introducing unwanted interactions.
2023-10-10 14:17:16 +02:00
Clément Pasteau
e1857e2e1e Deactivate PixiJS accessibility plugin (#5746)
* For developer changelog
* Better remove it, as this isn't used and adds unnecessary elements in the Dom.
2023-10-10 14:16:57 +02:00
Florian Rival
d1385e5fc2 Fix completions in expressions not being case insensitive and limited for objects/variables/properties/parameters (#5742) 2023-10-10 10:48:33 +02:00
AlexandreS
9298026179 Add possibility to set Z offset when creating instances from external layout (#5704) 2023-10-10 09:40:10 +02:00
Florian Rival
913e367f6f Allow to properly use variables/properties/parameters in brackets to access a structure variable (#5738)
* This means expressions like `MyStructure[SomeIndexVariable]` will work properly for a structure if `SomeIndexVariable` is a string variable. Gdevelop now properly uses the type of the variable/property/parameter (to avoid considering what's inside the brackets as a number if it's a string). Remember to declare your variables in the variables editor so that you can use them in expressions.
2023-10-09 18:21:49 +02:00
AlexandreS
1bc27bdd8b Fix shape painter drawing stars crashing games 2023-10-09 10:55:04 +02:00
Florian Rival
4d8b39df95 Add more tests for new variable syntax
Don't show in changelog
2023-10-08 19:02:55 +02:00
Florian Rival
e6970319b9 Fix usage of string properties and parameters using the new simplified syntax (#5737)
* Properties or parameters holding a string where wrongly interpreted as a number using the new syntax.
2023-10-08 17:41:34 +02:00
Florian Rival
cdf21ebd4c Allow to use the new variable syntax for groups (#5730)
* You can write `MyObjectGroup.SomeVariable` in the expressions as long as all the objects of the groups have this variable declared in the objects variable editor.
2023-10-08 11:59:35 +02:00
Clément Pasteau
dc014e2ab0 Fix missing ID in simple text fields (#5728)
Do not show in changelog
2023-10-06 17:17:36 +02:00
Clément Pasteau
25509e1c7b Bump to 5.2.175 (#5731) 2023-10-06 17:11:16 +02:00
AlexandreS
516621e989 Fix infinite loop due to 2 items havinf the same unique id (#5729)
Don't show in changelog
2023-10-06 15:48:06 +02:00
github-actions[bot]
9b2435b210 Update translations [skip ci] (#5726)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-06 15:27:52 +02:00
Clément Pasteau
f894e1962a Split the loading of videos to ensure they are loaded without autoplay (#5723) 2023-10-06 15:06:30 +02:00
Florian Rival
cc35f51158 Fix "Add or edit variables" shown when not applicable (#5725) 2023-10-06 09:41:49 +02:00
Clément Pasteau
a7ef02f1c1 Bump to 5.2.174 (#5721) 2023-10-05 12:35:38 +02:00
github-actions[bot]
2cc7506ad7 Update translations [skip ci] (#5714)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-05 12:28:00 +02:00
D8H
608b460da7 Fix particle emitter spray cone angle being properly updated (#5720) 2023-10-05 12:19:39 +02:00
Clément Pasteau
6b082731c2 Fix dom errors because using a component inside a listItemText (#5717)
Do not show in changelog
2023-10-04 16:15:12 +02:00
Clément Pasteau
e040dd5f28 Deprecate "Always" condition (#5716)
* It was considered misleading as using it or not didn't change the condition's result
2023-10-04 15:07:45 +02:00
Clément Pasteau
ae64cfb3bb Fix crash when using Tilemaps on the web-app (#5715) 2023-10-04 14:55:29 +02:00
Clément Pasteau
02b96eb298 Fix loading video resources in Runtime (#5713) 2023-10-04 10:55:57 +02:00
Clément Pasteau
6f59a3ba03 Improve autocomplete options height on mobile (#5709) 2023-10-03 16:27:12 +02:00
github-actions[bot]
6180016b51 Update translations [skip ci] (#5706)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-10-03 16:10:54 +02:00
Clément Pasteau
b15e72410e Fix DOM and Console errors (#5708)
Do not show in changelog
2023-10-03 15:56:34 +02:00
Clément Pasteau
d6fbb0e78b Show a link to try premium game templates (#5707) 2023-10-03 13:51:04 +02:00
Clément Pasteau
8eb9136847 Fix wrong jsdoc typing (#5705)
Do not show in changelog
2023-10-02 16:28:59 +02:00
Clément Pasteau
46c02ad9b7 Improvements indicator wording (#5702)
Do not show in changelog
2023-10-02 11:50:42 +02:00
github-actions[bot]
9846a9b45e Update translations [skip ci] (#5701)
Co-authored-by: AlexandreSi <AlexandreSi@users.noreply.github.com>
2023-10-02 11:41:41 +02:00
AlexandreS
3d86820581 Fix: Do not highlight instance on hover if moving an instance around (#5700) 2023-10-02 10:50:09 +02:00
D8H
b636eeb859 Fix a regression on the negation of compare number/string conditions (#5698) 2023-09-29 20:04:33 +02:00
github-actions[bot]
189bcc4ceb Update translations [skip ci] (#5697)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-09-29 16:09:38 +02:00
Clément Pasteau
3fe031063c Bump to 5.2.173 (#5696) 2023-09-29 15:30:51 +02:00
github-actions[bot]
d10712d584 Update translations [skip ci] (#5691)
Co-authored-by: ClementPasteau <ClementPasteau@users.noreply.github.com>
2023-09-29 14:48:29 +02:00
Clément Pasteau
1721fb6211 Cloud projects can now be shared with collaborators (#5687)
* Ability to add 1 guest per project or multiple collaborators with a startup subscription
* Projects shared appear on the projects list of all collaborators and all are able to open it
* A warning appears if you are trying to save a project that someone else modified, to avoid losing changes, so ensure you bring changes one at a time!
2023-09-29 14:42:18 +02:00
D8H
e31ea07902 Fix a 0-size frame buffer exception (#5695)
* Don't show in changelog
2023-09-29 11:05:42 +02:00
Arthur Pacaud (arthuro555)
85929b6e76 Make it possible to render a GDevelop scene in VR (#5681)
Only show in developer changelog
2023-09-27 23:15:38 +02:00
Clément Pasteau
0662607409 Don't show premium features if GDevelop is not the seller (#5692)
Do not show in changelog
2023-09-27 17:51:26 +02:00
Arthur Pacaud (arthuro555)
52f10d73ab Make the Debugger more resilient to errors (#5680) 2023-09-27 16:47:01 +02:00
Florian Rival
d4cb4fafd3 Rework the variables editor so that it's more performant and comfortable to use (#5690)
- The editor is now way faster to open when lots of variables are present. It's also way faster when editing and when lots of variables are present.
- Variable names that have forbidden characters are automatically fixed, not breaking your workflow.
- Deletion of multiple variables in an array was fixed. 
- Also fix adding a new variable sometimes overriding a variable inherited from the object
- Allow editing text variables in a separate window (more comfortable and faster)
2023-09-27 16:30:46 +02:00
D8H
9f4b12ce8a Add missing text parameter types for event-based extensions (#5689)
* Replace "effect parameter" with "effect property" in the UI and documentation.
* Inline property fields for effect name and property.
2023-09-27 11:05:19 +02:00
github-actions[bot]
b5b7edafb1 Update translations [skip ci] (#5640)
Co-authored-by: D8H <D8H@users.noreply.github.com>
2023-09-27 10:06:31 +02:00
D8H
1335819ea7 Add an expression, a condition and an action for objects center on Z axis (#5682) 2023-09-25 10:12:03 +02:00
Florian Rival
aa7ae758f7 Avoid some useless rendering of toolbars (#5679)
Don't show in changelog
2023-09-23 15:35:22 +02:00
Tristan Rhodes
7da88662fb Fix action sentence for BBText objects alignment (#5678) 2023-09-23 13:37:05 +02:00
Florian Rival
dbc1385787 Fix error when loading resources in editor (#5675)
Don't show in changelog
2023-09-23 13:35:00 +02:00
Florian Rival
8944df1831 Add support for writing parameter names directly in expressions (#5676) 2023-09-22 22:41:13 +02:00
Florian Rival
a0925c79c3 Add a new simplified way to use variables in expressions, and automate renaming of variables in events (#5580)
* You can now simply write the name of the scene or global variable in an expression to use it: `1 + MyVariable` (instead of `1 + Variable(MyVariable)`).
* Objects can also have their variables accessed like this: `MyObject.MyVariable` (instead of `MyObject.Variable(MyVariable)`.
* This also works for properties inside functions of behaviors or custom objects. For example, you can write `Speed` instead of `Object.Behavior::PropertySpeed()`.
* This syntax will also handle all types of variables without the need to write ToString. For example, you can now write "Score: " + CoinsEarned instead of "Score: " + ToString(Variable(CoinsEarned)).
* This syntax will only work (and autocompletions will be shown) if you add the variable in the variables editor of the scene, the project or in the variables of the object. It's a good practice to always declare your variables here and give them a default value - do it to benefit from this new simplified syntax, which will make your formulas and expressions much more readable.
* When you rename a variable in an editor, it will now rename the variables everywhere in the events of the project. This makes it much easier to change the name of a variable if you find a better one. Note that this works for "rootæ variables, but not variables inside structures or arrays.
2023-09-21 18:56:12 +02:00
D8H
73642092bb Upgrade games 2D rendering engine (PixiJS) to version 7.3.0 (#5633)
* This opens the path to multiple performance improvements (faster WebGL rendering, and then WebGPU rendering) as well as optimised resources loading in the future.
2023-09-21 17:14:05 +02:00
D8H
dfe84fbe2b Fix an issue in the frame rate synchronization of the Physics behavior (#5649)
* It should avoid jittery cameras that was happening from time to time.
2023-09-19 15:11:57 +02:00
Aurélien Vivet
cd896c399d Fix typo (#5668)
- Fix the width word used in the getHeight() method for the objects.
- Fix the purial of the window in an action.
2023-09-19 13:32:34 +01:00
Clément Pasteau
d8126ead34 Prevent triggering layout change when the virtual keyboard appears. (#5666)
* It fixes a bug on mobile where starting typing would show the virtual keyboard, triggering a resize of the window, which would change the layout and make the input disappear.
2023-09-18 18:15:22 +02:00
AlexandreS
bdbbe1f409 Fix Health bar and Joystick guided lessons in web app (#5665) 2023-09-18 10:38:36 +02:00
D8H
3a241d03f1 Allow sprite animations to play backward with a negative speed scale (#5662) 2023-09-18 09:24:51 +02:00
Florian Rival
e4cf92e949 Improve documentation of expressions by showing parameter in a human readable way 2023-09-16 16:35:24 +02:00
Florian Rival
0dc513317c Revert "Update createObjectsFrom and createObjectsFromExternalLayout to return a list of objects that it creates. (#5658)" (#5660)
This reverts commit 3e85242165.
2023-09-15 14:46:33 +02:00
Dawid Fatyga
3e85242165 Update createObjectsFrom and createObjectsFromExternalLayout to return a list of objects that it creates. (#5658)
Only show in developer changelog
2023-09-15 14:45:25 +02:00
D8H
ca9fe51d99 Fix scaling actions icons (#5647) 2023-09-13 12:54:22 +02:00
Florian Rival
2e9a5ee287 Fix ExpressionParser2 tests (#5654)
Don't show in changelog
2023-09-13 12:54:09 +02:00
Clément Pasteau
b57e944da5 Fix opening a custom object after having opened a pack in the store (#5651) 2023-09-11 14:30:36 +02:00
Florian Rival
06cdeb763d Fix user questionnaire sometimes shown even when already filled (#5648) 2023-09-08 14:15:00 +02:00
Clément Pasteau
5809a02a26 Add more events about premium game templates opening (#5644)
Do not show in changelog
2023-09-07 19:24:23 +02:00
Florian Rival
3a2878c737 Add more restricted characters in internationalized names (#5643)
Only show in developer changelog
2023-09-07 14:54:56 +02:00
Clément Pasteau
2cbfb806e2 Fix speed of adding an asset (#5642)
Do not show in changelog
2023-09-07 14:36:26 +02:00
AlexandreS
18211d197a Remember collapsed state of object behaviors configuration panels (#5641) 2023-09-07 14:30:23 +02:00
Clément Pasteau
128657c876 Prevent throwing when leaderboards cannot be fetched (#5639)
Do not show in changelog
2023-09-07 11:26:42 +02:00
github-actions[bot]
930d4d394f Update translations [skip ci] (#5629)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-09-07 09:53:55 +02:00
D8H
cc460657e5 Upgrade Storybook to 7.4.0 (#5625)
Only show in developer changelog 

Co-authored-by: Clément Pasteau <4895034+ClementPasteau@users.noreply.github.com>
Co-authored-by: Florian Rival <Florian.Rival@gmail.com>
2023-09-06 20:04:01 +02:00
AlexandreS
d9f2cb51a3 Add view of team and its groups (classrooms) for education plan holders (#5603) 2023-09-06 17:00:19 +02:00
AlexandreS
d736afe4a0 Fix events search crashing the events sheet when a parameter contains more than 5 spaces (#5628) 2023-09-06 16:53:35 +02:00
Clément Pasteau
10825c0a8c Allow using Premium game templates, high quality games (#5611)
* Those templates are ready-made games, which can be used for as many games as you want
* They directly work on mobile, web and desktop for a seamless experience
* They contain everything for a full game, start menu, options, leaderboards, and even character selection
* They can easily be modified to integrate your assets, or your custom logic to make this game yours
2023-09-06 15:26:45 +02:00
Clément Pasteau
a75a8e1bfe Add steamworks extension (#5440) 2023-09-06 09:31:27 +02:00
D8H
9a02905fb6 Refactor resources loading in Runtime (#5632)
* Only for dev
2023-09-06 00:12:07 +02:00
AlexandreS
414292812f Fix context menu actions disappearing when opening a project and Resources tab is restored (#5630) 2023-09-05 16:29:28 +02:00
AlexandreS
4d9035ad07 Display the navigation bar of the homepage at the bottom on mobile (#5627) 2023-09-05 14:10:01 +02:00
github-actions[bot]
bcf0184fdd Update translations [skip ci] (#5622)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-09-05 10:28:24 +02:00
Florian Rival
817f2cf758 Display a message if a subscription is valid but will expire in the future (#5626) 2023-09-04 12:35:00 +02:00
Aurélien Vivet
3c5e10f5c3 Fix typo in theme template (theme.json) (#5623)
Thanks to @Entr0py404 for finding this!
Only show in developer changelog

Co-authored-by: Tristan Rhodes <tristan.rhodes@gmail.com>
2023-09-02 18:38:34 +02:00
Clément Pasteau
7d19b811e4 Fix creating a local project in the right folder (#5621) 2023-09-01 17:46:47 +02:00
Clément Pasteau
17b3579e5b Bump to 5.2.172 (#5620) 2023-09-01 14:32:32 +02:00
github-actions[bot]
afa8d4f7f6 Update translations [skip ci] (#5614)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-09-01 14:30:07 +02:00
Florian Rival
b262cb4c3e Fix link in disabled events still processed internally (#5619)
* This could generate infinite loop crashs even when a link is included as
a sub event of a disabled event.
2023-09-01 13:58:51 +02:00
Clément Pasteau
1c69302c02 Fix using correct parameter for size capability behavior (#5618)
Do not show in changelog
2023-09-01 09:57:51 +02:00
AlexandreS
5ba36916c6 Display a nicer refresh icon in the UI and fields (#5617) 2023-09-01 09:15:21 +02:00
D8H
7f3e2b210a Fix missing effect tab on some objects (#5616) 2023-08-31 20:41:06 +02:00
D8H
f83df15817 Fix a performance regression on scenes with a lot of sprites (#5615) 2023-08-31 18:19:50 +02:00
Gleb Volkov
40639f5578 Add clearCanvas flag to RuntimeScene for multi-scene rendering support (#5525)
* Add `clearCanvas` field to `RuntimeScene` class and pass it to `PIXI.Renderer.render` as `clear` option to support multi-scene rendering.

* Show in developer changelog
2023-08-31 10:10:15 +02:00
Florian Rival
f07e3bfe19 Reduce "layout shifts" in Events Sheet when adding events or modifying them (#5612)
* Browsing the events sheet and editing events should be a bit more comfortable now.
2023-08-31 10:09:47 +02:00
D8H
ef9a82ee33 Fix scene editor window rectangle color (#5613) 2023-08-30 21:44:01 +02:00
D8H
e916662ad4 Fix behavior list placeholder for object with default behaviors (#5610) 2023-08-29 17:32:50 +02:00
AlexandreS
d7ca46455e Abstract project caching (#5609)
Don't show in changelog
2023-08-29 16:52:41 +02:00
AlexandreS
d193b9375e Small improvements in tabs opening (#5608)
Don't show in changelog
2023-08-29 16:22:03 +02:00
Clément Pasteau
90b830853a Bump version to 5.2.171 (#5607) 2023-08-29 14:12:27 +02:00
github-actions[bot]
f6b4cab966 Update translations (#5605)
Co-authored-by: 4ian <4ian@users.noreply.github.com>
2023-08-29 14:09:52 +02:00
D8H
0ebec6fa02 Fix regression in some expressions of Sprite objects (#5606) 2023-08-29 13:31:31 +02:00
Florian Rival
ab2b46adad Add a shortcut (Cmd/Ctrl+G) to move selected events in a new group (#5604) 2023-08-28 12:56:33 +02:00
966 changed files with 82634 additions and 47149 deletions

View File

@@ -62,9 +62,10 @@ jobs:
# Build GDevelop IDE (seems like we need to allow Node.js to use more space than usual)
# Note: Code signing is done using CSC_LINK (see https://www.electron.build/code-signing).
# To test signing the code in the CI, add "export CSC_FOR_PULL_REQUEST=true && " before the command.
- run:
name: Build GDevelop IDE
command: export NODE_OPTIONS="--max-old-space-size=7168" && cd newIDE/electron-app && CI=false npm run build -- --mac --publish=never
command: export CSC_FOR_PULL_REQUEST=true && export NODE_OPTIONS="--max-old-space-size=7168" && cd newIDE/electron-app && CI=false npm run build -- --mac --publish=never
- run:
name: Clean dist folder to keep only installers/binaries.

View File

@@ -26,7 +26,7 @@ jobs:
run: sudo apt update && sudo apt install gettext -y
- name: Install newIDE dependencies
run: npm install
run: npm ci
working-directory: newIDE/app
- name: Extract translations

View File

@@ -60,7 +60,7 @@ if("${CMAKE_BUILD_TYPE}" STREQUAL "Release" AND NOT WIN32 AND CMAKE_COMPILER_IS_
endif()
#Activate C++11
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD 11) # Upgrading to C++17 would need to remove usage of bind2nd (should be easy).
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Mark some warnings as errors
@@ -77,7 +77,8 @@ if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
-Wno-unused-private-field
# Make as much warnings considered as errors as possible (only one for now).
-Werror=return-stack-address)
-Werror=return-stack-address
-Werror=return-type)
endif()
# Define common directories:

View File

@@ -45,10 +45,39 @@ ForEachChildVariableEvent::GetAllActionsVectors() const {
return allActions;
}
vector<pair<gd::Expression*, gd::ParameterMetadata> >
ForEachChildVariableEvent::GetAllExpressionsWithMetadata() {
vector<pair<gd::Expression*, gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("scenevar");
allExpressionsWithMetadata.push_back(
std::make_pair(&iterableVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&valueIteratorVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&keyIteratorVariableName, metadata));
return allExpressionsWithMetadata;
}
vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
ForEachChildVariableEvent::GetAllExpressionsWithMetadata() const {
vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("scenevar");
allExpressionsWithMetadata.push_back(
std::make_pair(&iterableVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&valueIteratorVariableName, metadata));
allExpressionsWithMetadata.push_back(
std::make_pair(&keyIteratorVariableName, metadata));
return allExpressionsWithMetadata;
}
void ForEachChildVariableEvent::SerializeTo(SerializerElement& element) const {
element.AddChild("iterableVariableName").SetValue(iterableVariableName);
element.AddChild("valueIteratorVariableName").SetValue(valueIteratorVariableName);
element.AddChild("keyIteratorVariableName").SetValue(keyIteratorVariableName);
element.AddChild("iterableVariableName").SetValue(iterableVariableName.GetPlainString());
element.AddChild("valueIteratorVariableName").SetValue(valueIteratorVariableName.GetPlainString());
element.AddChild("keyIteratorVariableName").SetValue(keyIteratorVariableName.GetPlainString());
gd::EventsListSerialization::SerializeInstructionsTo(
conditions, element.AddChild("conditions"));
gd::EventsListSerialization::SerializeInstructionsTo(

View File

@@ -8,6 +8,7 @@
#define FOREACHCHILDVARIABLEEVENT_H
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Expression.h"
namespace gd {
class Instruction;
class Project;
@@ -44,7 +45,7 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
*
* It is the structure variable that will be iterated on.
*/
const gd::String& GetIterableVariableName() const { return iterableVariableName; };
const gd::String& GetIterableVariableName() const { return iterableVariableName.GetPlainString(); };
/**
* \brief Set the iterable variable name attached to the event.
@@ -56,15 +57,15 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
/**
* \brief Get the value iterator variable attached to the event.
*
* It is the variable that will contain the value of the
* It is the variable that will contain the value of the
* iterable's child being iterated on.
*/
const gd::String& GetValueIteratorVariableName() const { return valueIteratorVariableName; };
const gd::String& GetValueIteratorVariableName() const { return valueIteratorVariableName.GetPlainString(); };
/**
* \brief Set the value iterator variable attached to the event.
*
* It is the variable that will contain the value of the
* It is the variable that will contain the value of the
* iterable's child being iterated on.
*/
void SetValueIteratorVariableName(gd::String newName) { valueIteratorVariableName = newName; };
@@ -72,15 +73,15 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
/**
* \brief Get the key iterator variable attached to the event.
*
* It is the variable that will contain the name of the
* It is the variable that will contain the name of the
* iterable's child being iterated on.
*/
const gd::String& GetKeyIteratorVariableName() const { return keyIteratorVariableName; };
const gd::String& GetKeyIteratorVariableName() const { return keyIteratorVariableName.GetPlainString(); };
/**
* \brief Set the key iterator variable attached to the event.
*
* It is the variable that will contain the name of the
* It is the variable that will contain the name of the
* iterable's child being iterated on.
*/
void SetKeyIteratorVariableName(gd::String newName) { keyIteratorVariableName = newName; };
@@ -92,14 +93,19 @@ class GD_CORE_API ForEachChildVariableEvent : public gd::BaseEvent {
virtual std::vector<gd::InstructionsList*> GetAllConditionsVectors();
virtual std::vector<gd::InstructionsList*> GetAllActionsVectors();
virtual std::vector<std::pair<const gd::Expression*, const gd::ParameterMetadata> >
GetAllExpressionsWithMetadata() const;
virtual std::vector<std::pair<gd::Expression*, gd::ParameterMetadata> >
GetAllExpressionsWithMetadata();
virtual void SerializeTo(SerializerElement& element) const;
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element);
private:
gd::String valueIteratorVariableName;
gd::String keyIteratorVariableName;
gd::String iterableVariableName;
gd::Expression valueIteratorVariableName;
gd::Expression keyIteratorVariableName;
gd::Expression iterableVariableName;
gd::InstructionsList conditions;
gd::InstructionsList actions;
gd::EventsList events;

View File

@@ -64,12 +64,6 @@ class GD_CORE_API ForEachEvent : public gd::BaseEvent {
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element);
std::vector<gd::Expression*> GetAllObjectExpressions() {
std::vector<gd::Expression*> allObjectExpressions;
allObjectExpressions.push_back(&objectsToPick);
return allObjectExpressions;
}
private:
gd::Expression objectsToPick;
gd::InstructionsList conditions;

View File

@@ -131,30 +131,36 @@ void LinkEvent::SerializeTo(SerializerElement& element) const {
void LinkEvent::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
SerializerElement& includeElement = element.GetChild("include", 0, "Limites");
SetTarget(element.GetChild("target", 0, "Scene").GetValue().GetString());
if (includeElement.HasAttribute("includeAll")) {
// Compatibility with GDevelop <= 4.0.92
if (includeElement.GetBoolAttribute("includeAll", true)) {
SetIncludeAllEvents();
// Compatibility with GD <= 5
if (element.HasChild("include", "Limites")) {
SerializerElement& includeElement = element.GetChild("include", 0, "Limites");
if (includeElement.HasAttribute("includeAll")) {
// Compatibility with GDevelop <= 4.0.92
if (includeElement.GetBoolAttribute("includeAll", true)) {
SetIncludeAllEvents();
} else {
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
}
} else {
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
// GDevelop > 4.0.92
IncludeConfig config = static_cast<IncludeConfig>(
includeElement.GetIntAttribute("includeConfig", 0));
if (config == INCLUDE_ALL)
SetIncludeAllEvents();
else if (config == INCLUDE_EVENTS_GROUP)
SetIncludeEventsGroup(includeElement.GetStringAttribute("eventsGroup"));
else if (config == INCLUDE_BY_INDEX)
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
}
} else {
// GDevelop > 4.0.92
IncludeConfig config = static_cast<IncludeConfig>(
includeElement.GetIntAttribute("includeConfig", 0));
if (config == INCLUDE_ALL)
SetIncludeAllEvents();
else if (config == INCLUDE_EVENTS_GROUP)
SetIncludeEventsGroup(includeElement.GetStringAttribute("eventsGroup"));
else if (config == INCLUDE_BY_INDEX)
SetIncludeStartAndEnd(includeElement.GetIntAttribute("start"),
includeElement.GetIntAttribute("end"));
// Since GDevelop 5, links always include all events.
SetIncludeAllEvents();
}
// end of compatibility code
}
bool LinkEvent::AcceptVisitor(gd::EventVisitor &eventVisitor) {

View File

@@ -35,7 +35,7 @@ vector<pair<gd::Expression*, gd::ParameterMetadata> >
RepeatEvent::GetAllExpressionsWithMetadata() {
vector<pair<gd::Expression*, gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("expression");
auto metadata = gd::ParameterMetadata().SetType("number");
allExpressionsWithMetadata.push_back(
std::make_pair(&repeatNumberExpression, metadata));
@@ -61,7 +61,7 @@ vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
RepeatEvent::GetAllExpressionsWithMetadata() const {
vector<pair<const gd::Expression*, const gd::ParameterMetadata> >
allExpressionsWithMetadata;
auto metadata = gd::ParameterMetadata().SetType("expression");
auto metadata = gd::ParameterMetadata().SetType("number");
allExpressionsWithMetadata.push_back(
std::make_pair(&repeatNumberExpression, metadata));

View File

@@ -16,6 +16,7 @@
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
using namespace std;
@@ -68,8 +69,36 @@ gd::String EventsCodeGenerator::GenerateRelationalOperatorCall(
}
}
return callStartString + "(" + argumentsStr + ") " + relationalOperator +
" " + rhs;
auto lhs = callStartString + "(" + argumentsStr + ")";
return GenerateRelationalOperation(relationalOperator, lhs, rhs);
}
/**
* @brief Generate a relational operation
*
* @param relationalOperator the operator
* @param lhs the left hand operand
* @param rhs the right hand operand
* @return gd::String
*/
gd::String EventsCodeGenerator::GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs) {
return lhs + " " + GenerateRelationalOperatorCodes(relationalOperator) + " " + rhs;
}
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" && operatorString != "!=" &&
operatorString != "startsWith" && operatorString != "endsWith" && operatorString != "contains") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
}
/**
@@ -295,16 +324,10 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
gd::String objectInParameter =
condition.GetParameter(pNb).GetPlainString();
if (!GetObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetGlobalObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetObjectsAndGroups().GetObjectGroups().Has(objectInParameter) &&
!GetGlobalObjectsAndGroups().GetObjectGroups().Has(
objectInParameter)) {
if (!GetObjectsContainersList().HasObjectOrGroupNamed(objectInParameter)) {
return "/* Unknown object - skipped. */";
} else if (!instrInfos.parameters[pNb].GetExtraInfo().empty() &&
gd::GetTypeOfObject(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
objectInParameter) !=
GetObjectsContainersList().GetTypeOfObject(objectInParameter) !=
instrInfos.parameters[pNb].GetExtraInfo()) {
return "/* Mismatched object type - skipped. */";
}
@@ -315,11 +338,10 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
gd::String objectName = condition.GetParameter(0).GetPlainString();
if (!objectName.empty() && !instrInfos.parameters.empty()) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Set up the context
gd::String objectType = gd::GetTypeOfObject(
GetGlobalObjectsAndGroups(), GetObjectsAndGroups(), realObjects[i]);
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
@@ -343,13 +365,10 @@ gd::String EventsCodeGenerator::GenerateConditionCode(
}
} else if (instrInfos.IsBehaviorInstruction()) {
gd::String objectName = condition.GetParameter(0).GetPlainString();
gd::String behaviorType =
gd::GetTypeOfBehavior(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
condition.GetParameter(1).GetPlainString());
gd::String behaviorType = GetObjectsContainersList().GetTypeOfBehavior(condition.GetParameter(1).GetPlainString());
if (instrInfos.parameters.size() >= 2) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
const BehaviorMetadata& autoInfo =
@@ -479,16 +498,10 @@ gd::String EventsCodeGenerator::GenerateActionCode(
for (std::size_t pNb = 0; pNb < instrInfos.parameters.size(); ++pNb) {
if (ParameterMetadata::IsObject(instrInfos.parameters[pNb].GetType())) {
gd::String objectInParameter = action.GetParameter(pNb).GetPlainString();
if (!GetObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetGlobalObjectsAndGroups().HasObjectNamed(objectInParameter) &&
!GetObjectsAndGroups().GetObjectGroups().Has(objectInParameter) &&
!GetGlobalObjectsAndGroups().GetObjectGroups().Has(
objectInParameter)) {
if (!GetObjectsContainersList().HasObjectOrGroupNamed(objectInParameter)) {
return "/* Unknown object - skipped. */";
} else if (!instrInfos.parameters[pNb].GetExtraInfo().empty() &&
gd::GetTypeOfObject(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
objectInParameter) !=
GetObjectsContainersList().GetTypeOfObject(objectInParameter) !=
instrInfos.parameters[pNb].GetExtraInfo()) {
return "/* Mismatched object type - skipped. */";
}
@@ -501,11 +514,10 @@ gd::String EventsCodeGenerator::GenerateActionCode(
if (!instrInfos.parameters.empty()) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
gd::String objectType = gd::GetTypeOfObject(
GetGlobalObjectsAndGroups(), GetObjectsAndGroups(), realObjects[i]);
gd::String objectType = GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo =
MetadataProvider::GetObjectMetadata(platform, objectType);
@@ -529,14 +541,11 @@ gd::String EventsCodeGenerator::GenerateActionCode(
}
} else if (instrInfos.IsBehaviorInstruction()) {
gd::String objectName = action.GetParameter(0).GetPlainString();
gd::String behaviorType =
gd::GetTypeOfBehavior(GetGlobalObjectsAndGroups(),
GetObjectsAndGroups(),
action.GetParameter(1).GetPlainString());
gd::String behaviorType = GetObjectsContainersList().GetTypeOfBehavior(action.GetParameter(1).GetPlainString());
if (instrInfos.parameters.size() >= 2) {
std::vector<gd::String> realObjects =
ExpandObjectsName(objectName, context);
GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
// Setup context
const BehaviorMetadata& autoInfo =
@@ -657,18 +666,6 @@ gd::String EventsCodeGenerator::GenerateActionsListCode(
return outputCode;
}
const gd::String EventsCodeGenerator::GenerateRelationalOperatorCodes(const gd::String &operatorString) {
if (operatorString == "=") {
return "==";
}
if (operatorString != "<" && operatorString != ">" &&
operatorString != "<=" && operatorString != ">=" && operatorString != "!=") {
cout << "Warning: Bad relational operator: Set to == by default." << endl;
return "==";
}
return operatorString;
}
gd::String EventsCodeGenerator::GenerateParameterCodes(
const gd::Expression& parameter,
const gd::ParameterMetadata& metadata,
@@ -693,7 +690,7 @@ gd::String EventsCodeGenerator::GenerateParameterCodes(
argOutput =
GenerateObject(parameter.GetPlainString(), metadata.GetType(), context);
} else if (metadata.GetType() == "relationalOperator") {
argOutput += GenerateRelationalOperatorCodes(parameter.GetPlainString());
argOutput += parameter.GetPlainString();
argOutput = "\"" + argOutput + "\"";
} else if (metadata.GetType() == "operator") {
argOutput += parameter.GetPlainString();
@@ -912,41 +909,6 @@ gd::String EventsCodeGenerator::ConvertToStringExplicit(
return "\"" + ConvertToString(plainString) + "\"";
}
std::vector<gd::String> EventsCodeGenerator::ExpandObjectsName(
const gd::String& objectName,
const EventsCodeGenerationContext& context) const {
// Note: this logic is duplicated in EventsContextAnalyzer::ExpandObjectsName
std::vector<gd::String> realObjects;
if (globalObjectsAndGroups.GetObjectGroups().Has(objectName))
realObjects = globalObjectsAndGroups.GetObjectGroups()
.Get(objectName)
.GetAllObjectsNames();
else if (objectsAndGroups.GetObjectGroups().Has(objectName))
realObjects =
objectsAndGroups.GetObjectGroups().Get(objectName).GetAllObjectsNames();
else
realObjects.push_back(objectName);
// If current object is present, use it and only it.
if (find(realObjects.begin(),
realObjects.end(),
context.GetCurrentObject()) != realObjects.end()) {
realObjects.clear();
realObjects.push_back(context.GetCurrentObject());
}
// Ensure that all returned objects actually exists.
for (std::size_t i = 0; i < realObjects.size();) {
if (!objectsAndGroups.HasObjectNamed(realObjects[i]) &&
!globalObjectsAndGroups.HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
else
++i;
}
return realObjects;
}
void EventsCodeGenerator::DeleteUselessEvents(gd::EventsList& events) {
for (std::size_t eId = events.size() - 1; eId < events.size(); --eId) {
if (events[eId].CanHaveSubEvents()) // Process sub events, if any
@@ -963,6 +925,8 @@ void EventsCodeGenerator::DeleteUselessEvents(gd::EventsList& events) {
*/
void EventsCodeGenerator::PreprocessEventList(gd::EventsList& listEvent) {
for (std::size_t i = 0; i < listEvent.GetEventsCount(); ++i) {
if (listEvent[i].IsDisabled()) continue;
listEvent[i].Preprocess(*this, listEvent, i);
if (i <
listEvent.GetEventsCount()) { // Be sure that that there is still an
@@ -1260,12 +1224,24 @@ gd::String EventsCodeGenerator::GenerateArgumentsList(
return argumentsStr;
}
gd::String EventsCodeGenerator::GeneratePropertyGetter(const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getProperty" + property.GetName() + "As" + type + "()";
}
gd::String EventsCodeGenerator::GenerateParameterGetter(const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context) {
return "getParameter" + parameter.GetName() + "As" + type + "()";
}
EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
const gd::Layout& layout,
const gd::Platform& platform_)
: platform(platform_),
globalObjectsAndGroups(project_),
objectsAndGroups(layout),
projectScopedContainers(gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project_, layout)),
hasProjectAndLayout(true),
project(&project_),
scene(&layout),
@@ -1277,11 +1253,9 @@ EventsCodeGenerator::EventsCodeGenerator(const gd::Project& project_,
EventsCodeGenerator::EventsCodeGenerator(
const gd::Platform& platform_,
const gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& objectsAndGroups_)
const gd::ProjectScopedContainers& projectScopedContainers_)
: platform(platform_),
globalObjectsAndGroups(globalObjectsAndGroups_),
objectsAndGroups(objectsAndGroups_),
projectScopedContainers(projectScopedContainers_),
hasProjectAndLayout(false),
project(nullptr),
scene(nullptr),

View File

@@ -12,6 +12,7 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
class EventsList;
@@ -19,6 +20,7 @@ class Expression;
class Project;
class Layout;
class ObjectsContainer;
class ObjectsContainersList;
class ExternalEvents;
class ParameterMetadata;
class ObjectMetadata;
@@ -56,9 +58,9 @@ class GD_CORE_API EventsCodeGenerator {
* \brief Construct a code generator for the specified
* objects/groups and platform
*/
EventsCodeGenerator(const gd::Platform& platform,
const gd::ObjectsContainer& globalObjectsAndGroups_,
const gd::ObjectsContainer& objectsAndGroups_);
EventsCodeGenerator(
const gd::Platform& platform,
const gd::ProjectScopedContainers& projectScopedContainers_);
virtual ~EventsCodeGenerator(){};
/**
@@ -326,18 +328,12 @@ class GD_CORE_API EventsCodeGenerator {
*/
bool ErrorOccurred() const { return errorOccurred; };
/**
* \brief Get the global objects/groups used for code generation.
*/
const gd::ObjectsContainer& GetGlobalObjectsAndGroups() const {
return globalObjectsAndGroups;
}
const gd::ObjectsContainersList& GetObjectsContainersList() const {
return projectScopedContainers.GetObjectsContainersList();
};
/**
* \brief Get the objects/groups used for code generation.
*/
const gd::ObjectsContainer& GetObjectsAndGroups() const {
return objectsAndGroups;
const gd::ProjectScopedContainers& GetProjectScopedContainers() const {
return projectScopedContainers;
}
/**
@@ -363,22 +359,6 @@ class GD_CORE_API EventsCodeGenerator {
*/
const gd::Platform& GetPlatform() const { return platform; }
/**
* \brief Convert a group name to the full list of objects contained in the
* group.
*
* Get a list containing the "real" objects name when the events refers to \a
* objectName :<br> If \a objectName is really an object, the list will only
* contains \a objectName unchanged.<br> If \a objectName is a group, the list
* will contains all the objects of the group.<br> If \a objectName is the
* "current" object in the context ( i.e: The object being used for launching
* an action... ), none of the two rules below apply, and the list will only
* contains the context "current" object name.
*/
std::vector<gd::String> ExpandObjectsName(
const gd::String& objectName,
const EventsCodeGenerationContext& context) const;
/**
* \brief Get the maximum depth of custom conditions reached during code
* generation.
@@ -493,10 +473,15 @@ class GD_CORE_API EventsCodeGenerator {
*/
size_t GenerateSingleUsageUniqueIdForEventsList();
virtual gd::String GenerateRelationalOperation(
const gd::String& relationalOperator,
const gd::String& lhs,
const gd::String& rhs);
protected:
virtual const gd::String GenerateRelationalOperatorCodes(
const gd::String& operatorString);
protected:
/**
* \brief Generate the code for a single parameter.
*
@@ -566,6 +551,12 @@ class GD_CORE_API EventsCodeGenerator {
return ".getChild(" + ConvertToStringExplicit(childName) + ")";
};
virtual gd::String GenerateVariableValueAs(const gd::String& type) {
return type == "number|string" ? ".getAsNumberOrString()"
: type == "string" ? ".getAsString()"
: ".getAsNumber()";
}
/**
* \brief Generate the code to get the child of a variable,
* using generated the expression.
@@ -594,6 +585,17 @@ class GD_CORE_API EventsCodeGenerator {
return "fakeObjectListOf_" + objectName;
}
virtual gd::String GeneratePropertyGetter(
const gd::PropertiesContainer& propertiesContainer,
const gd::NamedPropertyDescriptor& property,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
virtual gd::String GenerateParameterGetter(
const gd::ParameterMetadata& parameter,
const gd::String& type,
gd::EventsCodeGenerationContext& context);
/**
* \brief Generate the code to reference an object which is
* in an empty/null state.
@@ -673,7 +675,8 @@ class GD_CORE_API EventsCodeGenerator {
* The default implementation generates C-style code : It wraps the predicate
* inside parenthesis and add a !.
*/
virtual gd::String GenerateNegatedPredicate(const gd::String& predicate) const {
virtual gd::String GenerateNegatedPredicate(
const gd::String& predicate) const {
return "!(" + predicate + ")";
};
@@ -734,6 +737,7 @@ class GD_CORE_API EventsCodeGenerator {
const std::vector<gd::String>& arguments,
const gd::String& callStartString,
std::size_t startFromArgument = 0);
gd::String GenerateOperatorCall(const gd::InstructionMetadata& instrInfos,
const std::vector<gd::String>& arguments,
const gd::String& callStartString,
@@ -777,8 +781,7 @@ class GD_CORE_API EventsCodeGenerator {
const gd::Platform& platform; ///< The platform being used.
const gd::ObjectsContainer& globalObjectsAndGroups;
const gd::ObjectsContainer& objectsAndGroups;
gd::ProjectScopedContainers projectScopedContainers;
bool hasProjectAndLayout; ///< true only if project and layout are valid
///< references. If false, they should not be used.

View File

@@ -7,6 +7,7 @@
#include <memory>
#include <vector>
#include <vector>
#include "GDCore/CommonTools.h"
#include "GDCore/Events/CodeGeneration/EventsCodeGenerationContext.h"
@@ -25,6 +26,9 @@
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/VariablesContainersList.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/IDE/Events/ExpressionVariableOwnerFinder.h"
@@ -47,8 +51,7 @@ gd::String ExpressionCodeGenerator::GenerateExpressionCode(
}
gd::ExpressionValidator validator(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetProjectScopedContainers(),
rootType);
node->Visit(validator);
if (!validator.GetFatalErrors().empty()) {
@@ -100,35 +103,95 @@ void ExpressionCodeGenerator::OnVisitVariableNode(VariableNode& node) {
// This "translation" from the type to an enum could be avoided
// if all types were moved to an enum.
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
EventsCodeGenerator::VariableScope scope =
type == "globalvar"
? gd::EventsCodeGenerator::PROJECT_VARIABLE
: ((type == "scenevar")
? gd::EventsCodeGenerator::LAYOUT_VARIABLE
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
node.name, scope, context, objectName);
if (node.child) node.child->Visit(*this);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// The node is a variable inside an expression waiting for a *variable* to be returned, not its value.
EventsCodeGenerator::VariableScope scope =
type == "globalvar"
? gd::EventsCodeGenerator::PROJECT_VARIABLE
: ((type == "scenevar")
? gd::EventsCodeGenerator::LAYOUT_VARIABLE
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetObjectsContainersList(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
node.name, scope, context, objectName);
if (node.child) node.child->Visit(*this);
} else {
// The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
codeGenerator.GetProjectScopedContainers().MatchIdentifierWithName<void>(node.name, [&](){
// Generate the code to access the object variables.
// Defer generation of the access to the object and variable to the child,
// once we know the name of the variable.
objectNameToUseForVariableAccessor = node.name;
if (node.child) node.child->Visit(*this);
objectNameToUseForVariableAccessor = "";
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
if (!codeGenerator.HasProjectAndLayout()) {
gd::LogWarning("Tried to generate access to a variable without a project/scene - the code generator only works for global and scene variables for now.");
output += GenerateDefaultValue(type);
return;
}
// This could be adapted in the future if more scopes are supported.
EventsCodeGenerator::VariableScope scope = gd::EventsCodeGenerator::PROJECT_VARIABLE;
if (codeGenerator.GetProjectScopedContainers().GetVariablesContainersList().GetBottomMostVariablesContainer()->Has(node.name)) {
scope = gd::EventsCodeGenerator::LAYOUT_VARIABLE;
}
output += codeGenerator.GenerateGetVariable(node.name, scope, context, "");
if (node.child) node.child->Visit(*this);
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
// Properties are not supported.
output += GenerateDefaultValue(type);
}, [&]() {
// Parameters are not supported.
output += GenerateDefaultValue(type);
}, [&]() {
// The identifier does not represents a variable (or a child variable), or not at least an existing
// one, nor an object variable. It's invalid.
output += GenerateDefaultValue(type);
});
}
}
void ExpressionCodeGenerator::OnVisitVariableAccessorNode(
VariableAccessorNode& node) {
output += codeGenerator.GenerateVariableAccessor(node.name);
if (!objectNameToUseForVariableAccessor.empty()) {
// Use the name of the object passed by the parent, as we need both to access an object variable.
output += codeGenerator.GenerateGetVariable(node.name,
gd::EventsCodeGenerator::OBJECT_VARIABLE, context, objectNameToUseForVariableAccessor);
// We have accessed an object variable, from now we can continue accessing the child variables
// (including using the bracket notation).
objectNameToUseForVariableAccessor = "";
} else {
output += codeGenerator.GenerateVariableAccessor(node.name);
}
if (node.child) node.child->Visit(*this);
}
void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) {
ExpressionCodeGenerator generator("string", "", codeGenerator, context);
if (!objectNameToUseForVariableAccessor.empty()) {
// Bracket notation can't be used to directly access a variable of an object (`MyObject["MyVariable"]`).
// This would be rejected by the ExpressionValidator.
output += codeGenerator.GenerateBadVariable();
return;
}
ExpressionCodeGenerator generator("number|string", "", codeGenerator, context);
node.expression->Visit(generator);
output +=
codeGenerator.GenerateVariableBracketAccessor(generator.GetOutput());
@@ -137,10 +200,10 @@ void ExpressionCodeGenerator::OnVisitVariableBracketAccessorNode(
void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
if (gd::ParameterMetadata::IsObject(type)) {
output +=
codeGenerator.GenerateObject(node.identifierName, type, context);
@@ -153,8 +216,7 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
: gd::EventsCodeGenerator::OBJECT_VARIABLE);
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootObjectName,
node);
output += codeGenerator.GenerateGetVariable(
@@ -162,30 +224,60 @@ void ExpressionCodeGenerator::OnVisitIdentifierNode(IdentifierNode& node) {
if (!node.childIdentifierName.empty()) {
output += codeGenerator.GenerateVariableAccessor(node.childIdentifierName);
}
} else if (node.childIdentifierName.empty()) {
output += "/* Error during generation, unrecognized identifier type: " +
codeGenerator.ConvertToString(type) + " with value " +
codeGenerator.ConvertToString(node.identifierName) + " */ " +
codeGenerator.ConvertToStringExplicit(node.identifierName);
}
else {
// This is for function names that are put in IdentifierNode
// because the type is needed to tell them apart from variables.
output += GenerateDefaultValue(type);
} else {
const auto& variablesContainersList = codeGenerator.GetProjectScopedContainers().GetVariablesContainersList();
const auto& propertiesContainersList = codeGenerator.GetProjectScopedContainers().GetPropertiesContainersList();
const auto& parametersVectorsList = codeGenerator.GetProjectScopedContainers().GetParametersVectorsList();
// The node represents a variable, property, parameter or an object.
codeGenerator.GetProjectScopedContainers().MatchIdentifierWithName<void>(node.identifierName, [&]() {
// Generate the code to access the object variable.
output += codeGenerator.GenerateGetVariable(
node.childIdentifierName, gd::EventsCodeGenerator::OBJECT_VARIABLE, context, node.identifierName);
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
if (!codeGenerator.HasProjectAndLayout()) {
gd::LogWarning("Tried to generate access to a variable without a project/scene - the code generator only works for global and scene variables for now.");
output += GenerateDefaultValue(type);
return;
}
// This could be adapted in the future if more scopes are supported at runtime.
EventsCodeGenerator::VariableScope scope = gd::EventsCodeGenerator::PROJECT_VARIABLE;
if (variablesContainersList.GetBottomMostVariablesContainer()->Has(node.identifierName)) {
scope = gd::EventsCodeGenerator::LAYOUT_VARIABLE;
}
output += codeGenerator.GenerateGetVariable(node.identifierName, scope, context, "");
if (!node.childIdentifierName.empty()) {
output += codeGenerator.GenerateVariableAccessor(node.childIdentifierName);
}
output += codeGenerator.GenerateVariableValueAs(type);
}, [&]() {
const auto& propertiesContainerAndProperty = propertiesContainersList.Get(node.identifierName);
output += codeGenerator.GeneratePropertyGetter(
propertiesContainerAndProperty.first, propertiesContainerAndProperty.second, type, context);
}, [&]() {
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, node.identifierName);
output += codeGenerator.GenerateParameterGetter(parameter, type, context);
}, [&]() {
// The identifier does not represents a variable (or a child variable), or not at least an existing
// one, nor an object variable. It's invalid.
output += GenerateDefaultValue(type);
});
}
}
void ExpressionCodeGenerator::OnVisitFunctionCallNode(FunctionCallNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
@@ -236,11 +328,6 @@ gd::String ExpressionCodeGenerator::GenerateObjectFunctionCode(
const gd::String& objectName,
const std::vector<std::unique_ptr<ExpressionNode>>& parameters,
const ExpressionMetadata& expressionMetadata) {
const gd::ObjectsContainer& globalObjectsAndGroups =
codeGenerator.GetGlobalObjectsAndGroups();
const gd::ObjectsContainer& objectsAndGroups =
codeGenerator.GetObjectsAndGroups();
codeGenerator.AddIncludeFiles(
expressionMetadata.GetIncludeFiles());
@@ -261,12 +348,11 @@ gd::String ExpressionCodeGenerator::GenerateObjectFunctionCode(
// Get object(s) concerned by function call
std::vector<gd::String> realObjects =
codeGenerator.ExpandObjectsName(objectName, context);
codeGenerator.GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
for (std::size_t i = 0; i < realObjects.size(); ++i) {
context.ObjectsListNeeded(realObjects[i]);
gd::String objectType = gd::GetTypeOfObject(
globalObjectsAndGroups, objectsAndGroups, realObjects[i]);
gd::String objectType = codeGenerator.GetObjectsContainersList().GetTypeOfObject(realObjects[i]);
const ObjectMetadata& objInfo = MetadataProvider::GetObjectMetadata(
codeGenerator.GetPlatform(), objectType);
@@ -288,11 +374,6 @@ gd::String ExpressionCodeGenerator::GenerateBehaviorFunctionCode(
const gd::String& behaviorName,
const std::vector<std::unique_ptr<ExpressionNode>>& parameters,
const ExpressionMetadata& expressionMetadata) {
const gd::ObjectsContainer& globalObjectsAndGroups =
codeGenerator.GetGlobalObjectsAndGroups();
const gd::ObjectsContainer& objectsAndGroups =
codeGenerator.GetObjectsAndGroups();
codeGenerator.AddIncludeFiles(
expressionMetadata.GetIncludeFiles());
@@ -311,12 +392,11 @@ gd::String ExpressionCodeGenerator::GenerateBehaviorFunctionCode(
// Get object(s) concerned by function call
std::vector<gd::String> realObjects =
codeGenerator.ExpandObjectsName(objectName, context);
codeGenerator.GetObjectsContainersList().ExpandObjectName(objectName, context.GetCurrentObject());
gd::String functionOutput = GenerateDefaultValue(type);
gd::String behaviorType = gd::GetTypeOfBehavior(
globalObjectsAndGroups, objectsAndGroups, behaviorName);
gd::String behaviorType = codeGenerator.GetObjectsContainersList().GetTypeOfBehavior(behaviorName);
const BehaviorMetadata& autoInfo = MetadataProvider::GetBehaviorMetadata(
codeGenerator.GetPlatform(), behaviorType);
@@ -352,8 +432,7 @@ gd::String ExpressionCodeGenerator::GenerateParametersCodes(
if (!parameterMetadata.IsCodeOnly()) {
if (nonCodeOnlyParameterIndex < parameters.size()) {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetObjectsContainersList(),
rootObjectName,
*parameters[nonCodeOnlyParameterIndex].get());
ExpressionCodeGenerator generator(parameterMetadata.GetType(), objectName, codeGenerator, context);
@@ -423,8 +502,7 @@ gd::String ExpressionCodeGenerator::GenerateDefaultValue(
void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
output += GenerateDefaultValue(type);
@@ -433,8 +511,7 @@ void ExpressionCodeGenerator::OnVisitEmptyNode(EmptyNode& node) {
void ExpressionCodeGenerator::OnVisitObjectFunctionNameNode(
ObjectFunctionNameNode& node) {
auto type = gd::ExpressionTypeFinder::GetType(codeGenerator.GetPlatform(),
codeGenerator.GetGlobalObjectsAndGroups(),
codeGenerator.GetObjectsAndGroups(),
codeGenerator.GetProjectScopedContainers(),
rootType,
node);
output += GenerateDefaultValue(type);

View File

@@ -102,6 +102,7 @@ class GD_CORE_API ExpressionCodeGenerator : public ExpressionParser2NodeWorker {
const std::vector<std::unique_ptr<ExpressionNode>>& parameters);
gd::String output;
gd::String objectNameToUseForVariableAccessor;
EventsCodeGenerator& codeGenerator;
EventsCodeGenerationContext& context;
const gd::String rootType;

View File

@@ -240,7 +240,7 @@ class GD_CORE_API BaseEvent {
*/
virtual void UnserializeFrom(gd::Project& project,
const SerializerElement& element){};
virtual bool AcceptVisitor(gd::EventVisitor& eventVisitor);
virtual void AcceptVisitor(gd::ReadOnlyEventVisitor& eventVisitor) const;
///@}
@@ -281,15 +281,6 @@ class GD_CORE_API BaseEvent {
* \brief True if the event should be folded in the events editor.
*/
bool IsFolded() const { return folded; }
/**
* \brief Return a list of all objects linked to the event.
*/
virtual std::vector<gd::Expression*> GetAllObjectExpressions() {
std::vector<gd::Expression*> allObjectExpressions;
return allObjectExpressions;
}
///@}
std::weak_ptr<gd::BaseEvent>

View File

@@ -47,14 +47,9 @@ class GD_CORE_API ExpressionParser2 {
virtual ~ExpressionParser2(){};
/**
* Parse the given expression with the specified type.
* Parse the given expression into a tree of nodes.
*
* \param type Type of the expression: "string", "number",
* type supported by gd::ParameterMetadata::IsObject, types supported by
* gd::ParameterMetadata::IsExpression or "unknown".
* \param expression The expression to parse
* \param objectName Specify the object name, only for the
* case of "objectvar" type.
* \param expression The expression to parse.
*
* \return The node representing the expression as a parsed tree.
*/
@@ -267,12 +262,11 @@ class GD_CORE_API ExpressionParser2 {
} else if (CheckIfChar(IsDot)) {
ExpressionParserLocation dotLocation = SkipChar();
SkipAllWhitespaces();
return ObjectFunctionOrBehaviorFunction(
return ObjectFunctionOrBehaviorFunctionOrVariable(
name, nameLocation, dotLocation);
} else if (CheckIfChar(IsOpeningSquareBracket)) {
return Variable(name, nameLocation);
}
else {
} else {
auto identifier = gd::make_unique<IdentifierNode>(name);
identifier->location = ExpressionParserLocation(
nameLocation.GetStartPosition(), GetCurrentPosition());
@@ -318,7 +312,7 @@ class GD_CORE_API ExpressionParser2 {
auto dotLocation = SkipChar();
SkipAllWhitespaces();
auto identifierAndLocation = ReadIdentifierName();
auto identifierAndLocation = ReadIdentifierName(/*allowDeprecatedSpacesInName=*/ false);
auto child =
gd::make_unique<VariableAccessorNode>(identifierAndLocation.name);
child->child = VariableAccessorOrVariableBracketAccessor();
@@ -358,11 +352,11 @@ class GD_CORE_API ExpressionParser2 {
}
std::unique_ptr<IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode>
ObjectFunctionOrBehaviorFunction(
ObjectFunctionOrBehaviorFunctionOrVariable(
const gd::String &parentIdentifier,
const ExpressionParserLocation &parentIdentifierLocation,
const ExpressionParserLocation &parentIdentifierDotLocation) {
auto childIdentifierAndLocation = ReadIdentifierName();
auto childIdentifierAndLocation = ReadIdentifierName(/*allowDeprecatedSpacesInName=*/ false);
const gd::String &childIdentifierName = childIdentifierAndLocation.name;
const auto &childIdentifierNameLocation =
childIdentifierAndLocation.location;
@@ -420,12 +414,6 @@ class GD_CORE_API ExpressionParser2 {
auto node = gd::make_unique<IdentifierNode>(
parentIdentifier, childIdentifierName);
if (!CheckIfChar(IsParameterSeparator) && !CheckIfChar(IsClosingParenthesis) && !IsEndReached()) {
node->diagnostic = RaiseSyntaxError(
_("An opening parenthesis (for an object expression), a double colon "
"(:: for a behavior expression), a dot or an opening bracket (for "
"a child variable) where expected."));
}
node->location = ExpressionParserLocation(
parentIdentifierLocation.GetStartPosition(), GetCurrentPosition());
node->identifierNameLocation = parentIdentifierLocation;
@@ -625,13 +613,13 @@ class GD_CORE_API ExpressionParser2 {
ExpressionParserLocation location;
};
IdentifierAndLocation ReadIdentifierName() {
IdentifierAndLocation ReadIdentifierName(bool allowDeprecatedSpacesInName = true) {
gd::String name;
size_t startPosition = currentPosition;
while (currentPosition < expression.size() &&
(CheckIfChar(IsAllowedInIdentifier)
// Allow whitespace in identifier name for compatibility
|| expression[currentPosition] == ' ')) {
|| (allowDeprecatedSpacesInName && expression[currentPosition] == ' '))) {
name += expression[currentPosition];
currentPosition++;
}

View File

@@ -189,7 +189,7 @@ struct GD_CORE_API IdentifierOrFunctionCallOrObjectFunctionNameOrEmptyNode
*
* The name of a function to call on an object or the behavior,
* for example: "MyObject.Function" or "MyObject.Physics".
*
*
* A variable, potentially with accessor to its child,
* for example: MyVariable or MyVariable.MyChild
*/
@@ -239,13 +239,14 @@ struct GD_CORE_API VariableAccessorOrVariableBracketAccessorNode : public Expres
};
/**
* \brief A variable with bracket accessor or at least 2 "dot" accessors.
* \brief A variable, or object variable, with bracket accessor or at least 2 "dot" accessors.
*
* Example: `MyVariable["MyChildren"]` or `MyVariable.MyChildren.MyGrandChildren`.
* Example: `MyObject["MyVariable"]` or `MyObject.MyVariable.MyChildren`.
*
* Example: MyVariable[MyChildren] or MyVariable.MyChildren.MyGranChildren.
*
* Other cases like "MyVariable" or "MyVariable.MyChildren" are IdentifierNode
* to allow handling ambiguities.
*
*
* \see gd::IdentifierNode
* \see gd::VariableAccessorNode
* \see gd::VariableBracketAccessorNode
@@ -267,7 +268,7 @@ struct GD_CORE_API VariableNode : public FunctionCallOrObjectFunctionNameOrEmpty
};
/**
* \brief A bracket accessor of a variable. Example: MyChild
* \brief A direct accessor to a child variable. Example: MyChild
* in MyVariable.MyChild
*/
struct GD_CORE_API VariableAccessorNode
@@ -285,7 +286,7 @@ struct GD_CORE_API VariableAccessorNode
};
/**
* \brief A bracket accessor of a variable. Example: ["MyChild"]
* \brief A bracket accessor to a child variable. Example: ["MyChild"]
* (in MyVariable["MyChild"]).
*/
struct GD_CORE_API VariableBracketAccessorNode
@@ -303,10 +304,10 @@ struct GD_CORE_API VariableBracketAccessorNode
/**
* \brief The name of a function to call on an object or the behavior
* For example: "MyObject.Physics::LinearVelocity".
*
*
* Other cases like "MyObject.Function" or "MyObject.Physics" are IdentifierNode
* to allow handling ambiguities.
*
*
* \see gd::IdentifierNode
*/
struct GD_CORE_API ObjectFunctionNameNode

View File

@@ -75,10 +75,21 @@ inline bool IsZeroDigit(gd::String::value_type character) {
return character == '0';
}
inline bool IsAdditionalReservedCharacter(gd::String::value_type character) {
// These characters are not part of the grammar - but are often used in programming language
// and could become operators or part of the grammar one day.
return character == '~' || character == '\'' || character == '%' ||
character == '#' || character == '@' || character == '|' ||
character == '&' || character == '`' || character == '$' ||
character == ';';
}
/**
* Check if the given character can be used in an identifier. This is
* any unicode character, except for:
* `, . " () [] {} + - < > ? ^ = \ : ! / *` and whitespaces (space, line break, carriage return).
* `, . " () [] {} + - < > ? ^ = \ : ! / * ~ ' % # @ | & $ ;`
* and backtick and whitespaces (space, line break, carriage return).
*
* This is loosely based on what is allowed in languages like JavaScript
* (see https://mathiasbynens.be/notes/javascript-properties), without support
@@ -96,7 +107,7 @@ inline bool IsAllowedInIdentifier(gd::String::value_type character) {
if (!IsParameterSeparator(character) && !IsDot(character) &&
!IsQuote(character) && !IsBracket(character) &&
!IsExpressionOperator(character) && !IsTermOperator(character) &&
!IsWhitespace(character)) {
!IsWhitespace(character) && !IsAdditionalReservedCharacter(character)) {
return true;
}

View File

@@ -115,21 +115,23 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAdvancedExtension(
.AddExpression(
"GetArgumentAsNumber",
_("Get function parameter value"),
_("Get function parameter (also called \"argument\") value."),
_("Get function parameter (also called \"argument\") value. You don't need this most of the time as you can simply write the parameter name in an expression."),
"",
"res/function16.png")
.AddParameter("functionParameterName", _("Parameter name"), "number,string,boolean")
.SetRelevantForFunctionEventsOnly();
.SetRelevantForFunctionEventsOnly()
.SetHidden();
extension
.AddStrExpression(
"GetArgumentAsString",
_("Get function parameter text"),
_("Get function parameter (also called \"argument\") text."),
_("Get function parameter (also called \"argument\") text. You don't need this most of the time as you can simply write the parameter name in an expression."),
"",
"res/function16.png")
.AddParameter("functionParameterName", _("Parameter name"), "number,string,boolean")
.SetRelevantForFunctionEventsOnly();
.SetRelevantForFunctionEventsOnly()
.SetHidden();
extension
.AddCondition(

View File

@@ -49,6 +49,7 @@ class GD_CORE_API BuiltinExtensionsImplementer {
static void ImplementsAnimatableExtension(gd::PlatformExtension& extension);
static void ImplementsEffectExtension(gd::PlatformExtension& extension);
static void ImplementsOpacityExtension(gd::PlatformExtension& extension);
static void ImplementsTextContainerExtension(gd::PlatformExtension& extension);
};
} // namespace gd

View File

@@ -617,6 +617,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"number", ParameterOptions::MakeNewOptions())
.MarkAsAdvanced();
// Deprecated
obj.AddCondition("AngleOfDisplacement",
_("Angle of movement (using forces)"),
_("Compare the angle of movement of an object according to "
@@ -626,7 +627,20 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
_("Movement using forces"),
"res/conditions/vitesse24.png",
"res/conditions/vitesse.png")
.SetHidden()
.AddParameter("object", _("Object"))
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Tolerance, in degrees"))
.MarkAsAdvanced();
obj.AddCondition("IsTotalForceAngleAround",
_("Angle of movement (using forces)"),
_("Compare the angle of movement of an object according to "
"the forces applied on it."),
_("Angle of movement of _PARAM0_ is _PARAM1_ ± _PARAM2_°"),
_("Movement using forces"),
"res/conditions/vitesse24.png",
"res/conditions/vitesse.png")
.AddParameter("object", _("Object"))
.AddParameter("expression", _("Angle, in degrees"))
.AddParameter("expression", _("Tolerance, in degrees"))
@@ -1277,9 +1291,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
// Deprecated
obj.AddAction("SetEffectDoubleParameter",
_("Effect parameter (number)"),
_("Change the value of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (number)"),
_("Change the value of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
_("Effects"),
@@ -1287,17 +1301,17 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/effect.png")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("expression", _("New value"))
.MarkAsSimple()
.SetHidden();
// Deprecated
obj.AddAction("SetEffectStringParameter",
_("Effect parameter (string)"),
_("Change the value (string) of a parameter of an effect.") +
_("Effect property (string)"),
_("Change the value (string) of a property of an effect.") +
"\n" +
_("You can find the parameter names (and change the effect "
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM2_ to _PARAM3_ for effect _PARAM1_ of _PARAM0_"),
_("Effects"),
@@ -1305,16 +1319,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/effect.png")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("string", _("New value"))
.MarkAsSimple()
.SetHidden();
// Deprecated
obj.AddAction("SetEffectBooleanParameter",
_("Effect parameter (enable or disable)"),
_("Enable or disable a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (enable or disable)"),
_("Enable or disable a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Enable _PARAM2_ for effect _PARAM1_ of _PARAM0_: _PARAM3_"),
_("Effects"),
@@ -1322,8 +1336,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsBaseObjectExtension(
"res/actions/effect.png")
.AddParameter("object", _("Object"))
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("yesorno", _("Enable?"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("yesorno", _("Enable this property"))
.MarkAsSimple()
.SetHidden();

View File

@@ -444,9 +444,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
extension
.AddAction(
"SetLayerEffectParameter",
_("Effect parameter (number)"),
_("Change the value of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (number)"),
_("Change the value of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
_("Effects"),
@@ -456,16 +456,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Parameter name"))
.AddParameter("layerEffectParameterName", _("Property name"))
.AddParameter("expression", _("New value"))
.MarkAsAdvanced();
extension
.AddAction(
"SetLayerEffectStringParameter",
_("Effect parameter (string)"),
_("Change the value (string) of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (string)"),
_("Change the value (string) of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of layer _PARAM1_"),
_("Effects"),
@@ -475,16 +475,16 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Parameter name"))
.AddParameter("layerEffectParameterName", _("Property name"))
.AddParameter("string", _("New value"))
.MarkAsAdvanced();
extension
.AddAction(
"SetLayerEffectBooleanParameter",
_("Effect parameter (enable or disable)"),
_("Enable or disable a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (enable or disable)"),
_("Enable or disable a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Enable _PARAM3_ for effect _PARAM2_ of layer _PARAM1_: _PARAM4_"),
_("Effects"),
@@ -494,8 +494,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsCameraExtension(
.AddParameter("layer", _("Layer"), "", true)
.SetDefaultValue("\"\"")
.AddParameter("layerEffectName", _("Effect name"))
.AddParameter("layerEffectParameterName", _("Parameter name"))
.AddParameter("yesorno", _("Enable this parameter"))
.AddParameter("layerEffectParameterName", _("Property name"))
.AddParameter("yesorno", _("Enable this property"))
.MarkAsAdvanced();
extension

View File

@@ -23,6 +23,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Animatable capability"))
.SetIcon("res/actions/animation24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Animations and images"))
.SetIcon("res/actions/animation24.png");
@@ -53,13 +55,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Animation index")))
.MarkAsSimple();
aut.GetAllExpressions()["Index"].SetGroup("");
aut.AddExpressionAndConditionAndAction(
"string",
"Name",
_("Animation (by name)"),
_("the animation played by the object using the name of the "
"animation."),
"animation"),
_("the animation"),
_("Animations and images"),
"res/actions/animation24.png")
@@ -69,10 +72,11 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"objectAnimationName", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Animation name")))
.MarkAsSimple();
aut.GetAllStrExpressions()["Name"].SetGroup("");
aut.AddScopedAction("PauseAnimation",
_("Pause the animation"),
_("Pause the animation of the object"),
_("Pause the animation of the object."),
_("Pause the animation of _PARAM0_"),
_("Animations and images"),
"res/actions/animation24.png",
@@ -83,7 +87,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
aut.AddScopedAction("PlayAnimation",
_("Resume the animation"),
_("Resume the animation of the object"),
_("Resume the animation of the object."),
_("Resume the animation of _PARAM0_"),
_("Animations and images"),
"res/actions/animation24.png",
@@ -107,6 +111,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Speed scale")))
.MarkAsSimple();
aut.GetAllExpressions()["SpeedScale"].SetGroup("");
aut.AddScopedCondition("IsAnimationPaused",
_("Animation paused"),
@@ -130,6 +135,31 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsAnimatableExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "AnimatableBehavior")
.MarkAsSimple();
aut.AddExpressionAndConditionAndAction(
"number",
"ElapsedTime",
_("Animation elapsed time"),
_("the elapsed time from the beginning of the animation (in seconds)"),
_("the animation elapsed time"),
_("Animations and images"),
"res/actions/animation24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "AnimatableBehavior")
.UseStandardParameters(
"number", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Elapsed time (in seconds)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["ElapsedTime"].SetGroup("");
aut.AddExpression(
"Duration",
_("Animation duration"),
_("Return the current animation duration (in seconds)."),
_("Animations and images"),
"res/actions/animation24.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "AnimatableBehavior");
}
} // namespace gd

View File

@@ -52,9 +52,9 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.MarkAsSimple();
aut.AddScopedAction("SetEffectDoubleParameter",
_("Effect parameter (number)"),
_("Change the value of a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (number)"),
_("Change the value of a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
_("Effects"),
@@ -63,15 +63,15 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("expression", _("New value"))
.MarkAsSimple();
aut.AddScopedAction("SetEffectStringParameter",
_("Effect parameter (string)"),
_("Change the value (string) of a parameter of an effect.") +
_("Effect property (string)"),
_("Change the value (string) of a property of an effect.") +
"\n" +
_("You can find the parameter names (and change the effect "
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Set _PARAM3_ to _PARAM4_ for effect _PARAM2_ of _PARAM0_"),
_("Effects"),
@@ -80,14 +80,14 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("string", _("New value"))
.MarkAsSimple();
aut.AddScopedAction("SetEffectBooleanParameter",
_("Effect parameter (enable or disable)"),
_("Enable or disable a parameter of an effect.") + "\n" +
_("You can find the parameter names (and change the effect "
_("Effect property (enable or disable)"),
_("Enable or disable a property of an effect.") + "\n" +
_("You can find the property names (and change the effect "
"names) in the effects window."),
_("Enable _PARAM3_ for effect _PARAM2_ of _PARAM0_: _PARAM4_"),
_("Effects"),
@@ -96,8 +96,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsEffectExtension(
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "EffectBehavior")
.AddParameter("objectEffectName", _("Effect name"))
.AddParameter("objectEffectParameterName", _("Parameter name"))
.AddParameter("yesorno", _("Enable?"))
.AddParameter("objectEffectParameterName", _("Property name"))
.AddParameter("yesorno", _("Enable this property"))
.MarkAsSimple();
aut.AddScopedCondition("IsEffectEnabled",

View File

@@ -23,6 +23,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Opacity capability"))
.SetIcon("res/actions/opacity24.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Visibility"))
.SetIcon("res/actions/opacity24.png");
@@ -54,6 +56,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsOpacityExtension(
_("Opacity (0-255)")))
.SetFunctionName("setOpacity")
.SetGetter("getOpacity");
aut.GetAllExpressions()["Value"].SetGroup("");
}
} // namespace gd

View File

@@ -14,7 +14,7 @@ using namespace std;
namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
gd::PlatformExtension& extension) {
gd::PlatformExtension &extension) {
extension
.SetExtensionInformation("ResizableCapability",
_("Resizable capability"),
@@ -22,84 +22,86 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsResizableExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size")).SetIcon(
"res/actions/scale24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"ResizableBehavior",
_("Resizable capability"),
"Resizable",
_("Change the object dimensions."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
gd::BehaviorMetadata &aut =
extension
.AddBehavior("ResizableBehavior",
_("Resizable capability"),
"Resizable",
_("Change the object dimensions."),
"",
"res/actions/scale24_black.png",
"ResizableBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddScopedAction("SetWidth",
_("Width"),
_("Change the width of the object."),
_("the width"),
_("Size"),
"res/actions/scaleWidth24_black.png",
"res/actions/scaleWidth_black.png")
_("Width"),
_("Change the width of the object."),
_("the width"),
_("Size"),
"res/actions/scaleWidth24_black.png",
"res/actions/scaleWidth_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardOperatorParameters("number",
ParameterOptions::MakeNewOptions().SetDescription(
_("Width")))
.UseStandardOperatorParameters(
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Width")))
.MarkAsAdvanced();
aut.AddScopedCondition("Width",
_("Width"),
_("Compare the width of the object."),
_("the width"),
_("Size"),
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
_("Width"),
_("Compare the width of the object."),
_("the width"),
_("Size"),
"res/conditions/scaleWidth24_black.png",
"res/conditions/scaleWidth_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardRelationalOperatorParameters(
"number", ParameterOptions::MakeNewOptions().SetDescription(
_("Width")))
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Width")))
.MarkAsAdvanced();
aut.AddScopedAction("SetHeight",
_("Height"),
_("Change the height of the object."),
_("the height"),
_("Size"),
"res/actions/scaleHeight24_black.png",
"res/actions/scaleHeight_black.png")
_("Height"),
_("Change the height of the object."),
_("the height"),
_("Size"),
"res/actions/scaleHeight24_black.png",
"res/actions/scaleHeight_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardOperatorParameters("number",
ParameterOptions::MakeNewOptions().SetDescription(
_("Height")))
.UseStandardOperatorParameters(
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Height")))
.MarkAsAdvanced();
aut.AddScopedCondition("Height",
_("Height"),
_("Compare the height of the object."),
_("the height"),
_("Size"),
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
_("Height"),
_("Compare the height of the object."),
_("the height"),
_("Size"),
"res/conditions/scaleHeight24_black.png",
"res/conditions/scaleHeight_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.UseStandardRelationalOperatorParameters(
"number", ParameterOptions::MakeNewOptions().SetDescription(
_("Height")))
"number",
ParameterOptions::MakeNewOptions().SetDescription(_("Height")))
.MarkAsAdvanced();
aut.AddScopedAction("SetSize",
_("Size"),
_("Change the size of an object."),
_("Change the size of _PARAM0_: set to _PARAM1_ x _PARAM2_"),
_("Size"),
"res/actions/scale24_black.png",
"res/actions/scale_black.png")
aut.AddScopedAction(
"SetSize",
_("Size"),
_("Change the size of an object."),
_("Change the size of _PARAM0_: set to _PARAM2_ x _PARAM3_"),
_("Size"),
"res/actions/scale24_black.png",
"res/actions/scale_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ResizableBehavior")
.AddParameter("expression", _("Width"))

View File

@@ -23,6 +23,8 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Scalable capability"))
.SetIcon("res/actions/scale24_black.png");
extension.AddInstructionOrExpressionGroupMetadata(_("Size"))
.SetIcon("res/actions/scale24_black.png");
@@ -53,6 +55,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["Value"].SetGroup("");
aut.AddExpressionAndConditionAndAction(
"number",
@@ -61,7 +64,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("the scale on X axis of the object (default scale is 1)"),
_("the scale on X axis"),
_("Scale"),
"res/actions/scale24_black.png")
"res/actions/scaleWidth24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
.UseStandardParameters(
@@ -69,6 +72,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["X"].SetGroup("");
aut.AddExpressionAndConditionAndAction(
"number",
@@ -77,7 +81,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
_("the scale on Y axis of the object (default scale is 1)"),
_("the scale on Y axis"),
_("Scale"),
"res/actions/scale24_black.png")
"res/actions/scaleHeight24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "ScalableBehavior")
.UseStandardParameters(
@@ -85,6 +89,7 @@ void GD_CORE_API BuiltinExtensionsImplementer::ImplementsScalableExtension(
gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Scale (1 by default)")))
.MarkAsAdvanced();
aut.GetAllExpressions()["Y"].SetGroup("");
}
} // namespace gd

View File

@@ -0,0 +1,58 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the GNU Lesser General Public
* License.
*/
#include "GDCore/Extensions/Builtin/AllBuiltinExtensions.h"
#include "GDCore/Extensions/Metadata/MultipleInstructionMetadata.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/BehaviorsSharedData.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Tools/Localization.h"
using namespace std;
namespace gd {
void GD_CORE_API BuiltinExtensionsImplementer::ImplementsTextContainerExtension(
gd::PlatformExtension& extension) {
extension
.SetExtensionInformation("TextContainerCapability",
_("Text capability"),
_("Animate objects."),
"Florian Rival",
"Open source (MIT License)")
.SetExtensionHelpPath("/objects");
extension.AddInstructionOrExpressionGroupMetadata(_("Text capability"))
.SetIcon("res/conditions/text24_black.png");
gd::BehaviorMetadata& aut = extension.AddBehavior(
"TextContainerBehavior",
_("Text capability"),
"Text",
_("Access objects text."),
"",
"res/conditions/text24_black.png",
"TextContainerBehavior",
std::make_shared<gd::Behavior>(),
std::make_shared<gd::BehaviorsSharedData>())
.SetHidden();
aut.AddExpressionAndConditionAndAction(
"string",
"Value",
_("Text"),
_("the text"),
_("the text"),
"",
"res/conditions/text24_black.png")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "TextContainerBehavior")
.UseStandardParameters(
"string", gd::ParameterOptions::MakeNewOptions().SetDescription(
_("Text")))
.MarkAsSimple();
aut.GetAllStrExpressions()["Value"].SetGroup("");
}
} // namespace gd

View File

@@ -35,6 +35,8 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
.AddInstructionOrExpressionGroupMetadata(_("Events and control flow"))
.SetIcon("res/conditions/toujours24_black.png");
// This condition is deprecated as this does not bring anything new
// and can be confusing or misleading for beginners.
extension
.AddCondition("Always",
_("Always"),
@@ -46,7 +48,8 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
"res/conditions/toujours_black.png")
.SetHelpPath("/all-features/advanced-conditions")
.AddCodeOnlyParameter("conditionInverted", "")
.MarkAsAdvanced();
.MarkAsAdvanced()
.SetHidden();
// Compatibility with GD <= 5.0.127
extension
@@ -114,8 +117,9 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
// Compatibility with GD <= 5.0.127
extension
.AddDuplicatedCondition(
"Egal", "BuiltinCommonInstructions::CompareNumbers", {.unscoped = true})
.AddDuplicatedCondition("Egal",
"BuiltinCommonInstructions::CompareNumbers",
{.unscoped = true})
.SetHidden();
// end of compatibility code
@@ -135,8 +139,9 @@ BuiltinExtensionsImplementer::ImplementsCommonInstructionsExtension(
// Compatibility with GD <= 5.0.127
extension
.AddDuplicatedCondition(
"StrEqual", "BuiltinCommonInstructions::CompareStrings", {.unscoped = true})
.AddDuplicatedCondition("StrEqual",
"BuiltinCommonInstructions::CompareStrings",
{.unscoped = true})
.SetHidden();
// end of compatibility code

View File

@@ -38,6 +38,8 @@ BuiltinExtensionsImplementer::ImplementsExternalLayoutsExtension(
.SetDefaultValue("0")
.AddParameter("expression", _("Y position of the origin"), "", true)
.SetDefaultValue("0")
.AddParameter("expression", _("Z position of the origin"), "", true)
.SetDefaultValue("0")
.MarkAsAdvanced();
}

View File

@@ -14,6 +14,7 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/String.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
@@ -391,20 +392,19 @@ MetadataProvider::GetBehaviorAnyExpressionMetadata(const gd::Platform& platform,
}
const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& node) {
if (!node.behaviorName.empty()) {
gd::String behaviorType =
GetTypeOfBehavior(globalObjectsContainer, objectsContainer, node.behaviorName);
gd::String behaviorType =
objectsContainersList.GetTypeOfBehavior(node.behaviorName);
return MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, node.functionName);
}
else if (!node.objectName.empty()) {
gd::String objectType =
GetTypeOfObject(globalObjectsContainer, objectsContainer, node.objectName);
gd::String objectType =
objectsContainersList.GetTypeOfObject(node.objectName);
return MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, node.functionName);
}
@@ -412,10 +412,9 @@ const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
return MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
}
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
ExpressionNode& parameter) {
int parameterIndex = -1;
@@ -429,17 +428,15 @@ const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
return nullptr;
}
return MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
functionCall,
parameterIndex);
}
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ParameterMetadata* MetadataProvider::GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
int parameterIndex) {
// Search the parameter metadata index skipping invisible ones.
@@ -448,7 +445,7 @@ const gd::ExpressionMetadata& MetadataProvider::GetFunctionCallMetadata(
ExpressionParser2::WrittenParametersFirstIndex(
functionCall.objectName, functionCall.behaviorName);
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, functionCall);
platform, objectsContainersList, functionCall);
if (IsBadExpressionMetadata(metadata)) {
return nullptr;

View File

@@ -15,6 +15,7 @@ class ExpressionMetadata;
class ExpressionMetadata;
class Platform;
class PlatformExtension;
class ObjectsContainersList;
struct FunctionCallNode;
struct ExpressionNode;
} // namespace gd
@@ -237,22 +238,19 @@ class GD_CORE_API MetadataProvider {
const gd::Platform& platform, gd::String objectType, gd::String exprType);
static const gd::ExpressionMetadata& GetFunctionCallMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& node);
static const gd::ParameterMetadata* GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
ExpressionNode& parameter);
static const gd::ParameterMetadata* GetFunctionCallParameterMetadata(
const gd::Platform& platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::Platform& platform,
const gd::ObjectsContainersList &objectsContainersList,
FunctionCallNode& functionCall,
int parameterIndex);

View File

@@ -34,6 +34,11 @@ class GD_CORE_API ParameterMetadata {
*/
gd::ValueTypeMetadata &GetValueTypeMetadata() { return valueTypeMetadata; }
/**
* \brief Return the metadata of the parameter type.
*/
const gd::ValueTypeMetadata &GetValueTypeMetadata() const { return valueTypeMetadata; }
/**
* \brief Set the metadata of the parameter type.
*/

View File

@@ -8,6 +8,7 @@
#include "GDCore/Events/Expression.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
#include "InstructionMetadata.h"
@@ -15,6 +16,8 @@
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
namespace gd {
const ParameterMetadata ParameterMetadataTools::badParameterMetadata;
void ParameterMetadataTools::ParametersToObjectsContainer(
const gd::Project& project,
const std::vector<gd::ParameterMetadata>& parameters,
@@ -57,6 +60,55 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
}
}
void ParameterMetadataTools::ForEachParameterMatchingSearch(
const std::vector<const std::vector<gd::ParameterMetadata>*>&
parametersVectorsList,
const gd::String& search,
std::function<void(const gd::ParameterMetadata&)> cb) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName().FindCaseInsensitive(search) != gd::String::npos) cb(parameterMetadata);
}
}
}
bool ParameterMetadataTools::Has(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName() == parameterName) return true;
}
}
return false;
}
const gd::ParameterMetadata& ParameterMetadataTools::Get(
const std::vector<const std::vector<gd::ParameterMetadata>*>&
parametersVectorsList,
const gd::String& parameterName) {
for (auto it = parametersVectorsList.rbegin();
it != parametersVectorsList.rend();
++it) {
const std::vector<gd::ParameterMetadata>* parametersVector = *it;
for (const auto& parameterMetadata: *parametersVector) {
if (parameterMetadata.GetName() == parameterName) return parameterMetadata;
}
}
return badParameterMetadata;
}
void ParameterMetadataTools::IterateOverParameters(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
@@ -105,8 +157,7 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
void ParameterMetadataTools::IterateOverParametersWithIndex(
const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer, FunctionCallNode &node,
const gd::ObjectsContainersList &objectsContainersList, FunctionCallNode &node,
std::function<void(const gd::ParameterMetadata &parameterMetadata,
std::unique_ptr<gd::ExpressionNode> &parameterNode,
size_t parameterIndex, const gd::String &lastObjectName)>
@@ -117,8 +168,7 @@ void ParameterMetadataTools::IterateOverParametersWithIndex(
const gd::ExpressionMetadata &metadata =
isObjectFunction ? MetadataProvider::GetObjectAnyExpressionMetadata(
platform,
GetTypeOfObject(globalObjectsContainer,
objectsContainer, node.objectName),
objectsContainersList.GetTypeOfObject(node.objectName),
node.functionName)
: MetadataProvider::GetAnyExpressionMetadata(
platform, node.functionName);

View File

@@ -3,9 +3,8 @@
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#if defined(GD_IDE_ONLY)
#ifndef ParameterMetadataTools_H
#define ParameterMetadataTools_H
#pragma once
#include <functional>
#include <vector>
#include <memory>
#include "GDCore/String.h"
@@ -13,6 +12,7 @@ namespace gd {
class Platform;
class Project;
class ObjectsContainer;
class ObjectsContainersList;
class ParameterMetadata;
class Expression;
struct FunctionCallNode;
@@ -27,6 +27,19 @@ class GD_CORE_API ParameterMetadataTools {
const std::vector<gd::ParameterMetadata>& parameters,
gd::ObjectsContainer& outputObjectsContainer);
static void ForEachParameterMatchingSearch(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& search,
std::function<void(const gd::ParameterMetadata&)> cb);
static bool Has(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName);
static const gd::ParameterMetadata& Get(
const std::vector<const std::vector<gd::ParameterMetadata>*>& parametersVectorsList,
const gd::String& parameterName);
/**
* Iterate over a list of parameters and their values.
* Callback function is called with the parameter metadata, its value
@@ -59,8 +72,7 @@ class GD_CORE_API ParameterMetadataTools {
*/
static void IterateOverParametersWithIndex(
const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer, FunctionCallNode &node,
const gd::ObjectsContainersList &objectsContainersList, FunctionCallNode &node,
std::function<void(const gd::ParameterMetadata &parameterMetadata,
std::unique_ptr<gd::ExpressionNode> &parameterNode,
size_t parameterIndex,
@@ -74,8 +86,8 @@ class GD_CORE_API ParameterMetadataTools {
static size_t GetObjectParameterIndexFor(
const std::vector<gd::ParameterMetadata>& parametersMetadata,
size_t parameterIndex);
private:
static const gd::ParameterMetadata badParameterMetadata;
};
} // namespace gd
#endif // ParameterMetadataTools_H
#endif

View File

@@ -121,6 +121,13 @@ class GD_CORE_API ValueTypeMetadata {
return gd::ValueTypeMetadata::IsTypeExpression("string", name);
}
/**
* \brief Return true if the type is a boolean.
*/
bool IsBoolean() const {
return gd::ValueTypeMetadata::IsTypeExpression("boolean", name);
}
/**
* \brief Return true if the type of the parameter is a number.
* \note If you had a new type of parameter, also add it in the IDE (
@@ -131,6 +138,24 @@ class GD_CORE_API ValueTypeMetadata {
return gd::ValueTypeMetadata::IsTypeExpression("variable", name);
}
/**
* \brief Return true if the type is a variable but from a specific scope
* (scene, project or object). In new code, prefer to use the more generic "variable"
* parameter (which accepts any variable coming from an object or from containers in the scope).
*/
bool IsLegacyPreScopedVariable() const {
return gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(name);
}
/**
* \brief Return true if the type is a variable but from a specific scope
* (scene, project or object). In new code, prefer to use the more generic "variable"
* parameter (which accepts any variable coming from an object or from containers in the scope).
*/
static bool IsTypeLegacyPreScopedVariable(const gd::String &type) {
return type == "scenevar" || type == "globalvar" || type == "objectvar";
}
/**
* \brief Return true if the type is representing one object
* (or more, i.e: an object group).
@@ -175,8 +200,13 @@ class GD_CORE_API ValueTypeMetadata {
parameterType == "externalLayoutName" ||
parameterType == "leaderboardId" ||
parameterType == "identifier";
} else if (type == "boolean") {
return parameterType == "yesorno" || parameterType == "trueorfalse";
} else if (type == "variable") {
return parameterType == "objectvar" || parameterType == "globalvar" ||
return
parameterType == "variable" || // Any variable.
// Old, "pre-scoped" variables:
parameterType == "objectvar" || parameterType == "globalvar" ||
parameterType == "scenevar";
} else if (type == "resource") {
return parameterType == "fontResource" ||
@@ -196,7 +226,7 @@ class GD_CORE_API ValueTypeMetadata {
* \brief Return the expression type from the parameter type.
* Declinations of "number" and "string" types (like "forceMultiplier" or
* "sceneName") are replaced by "number" and "string".
*
*
* \note It only maps string and number types.
*/
static const gd::String &GetExpressionPrimitiveValueType(const gd::String &parameterType);
@@ -205,7 +235,7 @@ class GD_CORE_API ValueTypeMetadata {
* \brief Return the primitive type from the parameter type.
* Declinations of "number" and "string" types (like "forceMultiplier" or
* "sceneName") are replaced by "number" and "string".
*
*
* \note It also maps variable and boolean types.
*/
static const gd::String &GetPrimitiveValueType(const gd::String &parameterType);

View File

@@ -11,6 +11,8 @@
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Instruction.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/String.h"
using namespace std;
@@ -47,7 +49,14 @@ bool ArbitraryEventsWorker::VisitEvent(gd::BaseEvent& event) {
for (std::size_t j = 0; j < actionsVectors.size(); ++j)
VisitInstructionList(*actionsVectors[j], false);
return false;
auto allExpressionsWithMetadata = event.GetAllExpressionsWithMetadata();
for (auto& expressionAndMetadata : allExpressionsWithMetadata) {
shouldDelete |= VisitEventExpression(
*expressionAndMetadata.first, expressionAndMetadata.second);
}
return shouldDelete;
}
bool ArbitraryEventsWorker::VisitLinkEvent(gd::LinkEvent& linkEvent) {
@@ -75,6 +84,11 @@ bool ArbitraryEventsWorker::VisitInstruction(gd::Instruction& instruction,
return DoVisitInstruction(instruction, isCondition);
}
bool ArbitraryEventsWorker::VisitEventExpression(gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
return DoVisitEventExpression(expression, metadata);
}
ArbitraryEventsWorkerWithContext::~ArbitraryEventsWorkerWithContext() {}
@@ -141,6 +155,12 @@ void ReadOnlyArbitraryEventsWorker::VisitInstruction(const gd::Instruction& inst
DoVisitInstruction(instruction, isCondition);
}
void ReadOnlyArbitraryEventsWorker::VisitEventExpression(const gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
DoVisitEventExpression(expression, metadata);
}
void ReadOnlyArbitraryEventsWorker::StopAnyEventIteration() {
shouldStopIteration = true;
}

View File

@@ -10,6 +10,7 @@
#include <vector>
#include "GDCore/Events/InstructionsList.h"
#include "GDCore/Events/EventVisitor.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
class Instruction;
@@ -17,6 +18,8 @@ class BaseEvent;
class LinkEvent;
class EventsList;
class ObjectsContainer;
class Expression;
class ParameterMetadata;
} // namespace gd
namespace gd {
@@ -47,6 +50,7 @@ class GD_CORE_API ArbitraryEventsWorker : private EventVisitor {
void VisitInstructionList(gd::InstructionsList& instructions,
bool areConditions);
bool VisitInstruction(gd::Instruction& instruction, bool isCondition);
bool VisitEventExpression(gd::Expression& expression, const gd::ParameterMetadata& metadata);
/**
* Called to do some work on an event list.
@@ -62,9 +66,9 @@ class GD_CORE_API ArbitraryEventsWorker : private EventVisitor {
/**
* Called to do some work on a link event.
*
*
* Note that DoVisitEvent is also called with this event.
*
*
* \return true if the event must be deleted from the events list, false
* otherwise (default).
*/
@@ -85,6 +89,16 @@ class GD_CORE_API ArbitraryEventsWorker : private EventVisitor {
bool isCondition) {
return false;
};
/**
* Called to do some work on an expression of an event.
* \return true if the event must be deleted from the list, false
* otherwise (default).
*/
virtual bool DoVisitEventExpression(gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
return false;
}
};
/**
@@ -99,8 +113,7 @@ class GD_CORE_API ArbitraryEventsWorkerWithContext
: public ArbitraryEventsWorker {
public:
ArbitraryEventsWorkerWithContext()
: currentGlobalObjectsContainer(nullptr),
currentObjectsContainer(nullptr){};
: projectScopedContainers(nullptr){};
virtual ~ArbitraryEventsWorkerWithContext();
/**
@@ -108,30 +121,27 @@ class GD_CORE_API ArbitraryEventsWorkerWithContext
* giving the objects container on which the events are applying to.
*/
void Launch(gd::EventsList& events,
const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_) {
currentGlobalObjectsContainer = &globalObjectsContainer_;
currentObjectsContainer = &objectsContainer_;
const gd::ProjectScopedContainers& projectScopedContainers_) {
projectScopedContainers = &projectScopedContainers_;
ArbitraryEventsWorker::Launch(events);
};
void Launch(gd::EventsList& events) = delete;
protected:
const gd::ObjectsContainer& GetGlobalObjectsContainer() {
const gd::ProjectScopedContainers& GetProjectScopedContainers() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentGlobalObjectsContainer;
return *projectScopedContainers;
};
const gd::ObjectsContainer& GetObjectsContainer() {
const gd::ObjectsContainersList& GetObjectsContainersList() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentObjectsContainer;
return projectScopedContainers->GetObjectsContainersList();
};
private:
const gd::ObjectsContainer* currentGlobalObjectsContainer;
const gd::ObjectsContainer* currentObjectsContainer;
const gd::ProjectScopedContainers* projectScopedContainers;
};
/**
@@ -162,6 +172,7 @@ protected:
void VisitInstructionList(const gd::InstructionsList& instructions,
bool areConditions);
void VisitInstruction(const gd::Instruction& instruction, bool isCondition);
void VisitEventExpression(const gd::Expression& expression, const gd::ParameterMetadata& metadata);
/**
* Called to do some work on an event list.
@@ -175,7 +186,7 @@ protected:
/**
* Called to do some work on a link event.
*
*
* Note that DoVisitEvent is also called with this event.
*/
virtual void DoVisitLinkEvent(const gd::LinkEvent& linkEvent) {};
@@ -192,6 +203,13 @@ protected:
virtual void DoVisitInstruction(const gd::Instruction& instruction,
bool isCondition) {};
/**
* Called to do some work on an expression of an event.
*/
virtual void DoVisitEventExpression(const gd::Expression& expression,
const gd::ParameterMetadata& metadata) {
}
bool shouldStopIteration;
};
@@ -207,8 +225,7 @@ class GD_CORE_API ReadOnlyArbitraryEventsWorkerWithContext
: public ReadOnlyArbitraryEventsWorker {
public:
ReadOnlyArbitraryEventsWorkerWithContext()
: currentGlobalObjectsContainer(nullptr),
currentObjectsContainer(nullptr){};
: projectScopedContainers(nullptr){};
virtual ~ReadOnlyArbitraryEventsWorkerWithContext();
/**
@@ -216,30 +233,22 @@ class GD_CORE_API ReadOnlyArbitraryEventsWorkerWithContext
* giving the objects container on which the events are applying to.
*/
void Launch(const gd::EventsList& events,
const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_) {
currentGlobalObjectsContainer = &globalObjectsContainer_;
currentObjectsContainer = &objectsContainer_;
const gd::ProjectScopedContainers& projectScopedContainers_) {
projectScopedContainers = &projectScopedContainers_;
ReadOnlyArbitraryEventsWorker::Launch(events);
};
void Launch(gd::EventsList& events) = delete;
protected:
const gd::ObjectsContainer& GetGlobalObjectsContainer() {
const gd::ProjectScopedContainers& GetProjectScopedContainers() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentGlobalObjectsContainer;
};
const gd::ObjectsContainer& GetObjectsContainer() {
// Pointers are guaranteed to be not nullptr after
// Launch was called.
return *currentObjectsContainer;
return *projectScopedContainers;
};
private:
const gd::ObjectsContainer* currentGlobalObjectsContainer;
const gd::ObjectsContainer* currentObjectsContainer;
const gd::ProjectScopedContainers* projectScopedContainers;
};
} // namespace gd

View File

@@ -4,7 +4,6 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"

View File

@@ -4,7 +4,6 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"

View File

@@ -31,14 +31,10 @@ namespace gd {
class GD_CORE_API ExpressionBehaviorRenamer
: public ExpressionParser2NodeWorker {
public:
ExpressionBehaviorRenamer(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
const gd::String& objectName_,
ExpressionBehaviorRenamer(const gd::String& objectName_,
const gd::String& oldBehaviorName_,
const gd::String& newBehaviorName_)
: hasDoneRenaming(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
objectName(objectName_),
oldBehaviorName(oldBehaviorName_),
newBehaviorName(newBehaviorName_){};
@@ -97,8 +93,6 @@ class GD_CORE_API ExpressionBehaviorRenamer
private:
bool hasDoneRenaming;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::String& objectName; // The object name for which the behavior
// must be replaced.
const gd::String& oldBehaviorName;
@@ -132,9 +126,7 @@ bool EventsBehaviorRenamer::DoVisitInstruction(gd::Instruction& instruction,
} else {
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionBehaviorRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),
objectName,
ExpressionBehaviorRenamer renamer(objectName,
oldBehaviorName,
newBehaviorName);
node->Visit(renamer);

View File

@@ -19,6 +19,8 @@
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
@@ -33,13 +35,11 @@ class GD_CORE_API ExpressionObjectsAnalyzer
public:
ExpressionObjectsAnalyzer(
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_,
EventsContext& context_) :
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
context(context_){};
virtual ~ExpressionObjectsAnalyzer(){};
@@ -58,6 +58,25 @@ class GD_CORE_API ExpressionObjectsAnalyzer
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
} else {
projectScopedContainers.MatchIdentifierWithName<void>(node.name, [&]() {
// This is an object variable.
context.AddObjectName(projectScopedContainers, node.name);
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -69,26 +88,41 @@ class GD_CORE_API ExpressionObjectsAnalyzer
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
context.AddObjectName(node.identifierName);
context.AddObjectName(projectScopedContainers, node.identifierName);
} else if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (identifier is a variable but not an object).
} else {
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName, [&]() {
// This is an object variable.
context.AddObjectName(projectScopedContainers, node.identifierName);
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.objectName.empty()) {
context.AddObjectName(node.objectName);
context.AddObjectName(projectScopedContainers, node.objectName);
if (!node.behaviorFunctionName.empty()) {
context.AddBehaviorName(node.objectName, node.objectFunctionOrBehaviorName);
context.AddBehaviorName(projectScopedContainers, node.objectName, node.objectFunctionOrBehaviorName);
}
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (!node.objectName.empty()) {
context.AddObjectName(node.objectName);
context.AddObjectName(projectScopedContainers, node.objectName);
if (!node.behaviorName.empty()) {
context.AddBehaviorName(node.objectName, node.behaviorName);
context.AddBehaviorName(projectScopedContainers, node.objectName, node.behaviorName);
}
}
for (auto& parameter : node.parameters) {
@@ -99,8 +133,7 @@ class GD_CORE_API ExpressionObjectsAnalyzer
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
EventsContext& context;
@@ -121,8 +154,7 @@ bool EventsContextAnalyzer::DoVisitInstruction(gd::Instruction& instruction,
const gd::Expression& parameterValue,
const gd::String& lastObjectName) {
AnalyzeParameter(platform,
project,
layout,
GetProjectScopedContainers(),
parameterMetadata,
parameterValue,
context,
@@ -134,8 +166,7 @@ bool EventsContextAnalyzer::DoVisitInstruction(gd::Instruction& instruction,
void EventsContextAnalyzer::AnalyzeParameter(
const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
const gd::ProjectScopedContainers& projectScopedContainers,
const gd::ParameterMetadata& metadata,
const gd::Expression& parameter,
EventsContext& context,
@@ -143,59 +174,39 @@ void EventsContextAnalyzer::AnalyzeParameter(
const auto& value = parameter.GetPlainString();
const auto& type = metadata.GetType();
if (ParameterMetadata::IsObject(type)) {
context.AddObjectName(value);
context.AddObjectName(projectScopedContainers, value);
} else if (ParameterMetadata::IsExpression("number", type)) {
auto node = parameter.GetRootNode();
ExpressionObjectsAnalyzer analyzer(platform, project, layout, "number", context);
ExpressionObjectsAnalyzer analyzer(platform, projectScopedContainers, "number", context);
node->Visit(analyzer);
} else if (ParameterMetadata::IsExpression("string", type)) {
auto node = parameter.GetRootNode();
ExpressionObjectsAnalyzer analyzer(platform, project, layout, "string", context);
ExpressionObjectsAnalyzer analyzer(platform, projectScopedContainers, "string", context);
node->Visit(analyzer);
} else if (ParameterMetadata::IsBehavior(type)) {
context.AddBehaviorName(lastObjectName, value);
context.AddBehaviorName(projectScopedContainers, lastObjectName, value);
}
}
void EventsContext::AddObjectName(const gd::String& objectOrGroupName) {
for (auto& realObjectName : ExpandObjectsName(objectOrGroupName)) {
void EventsContext::AddObjectName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName) {
auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
for (auto& realObjectName : objectsContainersList.ExpandObjectName(objectOrGroupName)) {
objectNames.insert(realObjectName);
}
referencedObjectOrGroupNames.insert(objectOrGroupName);
}
void EventsContext::AddBehaviorName(const gd::String& objectOrGroupName,
void EventsContext::AddBehaviorName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName,
const gd::String& behaviorName) {
for (auto& realObjectName : ExpandObjectsName(objectOrGroupName)) {
auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
for (auto& realObjectName : objectsContainersList.ExpandObjectName(objectOrGroupName)) {
objectOrGroupBehaviorNames[realObjectName].insert(behaviorName);
}
objectOrGroupBehaviorNames[objectOrGroupName].insert(behaviorName);
}
std::vector<gd::String> EventsContext::ExpandObjectsName(
const gd::String& objectName) {
// Note: this logic is duplicated in EventsCodeGenerator::ExpandObjectsName
std::vector<gd::String> realObjects;
if (project.GetObjectGroups().Has(objectName))
realObjects =
project.GetObjectGroups().Get(objectName).GetAllObjectsNames();
else if (layout.GetObjectGroups().Has(objectName))
realObjects = layout.GetObjectGroups().Get(objectName).GetAllObjectsNames();
else
realObjects.push_back(objectName);
// Ensure that all returned objects actually exists.
for (std::size_t i = 0; i < realObjects.size();) {
if (!layout.HasObjectNamed(realObjects[i]) &&
!project.HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
else
++i;
}
return realObjects;
}
} // namespace gd

View File

@@ -15,6 +15,7 @@ namespace gd {
class BaseEvent;
class Platform;
class ObjectsContainer;
class ObjectsContainersList;
class Project;
class Layout;
class EventsList;
@@ -29,12 +30,13 @@ namespace gd {
*/
class GD_CORE_API EventsContext {
public:
EventsContext(gd::ObjectsContainer& project_, gd::ObjectsContainer& layout_)
: project(project_), layout(layout_){};
EventsContext(){};
virtual ~EventsContext(){};
void AddObjectName(const gd::String& objectOrGroupName);
void AddBehaviorName(const gd::String& objectOrGroupName,
void AddObjectName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName);
void AddBehaviorName(const gd::ProjectScopedContainers& projectScopedContainers,
const gd::String& objectOrGroupName,
const gd::String& behaviorName);
/**
@@ -59,13 +61,9 @@ class GD_CORE_API EventsContext {
}
private:
std::vector<gd::String> ExpandObjectsName(const gd::String& objectOrGroupName);
std::set<gd::String> referencedObjectOrGroupNames;
std::set<gd::String> objectNames;
std::map<gd::String, std::set<gd::String>> objectOrGroupBehaviorNames;
gd::ObjectsContainer& project;
gd::ObjectsContainer& layout;
};
/**
@@ -73,15 +71,10 @@ class GD_CORE_API EventsContext {
*
* \ingroup IDE
*/
class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorker {
class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorkerWithContext {
public:
EventsContextAnalyzer(const gd::Platform& platform_,
gd::ObjectsContainer& project_,
gd::ObjectsContainer& layout_)
: platform(platform_),
project(project_),
layout(layout_),
context(project, layout){};
EventsContextAnalyzer(const gd::Platform& platform_)
: platform(platform_) {};
virtual ~EventsContextAnalyzer(){};
/**
@@ -90,8 +83,7 @@ class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorker {
const EventsContext& GetEventsContext() { return context; }
static void AnalyzeParameter(const gd::Platform& platform,
const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
const gd::ProjectScopedContainers& projectScopedContainers,
const gd::ParameterMetadata& metadata,
const gd::Expression& parameter,
EventsContext& context,
@@ -102,8 +94,6 @@ class GD_CORE_API EventsContextAnalyzer : public ArbitraryEventsWorker {
bool isCondition);
const gd::Platform& platform;
gd::ObjectsContainer& project;
gd::ObjectsContainer& layout;
EventsContext context;
};

View File

@@ -34,14 +34,12 @@ class GD_CORE_API IdentifierFinderExpressionNodeWorker
public:
IdentifierFinderExpressionNodeWorker(std::set<gd::String>& results_,
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String& identifierType_,
const gd::String& objectName_ = "")
: results(results_),
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
identifierType(identifierType_),
objectName(objectName_){};
virtual ~IdentifierFinderExpressionNodeWorker(){};
@@ -79,14 +77,14 @@ class GD_CORE_API IdentifierFinderExpressionNodeWorker
const gd::ExpressionMetadata &metadata = isObjectFunction ?
MetadataProvider::GetObjectAnyExpressionMetadata(
platform,
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName),
projectScopedContainers.GetObjectsContainersList().GetTypeOfObject(objectName),
node.functionName):
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
return;
}
size_t parameterIndex = 0;
for (size_t metadataIndex = (isObjectFunction ? 1 : 0); metadataIndex < metadata.parameters.size()
&& parameterIndex < node.parameters.size(); ++metadataIndex) {
@@ -111,8 +109,7 @@ class GD_CORE_API IdentifierFinderExpressionNodeWorker
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
std::set<gd::String>& results; ///< Reference to the std::set where argument
///< values must be stored.
@@ -166,8 +163,7 @@ class GD_CORE_API IdentifierFinderEventWorker
IdentifierFinderExpressionNodeWorker searcher(
results,
platform,
GetGlobalObjectsContainer(),
GetObjectsContainer(),
GetProjectScopedContainers(),
identifierType,
objectName);
node->Visit(searcher);
@@ -227,7 +223,8 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
platform,
identifierType,
objectName);
eventWorker.Launch(layout.GetEvents(), project, layout);
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
@@ -238,7 +235,8 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
platform,
identifierType,
objectName);
eventWorker.Launch(externalEvents.GetEvents(), project, layout);
eventWorker.Launch(externalEvents.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
}
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
const gd::Layout& dependencyLayout = project.GetLayout(sceneName);
@@ -247,7 +245,8 @@ void EventsIdentifiersFinder::FindArgumentsInEventsAndDependencies(
platform,
identifierType,
objectName);
eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout);
eventWorker.Launch(dependencyLayout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, dependencyLayout));
}
}

View File

@@ -0,0 +1,277 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsPropertyReplacer.h"
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#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"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
/**
* \brief Go through the nodes and rename properties,
* or signal if the instruction must be renamed if a removed property is used.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionPropertyReplacer
: public ExpressionParser2NodeWorker {
public:
ExpressionPropertyReplacer(
const gd::Platform& platform_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::PropertiesContainer& targetPropertiesContainer_,
const std::unordered_map<gd::String, gd::String>& oldToNewPropertyNames_,
const std::unordered_set<gd::String>& removedPropertyNames_)
: hasDoneRenaming(false),
removedPropertyUsed(false),
platform(platform_),
projectScopedContainers(projectScopedContainers_),
targetPropertiesContainer(targetPropertiesContainer_),
oldToNewPropertyNames(oldToNewPropertyNames_),
removedPropertyNames(removedPropertyNames_){};
virtual ~ExpressionPropertyReplacer(){};
bool HasDoneRenaming() const { return hasDoneRenaming; }
bool IsRemovedPropertyUsed() const { return removedPropertyUsed; }
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
node.rightHandSide->Visit(*this);
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
}
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
auto& propertiesContainersList =
projectScopedContainers.GetPropertiesContainersList();
// The node represents a variable or an object name on which a variable
// will be accessed, or a property with a child.
// Match the potential *new* name of the property, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.name),
[&]() {
// Do nothing, it's an object variable.
if (node.child) node.child->Visit(*this);
}, [&]() {
// Do nothing, it's a variable.
if (node.child) node.child->Visit(*this);
}, [&]() {
// This is a property, check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.name);
}
if (node.child) node.child->Visit(*this);
}, [&]() {
// Do nothing, it's a parameter.
if (node.child) node.child->Visit(*this);
}, [&]() {
// This is something else - potentially a deleted property.
// Check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.name);
}
if (node.child) node.child->Visit(*this);
});
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto& propertiesContainersList =
projectScopedContainers.GetPropertiesContainersList();
// Match the potential *new* name of the property, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.identifierName),
[&]() {
// Do nothing, it's an object variable.
}, [&]() {
// Do nothing, it's a variable.
}, [&]() {
// This is a property, check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.identifierName);
}
}, [&]() {
// Do nothing, it's a parameter.
}, [&]() {
// This is something else - potentially a deleted property.
// Check if it's coming from the target container with
// properties to replace.
if (propertiesContainersList.HasPropertiesContainer(
targetPropertiesContainer)) {
// The node represents a property, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemovePropertyOfTargetPropertyContainer(node.identifierName);
}
});
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
for (auto& parameter : node.parameters) {
parameter->Visit(*this);
}
}
void OnVisitEmptyNode(EmptyNode& node) override {}
private:
bool hasDoneRenaming;
bool removedPropertyUsed;
const gd::String& GetPotentialNewName(const gd::String& oldName) {
return oldToNewPropertyNames.count(oldName) >= 1
? oldToNewPropertyNames.find(oldName)->second
: oldName;
}
bool RenameOrRemovePropertyOfTargetPropertyContainer(
gd::String& propertyName) {
if (oldToNewPropertyNames.count(propertyName) >= 1) {
propertyName = oldToNewPropertyNames.find(propertyName)->second;
hasDoneRenaming = true;
return true;
} else if (removedPropertyNames.count(propertyName) >= 1) {
removedPropertyUsed = true;
return true;
}
return false; // Nothing was changed or done.
}
// Scope:
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
// Renaming or removing to do:
const gd::PropertiesContainer& targetPropertiesContainer;
const std::unordered_map<gd::String, gd::String>& oldToNewPropertyNames;
const std::unordered_set<gd::String>& removedPropertyNames;
gd::String objectNameToUseForVariableAccessor;
};
bool EventsPropertyReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const auto& metadata = isCondition
? gd::MetadataProvider::GetConditionMetadata(
platform, instruction.GetType())
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return; // Not an expression that can contain properties.
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionPropertyReplacer renamer(platform,
GetProjectScopedContainers(),
targetPropertiesContainer,
oldToNewPropertyNames,
removedPropertyNames);
node->Visit(renamer);
if (renamer.IsRemovedPropertyUsed()) {
shouldDeleteInstruction = true;
} else if (renamer.HasDoneRenaming()) {
instruction.SetParameter(
parameterIndex, ExpressionParser2NodePrinter::PrintNode(*node));
}
}
});
return shouldDeleteInstruction;
}
bool EventsPropertyReplacer::DoVisitEventExpression(
gd::Expression& expression, const gd::ParameterMetadata& metadata) {
const gd::String& type = metadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return false; // Not an expression that can contain properties.
auto node = expression.GetRootNode();
if (node) {
ExpressionPropertyReplacer renamer(platform,
GetProjectScopedContainers(),
targetPropertiesContainer,
oldToNewPropertyNames,
removedPropertyNames);
node->Visit(renamer);
if (renamer.IsRemovedPropertyUsed()) {
return true;
} else if (renamer.HasDoneRenaming()) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
return false;
}
EventsPropertyReplacer::~EventsPropertyReplacer() {}
} // namespace gd

View File

@@ -0,0 +1,56 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class PropertiesContainer;
class EventsList;
class Platform;
} // namespace gd
namespace gd {
/**
* \brief Replace in expressions and in parameters of actions or conditions,
* references to the name of a property by another.
*
* \ingroup IDE
*/
class GD_CORE_API EventsPropertyReplacer
: public ArbitraryEventsWorkerWithContext {
public:
EventsPropertyReplacer(
const gd::Platform &platform_,
const gd::PropertiesContainer &targetPropertiesContainer_,
const std::unordered_map<gd::String, gd::String> &oldToNewPropertyNames_,
const std::unordered_set<gd::String> &removedPropertyNames_)
: platform(platform_),
targetPropertiesContainer(targetPropertiesContainer_),
oldToNewPropertyNames(oldToNewPropertyNames_),
removedPropertyNames(removedPropertyNames_){};
virtual ~EventsPropertyReplacer();
private:
bool DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) override;
bool DoVisitEventExpression(gd::Expression &expression,
const gd::ParameterMetadata &metadata) override;
const gd::Platform &platform;
const gd::PropertiesContainer &targetPropertiesContainer;
const std::unordered_map<gd::String, gd::String> &oldToNewPropertyNames;
const std::unordered_set<gd::String> &removedPropertyNames;
};
} // namespace gd

View File

@@ -20,6 +20,7 @@
#include "GDCore/IDE/Events/InstructionSentenceFormatter.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/EventsBasedObject.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/IDE/Events/ExpressionTypeFinder.h"
using namespace std;
@@ -36,14 +37,12 @@ const gd::String EventsRefactorer::searchIgnoredCharacters = ";:,#()";
class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
public:
ExpressionObjectRenamer(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String &rootType_,
const gd::String& objectName_,
const gd::String& objectNewName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
hasDoneRenaming(false),
objectName(objectName_),
@@ -51,14 +50,13 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
virtual ~ExpressionObjectRenamer(){};
static bool Rename(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node,
const gd::String& objectName,
const gd::String& objectNewName) {
if (gd::ExpressionValidator::HasNoErrors(platform, globalObjectsContainer, objectsContainer, rootType, node)) {
ExpressionObjectRenamer renamer(platform, globalObjectsContainer, objectsContainer, rootType, objectName, objectNewName);
if (gd::ExpressionValidator::HasNoErrors(platform, projectScopedContainers, rootType, node)) {
ExpressionObjectRenamer renamer(platform, projectScopedContainers, rootType, objectName, objectNewName);
node.Visit(renamer);
return renamer.HasDoneRenaming();
@@ -83,6 +81,28 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.name == objectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.name, [&]() {
// This is an object variable.
hasDoneRenaming = true;
node.name = objectNewName;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -94,11 +114,29 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
hasDoneRenaming = true;
node.identifierName = objectNewName;
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.identifierName == objectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName, [&]() {
// This is an object variable.
hasDoneRenaming = true;
node.identifierName = objectNewName;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
@@ -124,8 +162,7 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
const gd::String& objectNewName;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};
@@ -138,26 +175,23 @@ class GD_CORE_API ExpressionObjectRenamer : public ExpressionParser2NodeWorker {
class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
public:
ExpressionObjectFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_,
const gd::String& objectName_)
const gd::String& searchedObjectName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
hasObject(false),
objectName(objectName_){};
searchedObjectName(searchedObjectName_){};
virtual ~ExpressionObjectFinder(){};
static bool CheckIfHasObject(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node,
const gd::String& objectName) {
if (gd::ExpressionValidator::HasNoErrors(platform, globalObjectsContainer, objectsContainer, rootType, node)) {
ExpressionObjectFinder finder(platform, globalObjectsContainer, objectsContainer, rootType, objectName);
if (gd::ExpressionValidator::HasNoErrors(platform, projectScopedContainers, rootType, node)) {
ExpressionObjectFinder finder(platform, projectScopedContainers, rootType, objectName);
node.Visit(finder);
return finder.HasFoundObject();
@@ -182,6 +216,27 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.name == searchedObjectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.name, [&]() {
// This is an object variable.
hasObject = true;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -193,19 +248,36 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto type = gd::ExpressionTypeFinder::GetType(platform, globalObjectsContainer, objectsContainer, rootType, node);
auto type = gd::ExpressionTypeFinder::GetType(platform, projectScopedContainers, rootType, node);
if (gd::ParameterMetadata::IsObject(type) &&
node.identifierName == objectName) {
node.identifierName == searchedObjectName) {
hasObject = true;
} else if (gd::ValueTypeMetadata::IsTypeLegacyPreScopedVariable(type)) {
// Nothing to do (this can't reference an object)
} else {
if (node.identifierName == searchedObjectName) {
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName, [&]() {
// This is an object variable.
hasObject = true;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
}
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (node.objectName == objectName) {
if (node.objectName == searchedObjectName) {
hasObject = true;
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (node.objectName == objectName) {
if (node.objectName == searchedObjectName) {
hasObject = true;
}
for (auto& parameter : node.parameters) {
@@ -216,17 +288,15 @@ class GD_CORE_API ExpressionObjectFinder : public ExpressionParser2NodeWorker {
private:
bool hasObject;
const gd::String& objectName;
const gd::String& searchedObjectName;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};
bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& actions,
gd::String oldName,
gd::String newName) {
@@ -245,7 +315,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
"number", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
actions[aId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -255,7 +325,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
"string", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
actions[aId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -265,8 +335,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
if (!actions[aId].GetSubInstructions().empty())
somethingModified =
RenameObjectInActions(platform,
project,
layout,
projectScopedContainers,
actions[aId].GetSubInstructions(),
oldName,
newName) ||
@@ -278,8 +347,7 @@ bool EventsRefactorer::RenameObjectInActions(const gd::Platform& platform,
bool EventsRefactorer::RenameObjectInConditions(
const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String oldName,
gd::String newName) {
@@ -299,7 +367,7 @@ bool EventsRefactorer::RenameObjectInConditions(
"number", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
conditions[cId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -309,7 +377,7 @@ bool EventsRefactorer::RenameObjectInConditions(
"string", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
conditions[cId].SetParameter(
pNb, ExpressionParser2NodePrinter::PrintNode(*node));
}
@@ -319,8 +387,7 @@ bool EventsRefactorer::RenameObjectInConditions(
if (!conditions[cId].GetSubInstructions().empty())
somethingModified =
RenameObjectInConditions(platform,
project,
layout,
projectScopedContainers,
conditions[cId].GetSubInstructions(),
oldName,
newName) ||
@@ -332,8 +399,7 @@ bool EventsRefactorer::RenameObjectInConditions(
bool EventsRefactorer::RenameObjectInEventParameters(
const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::Expression& expression,
gd::ParameterMetadata parameterMetadata,
gd::String oldName,
@@ -348,7 +414,7 @@ bool EventsRefactorer::RenameObjectInEventParameters(
parameterMetadata.GetType())) {
auto node = expression.GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "number", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "number", *node, oldName, newName)) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
@@ -357,7 +423,7 @@ bool EventsRefactorer::RenameObjectInEventParameters(
parameterMetadata.GetType())) {
auto node = expression.GetRootNode();
if (ExpressionObjectRenamer::Rename(platform, project, layout, "string", *node, oldName, newName)) {
if (ExpressionObjectRenamer::Rename(platform, projectScopedContainers, "string", *node, oldName, newName)) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
@@ -366,8 +432,7 @@ bool EventsRefactorer::RenameObjectInEventParameters(
}
void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String oldName,
gd::String newName) {
@@ -376,14 +441,14 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
events[i].GetAllConditionsVectors();
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
bool somethingModified = RenameObjectInConditions(
platform, project, layout, *conditionsVectors[j], oldName, newName);
platform, projectScopedContainers, *conditionsVectors[j], oldName, newName);
}
vector<gd::InstructionsList*> actionsVectors =
events[i].GetAllActionsVectors();
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
bool somethingModified = RenameObjectInActions(
platform, project, layout, *actionsVectors[j], oldName, newName);
platform, projectScopedContainers, *actionsVectors[j], oldName, newName);
}
vector<pair<gd::Expression*, gd::ParameterMetadata>>
@@ -393,8 +458,7 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
gd::ParameterMetadata parameterMetadata =
expressionsWithMetadata[j].second;
bool somethingModified = RenameObjectInEventParameters(platform,
project,
layout,
projectScopedContainers,
*expression,
parameterMetadata,
oldName,
@@ -403,8 +467,7 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
if (events[i].CanHaveSubEvents())
RenameObjectInEvents(platform,
project,
layout,
projectScopedContainers,
events[i].GetSubEvents(),
oldName,
newName);
@@ -412,8 +475,7 @@ void EventsRefactorer::RenameObjectInEvents(const gd::Platform& platform,
}
bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& actions,
gd::String name) {
bool somethingModified = false;
@@ -435,7 +497,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
"number", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "number", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
deleteMe = true;
break;
}
@@ -445,7 +507,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
"string", instrInfos.parameters[pNb].GetType())) {
auto node = actions[aId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "string", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {
deleteMe = true;
break;
}
@@ -459,8 +521,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
} else if (!actions[aId].GetSubInstructions().empty())
somethingModified =
RemoveObjectInActions(platform,
globalObjectsContainer,
objectsContainer,
projectScopedContainers,
actions[aId].GetSubInstructions(),
name) ||
somethingModified;
@@ -471,8 +532,7 @@ bool EventsRefactorer::RemoveObjectInActions(const gd::Platform& platform,
bool EventsRefactorer::RemoveObjectInConditions(
const gd::Platform& platform,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String name) {
bool somethingModified = false;
@@ -495,7 +555,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
"number", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "number", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "number", *node, name)) {
deleteMe = true;
break;
}
@@ -505,7 +565,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
"string", instrInfos.parameters[pNb].GetType())) {
auto node = conditions[cId].GetParameter(pNb).GetRootNode();
if (ExpressionObjectFinder::CheckIfHasObject(platform, globalObjectsContainer, objectsContainer, "string", *node, name)) {
if (ExpressionObjectFinder::CheckIfHasObject(platform, projectScopedContainers, "string", *node, name)) {
deleteMe = true;
break;
}
@@ -519,8 +579,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
} else if (!conditions[cId].GetSubInstructions().empty())
somethingModified =
RemoveObjectInConditions(platform,
globalObjectsContainer,
objectsContainer,
projectScopedContainers,
conditions[cId].GetSubInstructions(),
name) ||
somethingModified;
@@ -530,8 +589,7 @@ bool EventsRefactorer::RemoveObjectInConditions(
}
void EventsRefactorer::RemoveObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& globalObjectsContainer,
gd::ObjectsContainer& objectsContainer,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String name) {
for (std::size_t i = 0; i < events.size(); ++i) {
@@ -539,19 +597,19 @@ void EventsRefactorer::RemoveObjectInEvents(const gd::Platform& platform,
events[i].GetAllConditionsVectors();
for (std::size_t j = 0; j < conditionsVectors.size(); ++j) {
bool conditionsModified = RemoveObjectInConditions(
platform, globalObjectsContainer, objectsContainer, *conditionsVectors[j], name);
platform, projectScopedContainers, *conditionsVectors[j], name);
}
vector<gd::InstructionsList*> actionsVectors =
events[i].GetAllActionsVectors();
for (std::size_t j = 0; j < actionsVectors.size(); ++j) {
bool actionsModified = RemoveObjectInActions(
platform, globalObjectsContainer, objectsContainer, *actionsVectors[j], name);
platform, projectScopedContainers, *actionsVectors[j], name);
}
if (events[i].CanHaveSubEvents())
RemoveObjectInEvents(
platform, globalObjectsContainer, objectsContainer, events[i].GetSubEvents(), name);
platform, projectScopedContainers, events[i].GetSubEvents(), name);
}
}
@@ -587,20 +645,20 @@ std::vector<EventsSearchResult> EventsRefactorer::ReplaceStringInEvents(
for (std::size_t i = 0; i < events.size(); ++i) {
bool eventModified = false;
std::vector<gd::Expression*> allObjectExpressions =
events[i].GetAllObjectExpressions();
for (std::size_t j = 0; j < allObjectExpressions.size(); ++j) {
auto allExpressionsWithMetadata = events[i].GetAllExpressionsWithMetadata();
for (auto& expressionAndMetadata : allExpressionsWithMetadata) {
gd::Expression* expression = expressionAndMetadata.first;
gd::String newExpressionPlainString =
matchCase ? allObjectExpressions[j]->GetPlainString().FindAndReplace(
matchCase ? expression->GetPlainString().FindAndReplace(
toReplace, newString, true)
: ReplaceAllOccurrencesCaseInsensitive(
allObjectExpressions[j]->GetPlainString(),
expression->GetPlainString(),
toReplace,
newString);
if (newExpressionPlainString !=
allObjectExpressions[j]->GetPlainString()) {
*allObjectExpressions[j] = gd::Expression(newExpressionPlainString);
if (newExpressionPlainString != expression->GetPlainString()) {
*expression = gd::Expression(newExpressionPlainString);
if (!eventModified) {
modifiedEvents.push_back(EventsSearchResult(
@@ -815,14 +873,14 @@ vector<EventsSearchResult> EventsRefactorer::SearchInEvents(
for (std::size_t i = 0; i < events.size(); ++i) {
bool eventAddedInResults = false;
std::vector<gd::Expression*> allObjectExpressions =
events[i].GetAllObjectExpressions();
for (std::size_t j = 0; j < allObjectExpressions.size(); ++j) {
auto allExpressionsWithMetadata = events[i].GetAllExpressionsWithMetadata();
for (auto& expressionAndMetadata : allExpressionsWithMetadata) {
gd::Expression* expression = expressionAndMetadata.first;
size_t foundPosition =
matchCase
? allObjectExpressions[j]->GetPlainString().find(search)
: allObjectExpressions[j]->GetPlainString().FindCaseInsensitive(
search);
? expression->GetPlainString().find(search)
: expression->GetPlainString().FindCaseInsensitive(search);
if (foundPosition != gd::String::npos && !eventAddedInResults) {
results.push_back(EventsSearchResult(

View File

@@ -14,6 +14,8 @@
namespace gd {
class EventsList;
class ObjectsContainer;
class ObjectsContainersList;
class ProjectScopedContainers;
class Platform;
class ExternalEvents;
class BaseEvent;
@@ -79,8 +81,7 @@ class GD_CORE_API EventsRefactorer {
* events ).
*/
static void RenameObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String oldName,
gd::String newName);
@@ -89,8 +90,7 @@ class GD_CORE_API EventsRefactorer {
* Remove all actions or conditions using an object
*/
static void RemoveObjectInEvents(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::EventsList& events,
gd::String name);
@@ -136,8 +136,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RenameObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& instructions,
gd::String oldName,
gd::String newName);
@@ -149,8 +148,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RenameObjectInConditions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& instructions,
gd::String oldName,
gd::String newName);
@@ -163,8 +161,7 @@ class GD_CORE_API EventsRefactorer {
*/
static bool RenameObjectInEventParameters(
const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::Expression& expression,
gd::ParameterMetadata parameterMetadata,
gd::String oldName,
@@ -176,8 +173,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RemoveObjectInConditions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String name);
@@ -187,8 +183,7 @@ class GD_CORE_API EventsRefactorer {
* \return true if something was modified.
*/
static bool RemoveObjectInActions(const gd::Platform& platform,
gd::ObjectsContainer& project,
gd::ObjectsContainer& layout,
gd::ProjectScopedContainers& projectScopedContainers,
gd::InstructionsList& conditions,
gd::String name);

View File

@@ -0,0 +1,404 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsVariableReplacer.h"
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#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/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
/**
* \brief Go through the nodes and rename variables,
* or signal if the instruction must be renamed if a removed variable is used.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionVariableReplacer
: public ExpressionParser2NodeWorker {
public:
ExpressionVariableReplacer(
const gd::Platform& platform_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::VariablesContainer& targetVariablesContainer_,
const std::unordered_map<gd::String, gd::String>& oldToNewVariableNames_,
const std::unordered_set<gd::String>& removedVariableNames_)
: hasDoneRenaming(false),
removedVariableUsed(false),
platform(platform_),
projectScopedContainers(projectScopedContainers_),
forcedInitialVariablesContainer(nullptr),
targetVariablesContainer(targetVariablesContainer_),
oldToNewVariableNames(oldToNewVariableNames_),
removedVariableNames(removedVariableNames_){};
virtual ~ExpressionVariableReplacer(){};
void SetForcedInitialVariablesContainer(
const gd::VariablesContainer* forcedInitialVariablesContainer_) {
forcedInitialVariablesContainer = forcedInitialVariablesContainer_;
}
bool HasDoneRenaming() const { return hasDoneRenaming; }
bool IsRemovedVariableUsed() const { return removedVariableUsed; }
protected:
void OnVisitSubExpressionNode(SubExpressionNode& node) override {
node.expression->Visit(*this);
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
node.rightHandSide->Visit(*this);
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
}
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
// The node represents a variable or an object name on which a variable
// will be accessed.
if (forcedInitialVariablesContainer) {
// A scope was forced. Honor it: it means this node represents a variable
// of the forced variables container.
if (forcedInitialVariablesContainer == &targetVariablesContainer) {
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
if (node.child) node.child->Visit(*this);
return;
}
// Match the potential *new* name of the variable, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.name),
[&]() {
// This represents an object.
// Remember the object name.
objectNameToUseForVariableAccessor = node.name;
if (node.child) node.child->Visit(*this);
objectNameToUseForVariableAccessor = "";
},
[&]() {
// This is a variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
if (node.child) node.child->Visit(*this);
},
[&]() {
// This is a property.
if (node.child) node.child->Visit(*this);
},
[&]() {
// This is a parameter.
if (node.child) node.child->Visit(*this);
},
[&]() {
// This is something else - potentially a deleted variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
if (node.child) node.child->Visit(*this);
});
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
if (!objectNameToUseForVariableAccessor.empty()) {
if (objectsContainersList.HasVariablesContainer(
objectNameToUseForVariableAccessor, targetVariablesContainer)) {
// The node represents an object variable, and this object variables are
// the target. Do the replacement or removals:
RenameOrRemoveVariableOfTargetVariableContainer(node.name);
}
}
objectNameToUseForVariableAccessor = "";
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableBracketAccessorNode(
VariableBracketAccessorNode& node) override {
objectNameToUseForVariableAccessor = "";
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
auto& objectsContainersList =
projectScopedContainers.GetObjectsContainersList();
// The node represents a variable or an object variable in an expression
// (and if it's a variable reference or a value does not have any importance
// here).
if (forcedInitialVariablesContainer) {
// A scope was forced. Honor it: it means this node represents a variable
// of the forced variables container.
if (forcedInitialVariablesContainer == &targetVariablesContainer) {
RenameOrRemoveVariableOfTargetVariableContainer(node.identifierName);
}
return;
}
// Match the potential *new* name of the variable, because refactorings are
// done after changes in the variables container.
projectScopedContainers.MatchIdentifierWithName<void>(
GetPotentialNewName(node.identifierName),
[&]() {
// This represents an object.
if (objectsContainersList.HasVariablesContainer(
node.identifierName, targetVariablesContainer)) {
// The node represents an object variable, and this object variables
// are the target. Do the replacement or removals:
RenameOrRemoveVariableOfTargetVariableContainer(
node.childIdentifierName);
}
},
[&]() {
// This is a variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(
node.identifierName);
}
},
[&]() {
// This is a property.
},
[&]() {
// This is a parameter.
},
[&]() {
// This is something else - potentially a deleted variable.
if (projectScopedContainers.GetVariablesContainersList()
.HasVariablesContainer(targetVariablesContainer)) {
// The node represents a variable, that can come from the target
// (because the target is in the scope), replace or remove it:
RenameOrRemoveVariableOfTargetVariableContainer(
node.identifierName);
}
});
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const gd::ExpressionMetadata& metadata =
MetadataProvider::GetFunctionCallMetadata(
platform, projectScopedContainers.GetObjectsContainersList(), node);
for (size_t parameterIndex = 0; parameterIndex < node.parameters.size();
++parameterIndex) {
const gd::ParameterMetadata* parameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
projectScopedContainers.GetObjectsContainersList(),
node,
parameterIndex);
// Handle legacy pre-scoped variable parameters: in this case, we
// force the "scope" at which starts the evalution of variables.
if (parameterMetadata && parameterMetadata->GetValueTypeMetadata()
.IsLegacyPreScopedVariable()) {
const gd::VariablesContainer* oldForcedInitialVariablesContainer =
forcedInitialVariablesContainer;
forcedInitialVariablesContainer = nullptr;
if (parameterMetadata->GetType() == "globalvar") {
forcedInitialVariablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetTopMostVariablesContainer();
} else if (parameterMetadata->GetType() == "scenevar") {
forcedInitialVariablesContainer =
projectScopedContainers.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
} else if (parameterMetadata->GetType() == "objectvar") {
auto objectName = gd::ExpressionVariableOwnerFinder::GetObjectName(
platform,
projectScopedContainers.GetObjectsContainersList(),
node.objectName,
*node.parameters[parameterIndex].get());
forcedInitialVariablesContainer =
projectScopedContainers.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(objectName);
}
node.parameters[parameterIndex]->Visit(*this);
forcedInitialVariablesContainer = oldForcedInitialVariablesContainer;
} else {
// For any other parameter, there is no special treatment being needed.
node.parameters[parameterIndex]->Visit(*this);
}
}
}
void OnVisitEmptyNode(EmptyNode& node) override {}
private:
bool hasDoneRenaming;
bool removedVariableUsed;
const gd::String& GetPotentialNewName(const gd::String& oldName) {
return oldToNewVariableNames.count(oldName) >= 1
? oldToNewVariableNames.find(oldName)->second
: oldName;
}
bool RenameOrRemoveVariableOfTargetVariableContainer(
gd::String& variableName) {
if (oldToNewVariableNames.count(variableName) >= 1) {
variableName = oldToNewVariableNames.find(variableName)->second;
hasDoneRenaming = true;
return true;
} else if (removedVariableNames.count(variableName) >= 1) {
removedVariableUsed = true;
return true;
}
return false; // Nothing was changed or done.
}
// Scope:
const gd::Platform& platform;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::VariablesContainer* forcedInitialVariablesContainer;
// Renaming or removing to do:
const gd::VariablesContainer& targetVariablesContainer;
const std::unordered_map<gd::String, gd::String>& oldToNewVariableNames;
const std::unordered_set<gd::String>& removedVariableNames;
gd::String objectNameToUseForVariableAccessor;
};
const gd::VariablesContainer*
EventsVariableReplacer::FindForcedVariablesContainerIfAny(
const gd::String& type, const gd::String& lastObjectName) {
// Handle legacy pre-scoped variable parameters: in this case, we
// force the "scope" at which starts the evalution of variables.
if (type == "objectvar") {
return GetProjectScopedContainers()
.GetObjectsContainersList()
.GetObjectOrGroupVariablesContainer(lastObjectName);
} else if (type == "globalvar") {
return GetProjectScopedContainers()
.GetVariablesContainersList()
.GetTopMostVariablesContainer();
} else if (type == "scenevar") {
return GetProjectScopedContainers()
.GetVariablesContainersList()
.GetBottomMostVariablesContainer();
}
return nullptr;
}
bool EventsVariableReplacer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const auto& metadata = isCondition
? gd::MetadataProvider::GetConditionMetadata(
platform, instruction.GetType())
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
bool shouldDeleteInstruction = false;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::Expression& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return; // Not an expression that can contain variables.
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionVariableReplacer renamer(platform,
GetProjectScopedContainers(),
targetVariablesContainer,
oldToNewVariableNames,
removedVariableNames);
renamer.SetForcedInitialVariablesContainer(
FindForcedVariablesContainerIfAny(type, lastObjectName));
node->Visit(renamer);
if (renamer.IsRemovedVariableUsed()) {
shouldDeleteInstruction = true;
} else if (renamer.HasDoneRenaming()) {
instruction.SetParameter(
parameterIndex, ExpressionParser2NodePrinter::PrintNode(*node));
}
}
});
return shouldDeleteInstruction;
}
bool EventsVariableReplacer::DoVisitEventExpression(
gd::Expression& expression, const gd::ParameterMetadata& metadata) {
const gd::String& type = metadata.GetType();
if (!gd::ParameterMetadata::IsExpression("variable", type) &&
!gd::ParameterMetadata::IsExpression("number", type) &&
!gd::ParameterMetadata::IsExpression("string", type))
return false; // Not an expression that can contain variables.
auto node = expression.GetRootNode();
if (node) {
ExpressionVariableReplacer renamer(platform,
GetProjectScopedContainers(),
targetVariablesContainer,
oldToNewVariableNames,
removedVariableNames);
renamer.SetForcedInitialVariablesContainer(
FindForcedVariablesContainerIfAny(type, ""));
node->Visit(renamer);
if (renamer.IsRemovedVariableUsed()) {
return true;
} else if (renamer.HasDoneRenaming()) {
expression = ExpressionParser2NodePrinter::PrintNode(*node);
}
}
return false;
}
EventsVariableReplacer::~EventsVariableReplacer() {}
} // namespace gd

View File

@@ -0,0 +1,60 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#pragma once
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class VariablesContainer;
class EventsList;
class Platform;
} // namespace gd
namespace gd {
/**
* \brief Replace in expressions and in parameters of actions or conditions,
* references to the name of a variable by another.
*
* \ingroup IDE
*/
class GD_CORE_API EventsVariableReplacer
: public ArbitraryEventsWorkerWithContext {
public:
EventsVariableReplacer(
const gd::Platform &platform_,
const gd::VariablesContainer &targetVariablesContainer_,
const std::unordered_map<gd::String, gd::String> &oldToNewVariableNames_,
const std::unordered_set<gd::String> &removedVariableNames_)
: platform(platform_),
targetVariablesContainer(targetVariablesContainer_),
oldToNewVariableNames(oldToNewVariableNames_),
removedVariableNames(removedVariableNames_){};
virtual ~EventsVariableReplacer();
private:
bool DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) override;
bool DoVisitEventExpression(gd::Expression &expression,
const gd::ParameterMetadata &metadata) override;
const gd::VariablesContainer *FindForcedVariablesContainerIfAny(
const gd::String &type, const gd::String &lastObjectName);
const gd::Platform &platform;
const gd::VariablesContainer &targetVariablesContainer;
gd::String objectName;
const std::unordered_map<gd::String, gd::String> &oldToNewVariableNames;
const std::unordered_set<gd::String> &removedVariableNames;
};
} // namespace gd

View File

@@ -17,6 +17,7 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/IDE/DependenciesAnalyzer.h"
@@ -34,14 +35,12 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
public:
VariableFinderExpressionNodeWorker(std::set<gd::String>& results_,
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String& parameterType_,
const gd::String& objectName_ = "")
: results(results_),
platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
parameterType(parameterType_),
objectName(objectName_){};
virtual ~VariableFinderExpressionNodeWorker(){};
@@ -60,6 +59,9 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
void OnVisitNumberNode(NumberNode& node) override {}
void OnVisitTextNode(TextNode& node) override {}
void OnVisitVariableNode(VariableNode& node) override {
// We don't check variables or object variables here, because object variables only work
// if the variable is already declared.
if (node.child) node.child->Visit(*this);
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
@@ -70,7 +72,10 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
// We don't check object variables here, because object variables only work
// if the variable is already declared.
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
bool considerFunction = objectName.empty() || node.objectName == objectName;
@@ -79,7 +84,7 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
const gd::ExpressionMetadata &metadata = isObjectFunction ?
MetadataProvider::GetObjectAnyExpressionMetadata(
platform,
GetTypeOfObject(globalObjectsContainer, objectsContainer, objectName),
projectScopedContainers.GetObjectsContainersList().GetTypeOfObject(objectName),
node.functionName):
MetadataProvider::GetAnyExpressionMetadata(platform, node.functionName);
@@ -110,8 +115,7 @@ class GD_CORE_API VariableFinderExpressionNodeWorker
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
std::set<gd::String>& results; ///< Reference to the std::set where argument
///< values must be stored.
@@ -163,8 +167,7 @@ class GD_CORE_API VariableFinderEventWorker
VariableFinderExpressionNodeWorker searcher(
results,
platform,
GetGlobalObjectsContainer(),
GetObjectsContainer(),
GetProjectScopedContainers(),
parameterType,
objectName);
node->Visit(searcher);
@@ -252,7 +255,8 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
platform,
parameterType,
objectName);
eventWorker.Launch(layout.GetEvents(), project, layout);
eventWorker.Launch(layout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
DependenciesAnalyzer dependenciesAnalyzer = DependenciesAnalyzer(project, layout);
dependenciesAnalyzer.Analyze();
@@ -263,7 +267,8 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
platform,
parameterType,
objectName);
eventWorker.Launch(externalEvents.GetEvents(), project, layout);
eventWorker.Launch(externalEvents.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout));
}
for (const gd::String& sceneName : dependenciesAnalyzer.GetScenesDependencies()) {
const gd::Layout& dependencyLayout = project.GetLayout(sceneName);
@@ -272,7 +277,8 @@ void EventsVariablesFinder::FindArgumentsInEventsAndDependencies(
platform,
parameterType,
objectName);
eventWorker.Launch(dependencyLayout.GetEvents(), project, dependencyLayout);
eventWorker.Launch(dependencyLayout.GetEvents(),
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, dependencyLayout));
}
}

View File

@@ -10,21 +10,15 @@ namespace gd {
const gd::ParameterMetadata
ExpressionCompletionDescription::badParameterMetadata;
const gd::ObjectConfiguration
ExpressionCompletionDescription::badObjectConfiguration;
/**
* \brief Turn an ExpressionCompletionDescription to a string.
*/
std::ostream& operator<<(std::ostream& os,
ExpressionCompletionDescription const& value) {
os << "{ " << value.GetCompletionKind() << ", " << value.GetType() << ", "
<< value.GetPrefix() << ", " << value.GetObjectName() << ", "
<< value.GetBehaviorName() << ", "
<< (value.IsExact() ? "exact" : "non-exact") << ", "
<< (value.IsLastParameter() ? "last parameter" : "not last parameter")
<< ", "
<< (value.HasParameterMetadata()
? gd::String::From(&value.GetParameterMetadata())
: "no parameter metadata")
<< " }";
os << value.ToString();
return os;
}

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
@@ -40,11 +40,10 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
* operations.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers &projectScopedContainers,
gd::ExpressionNode& node) {
gd::ExpressionLeftSideTypeFinder typeFinder(
platform, globalObjectsContainer, objectsContainer);
platform, projectScopedContainers);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -53,11 +52,9 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
protected:
ExpressionLeftSideTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_)
const gd::ProjectScopedContainers &projectScopedContainers_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
type("unknown") {};
const gd::String &GetType() {
@@ -69,6 +66,14 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitOperatorNode(OperatorNode& node) override {
node.leftHandSide->Visit(*this);
// The type is decided by the first operand, unless it can (`number|string`)
// or should (`unknown`) be refined, in which case we go for the right
// operand (which got visited knowing the type of the first operand, so it's
// equal or strictly more precise than the left operand).
if (type == "unknown" || type == "number|string") {
node.rightHandSide->Visit(*this);
}
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
node.factor->Visit(*this);
@@ -85,7 +90,7 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, node);
platform, projectScopedContainers.GetObjectsContainersList(), node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
type = "unknown";
}
@@ -95,12 +100,99 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
void OnVisitVariableNode(VariableNode& node) override {
type = "unknown";
projectScopedContainers.MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
// We could store it to explore the type of the variable, but in practice this
// is only called for structures/arrays with 2 levels, and we don't support structure
// type identification for now.
},
[&]() {
// This is a variable.
// We could store it to explore the type of the variable, but in practice this
// is only called for structures/arrays with 2 levels, and we don't support structure
// type identification for now.
}, [&]() {
// This is a property with more than one child - this is unsupported.
}, [&]() {
// This is a parameter with more than one child - this is unsupported.
}, [&]() {
// This is something else.
type = "unknown";
});
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
type = "unknown";
}
void OnVisitIdentifierNode(IdentifierNode& node) override {
type = "unknown";
projectScopedContainers.MatchIdentifierWithName<void>(node.identifierName,
[&]() {
// It's an object variable.
if (projectScopedContainers.GetObjectsContainersList()
.HasObjectOrGroupWithVariableNamed(
node.identifierName, node.childIdentifierName)
== ObjectsContainersList::VariableExistence::DoesNotExist) {
type = "unknown";
return;
}
auto variableType =
projectScopedContainers.GetObjectsContainersList()
.GetTypeOfObjectOrGroupVariable(node.identifierName,
node.childIdentifierName);
ReadTypeFromVariable(variableType);
},
[&]() {
// It's a variable.
const auto& variable =
projectScopedContainers.GetVariablesContainersList().Get(
node.identifierName);
if (node.childIdentifierName.empty()) {
ReadTypeFromVariable(variable.GetType());
} else {
if (!variable.HasChild(node.childIdentifierName)) {
type = "unknown";
return;
}
ReadTypeFromVariable(
variable.GetChild(node.childIdentifierName).GetType());
}
}, [&]() {
// This is a property.
const gd::NamedPropertyDescriptor& property = projectScopedContainers
.GetPropertiesContainersList().Get(node.identifierName).second;
if (property.GetType() == "Number") {
type = "number";
} else if (property.GetType() == "Boolean") {
// Nothing - we don't know the precise type (this could be used a string or as a number)
} else {
// Assume type is String or equivalent.
type = "string";
}
}, [&]() {
// It's a parameter.
const auto& parametersVectorsList = projectScopedContainers.GetParametersVectorsList();
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, node.identifierName);
const auto& valueTypeMetadata = parameter.GetValueTypeMetadata();
if (valueTypeMetadata.IsNumber()) {
type = "number";
} else if (valueTypeMetadata.IsString()) {
type = "string";
} else if (valueTypeMetadata.IsBoolean()) {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
} else {
type = "unknown";
}
}, [&]() {
// This is something else.
type = "unknown";
});
}
void OnVisitEmptyNode(EmptyNode& node) override {
type = "unknown";
@@ -110,11 +202,18 @@ class GD_CORE_API ExpressionLeftSideTypeFinder : public ExpressionParser2NodeWor
}
private:
void ReadTypeFromVariable(gd::Variable::Type variableType) {
if (variableType == gd::Variable::Number) {
type = "number";
} else if (variableType == gd::Variable::String) {
type = "string";
}
}
gd::String type;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};

View File

@@ -15,7 +15,7 @@
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
@@ -31,7 +31,7 @@ namespace gd {
/**
* \brief Find the type of the expression or sub-expression that a given node
* represents.
*
*
* The type returned by this worker is a mix of:
* - an expected type looking up like a parameter declaration
* - an actual type looking down, but only looking at the most left branch
@@ -50,12 +50,11 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
* sub-expression that a given node represents.
*/
static const gd::String GetType(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers &projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionTypeFinder typeFinder(
platform, globalObjectsContainer, objectsContainer, rootType);
platform, projectScopedContainers, rootType);
node.Visit(typeFinder);
return typeFinder.GetType();
}
@@ -64,12 +63,10 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
protected:
ExpressionTypeFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers &projectScopedContainers_,
const gd::String &rootType_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
rootType(rootType_),
type(ExpressionTypeFinder::unknownType),
child(nullptr) {};
@@ -114,10 +111,13 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
type = ExpressionTypeFinder::unknownType;
}
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
globalObjectsContainer,
objectsContainer,
platform,
projectScopedContainers,
node);
// If we can infer a definitive number or string type, use it.
// Otherwise, we only know that it's number or string, and this can even
// be used as is at runtime.
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
type = leftSideType;
@@ -129,7 +129,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (child == nullptr) {
const gd::ExpressionMetadata &metadata = MetadataProvider::GetFunctionCallMetadata(
platform, globalObjectsContainer, objectsContainer, node);
platform, projectScopedContainers.GetObjectsContainersList(), node);
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
VisitParent(node);
}
@@ -141,8 +141,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
const gd::ParameterMetadata* parameterMetadata =
gd::MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
projectScopedContainers.GetObjectsContainersList(),
node,
*child);
if (parameterMetadata == nullptr || parameterMetadata->GetType().empty()) {
@@ -162,9 +161,8 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
}
else if (rootType == ExpressionTypeFinder::numberOrStringType) {
auto leftSideType = gd::ExpressionLeftSideTypeFinder::GetType(
platform,
globalObjectsContainer,
objectsContainer,
platform,
projectScopedContainers,
node);
if (leftSideType == ExpressionTypeFinder::numberType
|| leftSideType == ExpressionTypeFinder::stringType) {
@@ -188,8 +186,7 @@ class GD_CORE_API ExpressionTypeFinder : public ExpressionParser2NodeWorker {
ExpressionNode *child;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
const gd::String rootType;
};

View File

@@ -11,16 +11,20 @@
#include "GDCore/CommonTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Extensions/Metadata/InstructionMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/Variable.h"
#include "GDCore/Project/VariablesContainersList.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
using namespace std;
@@ -59,61 +63,212 @@ size_t GetMaximumParametersNumber(
} // namespace
ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::FunctionCallNode& function) {
bool ExpressionValidator::ValidateObjectVariableOrVariableOrProperty(
const gd::IdentifierNode& identifier) {
auto validateVariableTypeForExpression =
[this, &identifier](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`."),
identifier.identifierNameLocation);
} else if (type == Variable::Array) {
RaiseTypeError(_("You need to specify the name of the child variable "
"to access. For example: `MyVariable[0]`."),
identifier.identifierNameLocation);
} else {
// Number, string or boolean variables can be used in expressions.
return;
}
};
const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
const auto& propertiesContainersList = projectScopedContainers.GetPropertiesContainersList();
const auto& parametersVectorsList = projectScopedContainers.GetParametersVectorsList();
// Unless we find something precise (like a variable or property or parameter with a known type),
// we consider this node will be of the type required by the parent.
childType = parentType;
return projectScopedContainers.MatchIdentifierWithName<bool>(identifier.identifierName,
[&]() {
// This represents an object.
if (identifier.childIdentifierName.empty()) {
RaiseTypeError(_("An object variable or expression should be entered."),
identifier.identifierNameLocation);
return true; // We should have found a variable.
}
auto variableExistence = objectsContainersList.HasObjectOrGroupWithVariableNamed(identifier.identifierName, identifier.childIdentifierName);
if (variableExistence == gd::ObjectsContainersList::DoesNotExist) {
RaiseTypeError(_("This variable does not exist on this object or group."),
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::ExistsOnlyOnSomeObjectsOfTheGroup) {
RaiseTypeError(_("This variable only exists on some objects of the group. It must be declared for all objects."),
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
else if (variableExistence == gd::ObjectsContainersList::GroupIsEmpty) {
RaiseTypeError(_("This group is empty. Add an object to this group first."),
identifier.identifierNameLocation);
return true; // We should have found a variable.
}
auto variableType = objectsContainersList.GetTypeOfObjectOrGroupVariable(identifier.identifierName, identifier.childIdentifierName);
ReadChildTypeFromVariable(variableType);
return true; // We found a variable.
}, [&]() {
// This is a variable.
// Try to identify a declared variable with the name (and maybe the child
// variable).
const gd::Variable& variable =
variablesContainersList.Get(identifier.identifierName);
if (identifier.childIdentifierName.empty()) {
// Just the root variable is accessed, check it can be used in an
// expression.
validateVariableTypeForExpression(variable.GetType());
ReadChildTypeFromVariable(variable.GetType());
return true; // We found a variable.
} else {
// A child variable is accessed, check it can be used in an expression.
if (!variable.HasChild(identifier.childIdentifierName)) {
RaiseTypeError(_("No child variable with this name found."),
identifier.childIdentifierNameLocation);
return true; // We should have found a variable.
}
const gd::Variable& childVariable =
variable.GetChild(identifier.childIdentifierName);
ReadChildTypeFromVariable(childVariable.GetType());
return true; // We found a variable.
}
}, [&]() {
// This is a property.
if (!identifier.childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
identifier.childIdentifierNameLocation);
return true; // We found a property, even if the child is not allowed.
}
const gd::NamedPropertyDescriptor& property = projectScopedContainers
.GetPropertiesContainersList().Get(identifier.identifierName).second;
if (property.GetType() == "Number") {
childType = Type::Number;
} else if (property.GetType() == "Boolean") {
// Nothing - we don't know the precise type (this could be used a string or as a number)
} else {
// Assume type is String or equivalent.
childType = Type::String;
}
return true; // We found a property.
}, [&]() {
// This is a parameter.
if (!identifier.childIdentifierName.empty()) {
RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
identifier.childIdentifierNameLocation);
return true; // We found a parameter, even if the child is not allowed.
}
const auto& parameter = gd::ParameterMetadataTools::Get(parametersVectorsList, identifier.identifierName);
const auto& valueTypeMetadata = parameter.GetValueTypeMetadata();
if (valueTypeMetadata.IsNumber()) {
childType = Type::Number;
} else if (valueTypeMetadata.IsString()) {
childType = Type::String;
} else if (valueTypeMetadata.IsBoolean()) {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
} else {
RaiseTypeError(_("This parameter is not a string, number or boolean - it can't be used in an expression."),
identifier.identifierNameLocation);
return true; // We found a parameter, even though the type is incompatible.
}
return true; // We found a parameter.
}, [&]() {
// This is something else.
return false;
});
}
ExpressionValidator::Type ExpressionValidator::ValidateFunction(
const gd::FunctionCallNode& function) {
ReportAnyError(function);
gd::String objectType = function.objectName.empty() ? "" :
GetTypeOfObject(globalObjectsContainer, objectsContainer, function.objectName);
gd::String behaviorType = function.behaviorName.empty() ? "" :
GetTypeOfBehavior(globalObjectsContainer, objectsContainer, function.behaviorName);
const gd::ExpressionMetadata &metadata = function.behaviorName.empty() ?
function.objectName.empty() ?
MetadataProvider::GetAnyExpressionMetadata(platform, function.functionName) :
MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, function.functionName) :
MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, function.functionName);
auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
gd::String objectType =
function.objectName.empty()
? ""
: objectsContainersList.GetTypeOfObject(function.objectName);
gd::String behaviorType = function.behaviorName.empty()
? ""
: objectsContainersList.GetTypeOfBehavior(function.behaviorName);
const gd::ExpressionMetadata& metadata =
function.behaviorName.empty()
? function.objectName.empty()
? MetadataProvider::GetAnyExpressionMetadata(
platform, function.functionName)
: MetadataProvider::GetObjectAnyExpressionMetadata(
platform, objectType, function.functionName)
: MetadataProvider::GetBehaviorAnyExpressionMetadata(
platform, behaviorType, function.functionName);
Type returnType = StringToType(metadata.GetReturnType());
if (!function.objectName.empty() &&
!globalObjectsContainer.HasObjectNamed(function.objectName) &&
!globalObjectsContainer.GetObjectGroups().Has(function.objectName) &&
!objectsContainer.HasObjectNamed(function.objectName) &&
!objectsContainer.GetObjectGroups().Has(function.objectName)) {
!objectsContainersList.HasObjectOrGroupNamed(function.objectName)) {
RaiseTypeError(_("This object doesn't exist."),
function.objectNameLocation, /*isFatal=*/false);
function.objectNameLocation,
/*isFatal=*/false);
return returnType;
}
if (!function.behaviorName.empty() &&
!gd::HasBehaviorInObjectOrGroup(globalObjectsContainer, objectsContainer,
function.objectName,
function.behaviorName)) {
!objectsContainersList.HasBehaviorInObjectOrGroup(function.objectName,
function.behaviorName)) {
RaiseTypeError(_("This behavior is not attached to this object."),
function.behaviorNameLocation, /*isFatal=*/false);
function.behaviorNameLocation,
/*isFatal=*/false);
return returnType;
}
if (!function.objectName.empty() &&
// If the function needs a capability on the object that may not be covered
// by all objects, check it now.
!metadata.GetRequiredBaseObjectCapability().empty()) {
const gd::ObjectMetadata &objectMetadata =
// If the function needs a capability on the object that may not be
// covered by all objects, check it now.
!metadata.GetRequiredBaseObjectCapability().empty()) {
const gd::ObjectMetadata& objectMetadata =
MetadataProvider::GetObjectMetadata(platform, objectType);
}
if (gd::MetadataProvider::IsBadExpressionMetadata(metadata)) {
RaiseError(
"invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
function.location);
return returnType;
RaiseError("invalid_function_name",
_("Cannot find an expression with this name: ") +
function.functionName + "\n" +
_("Double check that you've not made any typo in the name."),
function.location);
return returnType;
}
// Validate the type of the function
@@ -128,9 +283,9 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
} else if (parentType != Type::Number &&
parentType != Type::NumberOrString) {
RaiseTypeError(_("You tried to use an expression that returns a "
"number, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
"number, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
return returnType;
}
} else if (returnType == Type::String) {
@@ -144,16 +299,16 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
} else if (parentType != Type::String &&
parentType != Type::NumberOrString) {
RaiseTypeError(_("You tried to use an expression that returns a "
"string, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
"string, but another type is expected:") +
" " + TypeToString(parentType),
function.location);
return returnType;
}
} else {
if (parentType != returnType) {
RaiseTypeError(
_("You tried to use an expression with the wrong return type:") + " " +
TypeToString(returnType),
_("You tried to use an expression with the wrong return type:") +
" " + TypeToString(returnType),
function.location);
return returnType;
}
@@ -162,10 +317,12 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
// Validate parameters count
size_t minParametersCount = GetMinimumParametersNumber(
metadata.parameters,
ExpressionParser2::WrittenParametersFirstIndex(function.objectName, function.behaviorName));
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName));
size_t maxParametersCount = GetMaximumParametersNumber(
metadata.parameters,
ExpressionParser2::WrittenParametersFirstIndex(function.objectName, function.behaviorName));
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName));
if (function.parameters.size() < minParametersCount ||
function.parameters.size() > maxParametersCount) {
gd::String expectedCountMessage =
@@ -179,28 +336,29 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
if (function.parameters.size() < minParametersCount) {
RaiseError(
"too_few_parameters",
_("You have not entered enough parameters for the expression.") + " " +
expectedCountMessage,
_("You have not entered enough parameters for the expression.") +
" " + expectedCountMessage,
function.location);
} else {
RaiseError(
"extra_parameter",
_("This parameter was not expected by this expression. Remove it "
"or verify that you've entered the proper expression name.") + " " +
expectedCountMessage,
ExpressionParserLocation(
function.parameters[maxParametersCount]->location.GetStartPosition(),
function.location.GetEndPosition() - 1));
"or verify that you've entered the proper expression name.") +
" " + expectedCountMessage,
ExpressionParserLocation(function.parameters[maxParametersCount]
->location.GetStartPosition(),
function.location.GetEndPosition() - 1));
}
return returnType;
}
// TODO: reverse the order of diagnostic?
size_t writtenParametersFirstIndex =
ExpressionParser2::WrittenParametersFirstIndex(
function.objectName, function.behaviorName);
ExpressionParser2::WrittenParametersFirstIndex(function.objectName,
function.behaviorName);
int metadataIndex = writtenParametersFirstIndex;
for (int parameterIndex = 0; parameterIndex < function.parameters.size(); parameterIndex++) {
for (int parameterIndex = 0; parameterIndex < function.parameters.size();
parameterIndex++) {
auto& parameter = function.parameters[parameterIndex];
while (metadata.GetParameters()[metadataIndex].IsCodeOnly()) {
// The sizes are already checked above.
@@ -208,45 +366,45 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
}
auto& parameterMetadata = metadata.GetParameters()[metadataIndex];
if (!parameterMetadata.IsOptional() || dynamic_cast<EmptyNode*>(parameter.get()) == nullptr) {
if (!parameterMetadata.IsOptional() ||
dynamic_cast<EmptyNode*>(parameter.get()) == nullptr) {
auto currentParentType = parentType;
parentType = StringToType(parameterMetadata.GetType());
parameter->Visit(*this);
parentType = currentParentType;
const gd::String &expectedParameterType = parameterMetadata.GetType();
const gd::String& expectedParameterType = parameterMetadata.GetType();
if (gd::ParameterMetadata::IsExpression(
ExpressionValidator::variableTypeString, expectedParameterType)) {
if (dynamic_cast<IdentifierNode *>(parameter.get()) == nullptr
&& dynamic_cast<VariableNode *>(parameter.get()) == nullptr) {
RaiseError(
"malformed_variable_parameter",
_("A variable name was expected but something else was "
"written. Enter just the name of the variable for this "
"parameter."),
parameter->location);
ExpressionValidator::variableTypeString, expectedParameterType)) {
if (dynamic_cast<IdentifierNode*>(parameter.get()) == nullptr &&
dynamic_cast<VariableNode*>(parameter.get()) == nullptr) {
RaiseError("malformed_variable_parameter",
_("A variable name was expected but something else was "
"written. Enter just the name of the variable for this "
"parameter."),
parameter->location);
}
} else if (gd::ParameterMetadata::IsObject(expectedParameterType)) {
if (dynamic_cast<IdentifierNode *>(parameter.get()) == nullptr) {
RaiseError(
"malformed_object_parameter",
_("An object name was expected but something else was "
"written. Enter just the name of the object for this "
"parameter."),
parameter->location);
if (dynamic_cast<IdentifierNode*>(parameter.get()) == nullptr) {
RaiseError("malformed_object_parameter",
_("An object name was expected but something else was "
"written. Enter just the name of the object for this "
"parameter."),
parameter->location);
}
}
// String and number are already checked in children.
else if (!gd::ParameterMetadata::IsExpression(
ExpressionValidator::numberTypeString, expectedParameterType)
&& !gd::ParameterMetadata::IsExpression(
ExpressionValidator::stringTypeString, expectedParameterType)) {
RaiseError(
"unknown_parameter_type",
_("This function is improperly set up. Reach out to the "
"extension developer or a GDevelop maintainer to fix "
"this issue"),
parameter->location);
ExpressionValidator::numberTypeString,
expectedParameterType) &&
!gd::ParameterMetadata::IsExpression(
ExpressionValidator::stringTypeString,
expectedParameterType)) {
RaiseError("unknown_parameter_type",
_("This function is improperly set up. Reach out to the "
"extension developer or a GDevelop maintainer to fix "
"this issue"),
parameter->location);
}
}
metadataIndex++;
@@ -254,55 +412,60 @@ ExpressionValidator::Type ExpressionValidator::ValidateFunction(const gd::Functi
return returnType;
}
// TODO factorize in a file with an enum and helpers?
const gd::String ExpressionValidator::unknownTypeString = "unknown";
const gd::String ExpressionValidator::numberTypeString = "number";
const gd::String ExpressionValidator::stringTypeString = "string";
const gd::String ExpressionValidator::numberOrStringTypeString = "number|string";
const gd::String ExpressionValidator::variableTypeString = "variable";
const gd::String ExpressionValidator::objectTypeString = "object";
const gd::String ExpressionValidator::emptyTypeString = "empty";
// TODO factorize in a file with an enum and helpers?
const gd::String ExpressionValidator::unknownTypeString = "unknown";
const gd::String ExpressionValidator::numberTypeString = "number";
const gd::String ExpressionValidator::stringTypeString = "string";
const gd::String ExpressionValidator::numberOrStringTypeString =
"number|string";
const gd::String ExpressionValidator::variableTypeString = "variable";
const gd::String ExpressionValidator::objectTypeString = "object";
const gd::String ExpressionValidator::emptyTypeString = "empty";
const gd::String &ExpressionValidator::TypeToString(Type type) {
switch (type) {
case Type::Unknown:
const gd::String& ExpressionValidator::TypeToString(Type type) {
switch (type) {
case Type::Unknown:
return unknownTypeString;
case Type::Number:
case Type::Number:
return numberTypeString;
case Type::String:
case Type::String:
return stringTypeString;
case Type::NumberOrString:
case Type::NumberOrString:
return numberOrStringTypeString;
case Type::Variable:
case Type::Variable:
return variableTypeString;
case Type::Object:
case Type::Object:
return objectTypeString;
case Type::Empty:
case Type::Empty:
return emptyTypeString;
}
return unknownTypeString;
}
return unknownTypeString;
}
ExpressionValidator::Type ExpressionValidator::StringToType(const gd::String &type) {
if (type == ExpressionValidator::numberTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::numberTypeString, type)) {
return Type::Number;
}
if (type == ExpressionValidator::stringTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::stringTypeString, type)) {
return Type::String;
}
if (type == ExpressionValidator::numberOrStringTypeString) {
return Type::NumberOrString;
}
if (type == ExpressionValidator::variableTypeString
|| gd::ParameterMetadata::IsExpression(ExpressionValidator::variableTypeString, type)) {
return Type::Variable;
}
if (type == ExpressionValidator::objectTypeString
|| gd::ParameterMetadata::IsObject(type)) {
return Type::Object;
}
return Type::Unknown;
ExpressionValidator::Type ExpressionValidator::StringToType(
const gd::String& type) {
if (type == ExpressionValidator::numberTypeString ||
gd::ParameterMetadata::IsExpression(ExpressionValidator::numberTypeString,
type)) {
return Type::Number;
}
if (type == ExpressionValidator::stringTypeString ||
gd::ParameterMetadata::IsExpression(ExpressionValidator::stringTypeString,
type)) {
return Type::String;
}
if (type == ExpressionValidator::numberOrStringTypeString) {
return Type::NumberOrString;
}
if (type == ExpressionValidator::variableTypeString ||
gd::ParameterMetadata::IsExpression(
ExpressionValidator::variableTypeString, type)) {
return Type::Variable;
}
if (type == ExpressionValidator::objectTypeString ||
gd::ParameterMetadata::IsObject(type)) {
return Type::Object;
}
return Type::Unknown;
}
} // namespace gd

View File

@@ -13,13 +13,18 @@
#include "GDCore/Tools/MakeUnique.h"
#include "GDCore/Tools/Localization.h"
#include "GDCore/Extensions/Metadata/ExpressionMetadata.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Project/VariablesContainersList.h"
namespace gd {
class Expression;
class ObjectsContainer;
class VariablesContainer;
class Platform;
class ParameterMetadata;
class ExpressionMetadata;
class VariablesContainersList;
class ProjectScopedContainers;
} // namespace gd
namespace gd {
@@ -33,14 +38,13 @@ namespace gd {
class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
public:
ExpressionValidator(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers & projectScopedContainers_,
const gd::String &rootType_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
parentType(StringToType(gd::ParameterMetadata::GetExpressionValueType(rootType_))),
childType(Type::Unknown) {};
childType(Type::Unknown),
forbidsUsageOfBracketsBecauseParentIsObject(false) {};
virtual ~ExpressionValidator(){};
/**
@@ -48,11 +52,10 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
* any error including non-fatal ones.
*/
static bool HasNoErrors(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ProjectScopedContainers & projectScopedContainers,
const gd::String &rootType,
gd::ExpressionNode& node) {
gd::ExpressionValidator validator(platform, globalObjectsContainer, objectsContainer, rootType);
gd::ExpressionValidator validator(platform, projectScopedContainers, rootType);
node.Visit(validator);
return validator.GetAllErrors().empty();
}
@@ -82,7 +85,7 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
void OnVisitOperatorNode(OperatorNode& node) override {
ReportAnyError(node);
node.leftHandSide->Visit(*this);
const Type leftType = childType;
@@ -100,9 +103,9 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
"example: \"Your name: \" + VariableString(PlayerName).", node.rightHandSide->location);
}
else if (node.op != '+') {
RaiseOperatorError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
RaiseOperatorError(
_("You've used an operator that is not supported. Only + can be used "
"to concatenate texts."),
ExpressionParserLocation(node.leftHandSide->location.GetEndPosition() + 1, node.location.GetEndPosition()));
}
} else if (leftType == Type::Object) {
@@ -121,7 +124,11 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
node.rightHandSide->Visit(*this);
const Type rightType = childType;
childType = leftType;
// The type is decided by the first operand, unless it can (`number|string`)
// or should (`unknown`) be refined, in which case we go for the right
// operand (which got visited knowing the type of the first operand, so it's
// equal or strictly more precise than the left operand).
childType = (leftType == Type::Unknown || leftType == Type::NumberOrString) ? leftType : rightType;
}
void OnVisitUnaryOperatorNode(UnaryOperatorNode& node) override {
ReportAnyError(node);
@@ -185,28 +192,69 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
void OnVisitVariableNode(VariableNode& node) override {
ReportAnyError(node);
if (node.child) {
node.child->Visit(*this);
}
childType = Type::Variable;
if (parentType == Type::String) {
RaiseTypeError(_("Variables must be surrounded by VariableString()."),
node.location);
} else if (parentType == Type::Number) {
RaiseTypeError(_("Variables must be surrounded by Variable()."),
node.location);
} else if (parentType == Type::NumberOrString) {
RaiseTypeError(
_("Variables must be surrounded by Variable() or VariableString()."),
node.location);
} else if (parentType != Type::Variable) {
if (parentType == Type::Variable) {
childType = Type::Variable;
if (node.child) {
node.child->Visit(*this);
}
} else if (parentType == Type::String || parentType == Type::Number || parentType == Type::NumberOrString) {
// The node represents a variable or an object variable in an expression waiting for its *value* to be returned.
childType = parentType;
const auto& variablesContainersList = projectScopedContainers.GetVariablesContainersList();
const auto& objectsContainersList = projectScopedContainers.GetObjectsContainersList();
const auto& propertiesContainerList = projectScopedContainers.GetPropertiesContainersList();
forbidsUsageOfBracketsBecauseParentIsObject = false;
projectScopedContainers.MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
// While understood by the parser, it's forbidden to use the bracket notation just after
// an object name (`MyObject["MyVariable"]`).
forbidsUsageOfBracketsBecauseParentIsObject = true;
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
// Being in this node implies that there is at least a child - which is not supported for properties.
RaiseTypeError(_("Accessing a child variable of a property is not possible - just write the property name."),
node.location);
}, [&]() {
// This is a parameter.
// Being in this node implies that there is at least a child - which is not supported for parameters.
RaiseTypeError(_("Accessing a child variable of a parameter is not possible - just write the parameter name."),
node.location);
}, [&]() {
// This is something else.
RaiseTypeError(_("No object, variable or property with this name found."),
node.location);
});
if (node.child) {
node.child->Visit(*this);
}
forbidsUsageOfBracketsBecauseParentIsObject = false;
} else {
RaiseTypeError(_("You entered a variable, but this type was expected:") +
" " + TypeToString(parentType),
node.location);
if (node.child) {
node.child->Visit(*this);
}
}
}
void OnVisitVariableAccessorNode(VariableAccessorNode& node) override {
ReportAnyError(node);
// In the case we accessed an object variable (`MyObject.MyVariable`),
// brackets can now be used (`MyObject.MyVariable["MyChildVariable"]` is now valid).
forbidsUsageOfBracketsBecauseParentIsObject = false;
if (node.child) {
node.child->Visit(*this);
}
@@ -215,6 +263,15 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
VariableBracketAccessorNode& node) override {
ReportAnyError(node);
if (forbidsUsageOfBracketsBecauseParentIsObject) {
RaiseError("brackets_not_allowed_for_objects",
_("You can't use the brackets to access an object variable. "
"Use a dot followed by the variable name, like this: "
"`MyObject.MyVariable`."),
node.location);
}
forbidsUsageOfBracketsBecauseParentIsObject = false;
Type currentParentType = parentType;
parentType = Type::NumberOrString;
node.expression->Visit(*this);
@@ -227,27 +284,39 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
void OnVisitIdentifierNode(IdentifierNode& node) override {
ReportAnyError(node);
if (parentType == Type::String) {
RaiseTypeError(_("You must wrap your text inside double quotes "
"(example: \"Hello world\")."),
node.location);
if (!ValidateObjectVariableOrVariableOrProperty(node)) {
// The identifier is not a variable, so either the variable is not properly declared
// or it's a text without quotes.
RaiseTypeError(_("You must wrap your text inside double quotes "
"(example: \"Hello world\")."),
node.location);
}
}
else if (parentType == Type::Number) {
RaiseTypeError(
_("You must enter a number."), node.location);
if (!ValidateObjectVariableOrVariableOrProperty(node)) {
// The identifier is not a variable, so the variable is not properly declared.
RaiseTypeError(
_("You must enter a number."), node.location);
}
}
else if (parentType == Type::NumberOrString) {
RaiseTypeError(
_("You must enter a number or a text, wrapped inside double quotes "
"(example: \"Hello world\")."),
node.location);
if (!ValidateObjectVariableOrVariableOrProperty(node)) {
// The identifier is not a variable, so either the variable is not properly declared
// or it's a text without quotes.
RaiseTypeError(
_("You must enter a number or a text, wrapped inside double quotes (example: \"Hello world\"), or a variable name."),
node.location);
}
}
else if (parentType != Type::Object && parentType != Type::Variable) {
// It can't happen.
RaiseTypeError(
_("You've entered a name, but this type was expected:") + " " + TypeToString(parentType),
node.location);
childType = parentType;
} else {
childType = parentType;
}
childType = parentType;
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
ReportAnyError(node);
@@ -278,6 +347,7 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
private:
enum Type {Unknown = 0, Number, String, NumberOrString, Variable, Object, Empty};
Type ValidateFunction(const gd::FunctionCallNode& function);
bool ValidateObjectVariableOrVariableOrProperty(const gd::IdentifierNode& identifier);
void ReportAnyError(const ExpressionNode& node, bool isFatal = true) {
if (node.diagnostic && node.diagnostic->IsError()) {
@@ -291,7 +361,7 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
}
}
void RaiseError(const gd::String &type,
void RaiseError(const gd::String &type,
const gd::String &message, const ExpressionParserLocation &location, bool isFatal = true) {
auto diagnostic = gd::make_unique<ExpressionParserError>(
type, message, location);
@@ -315,6 +385,16 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
RaiseError("invalid_operator", message, location);
}
void ReadChildTypeFromVariable(gd::Variable::Type variableType) {
if (variableType == gd::Variable::Number) {
childType = Type::Number;
} else if (variableType == gd::Variable::String) {
childType = Type::String;
} else {
// Nothing - we don't know the precise type (this could be used as a string or as a number).
}
}
static Type StringToType(const gd::String &type);
static const gd::String &TypeToString(Type type);
static const gd::String unknownTypeString;
@@ -329,11 +409,11 @@ class GD_CORE_API ExpressionValidator : public ExpressionParser2NodeWorker {
std::vector<ExpressionParserDiagnostic*> fatalErrors;
std::vector<ExpressionParserDiagnostic*> allErrors;
std::vector<std::unique_ptr<ExpressionParserDiagnostic>> supplementalErrors;
Type childType;
Type parentType;
Type childType; ///< The type "discovered" down the tree and passed up.
Type parentType; ///< The type "required" by the top of the tree.
bool forbidsUsageOfBracketsBecauseParentIsObject;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
};
} // namespace gd

View File

@@ -15,6 +15,7 @@
#include "GDCore/Extensions/Metadata/ObjectMetadata.h"
#include "GDCore/Extensions/Metadata/ParameterMetadata.h"
#include "GDCore/Project/Layout.h" // For GetTypeOfObject and GetTypeOfBehavior
#include "GDCore/Project/ObjectsContainersList.h"
#include "GDCore/Tools/Localization.h"
namespace gd {
@@ -31,6 +32,10 @@ namespace gd {
* \brief Find the object name that should be used as a context of the
* expression or sub-expression that a given node represents.
*
* This is needed because of the legacy convention where a "objectvar"
* parameter represents a variable of the object represented by the previous "object"
* parameter.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWorker {
@@ -41,12 +46,11 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
* context of the expression or sub-expression that a given node represents.
*/
static const gd::String GetObjectName(const gd::Platform &platform,
const gd::ObjectsContainer &globalObjectsContainer,
const gd::ObjectsContainer &objectsContainer,
const gd::ObjectsContainersList &objectsContainersList,
const gd::String& rootObjectName,
gd::ExpressionNode& node) {
gd::ExpressionVariableOwnerFinder typeFinder(
platform, globalObjectsContainer, objectsContainer, rootObjectName);
platform, objectsContainersList, rootObjectName);
node.Visit(typeFinder);
return typeFinder.GetObjectName();
}
@@ -55,12 +59,10 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
protected:
ExpressionVariableOwnerFinder(const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ObjectsContainersList &objectsContainersList_,
const gd::String& rootObjectName_)
: platform(platform_),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
objectsContainersList(objectsContainersList_),
rootObjectName(rootObjectName_),
objectName(""),
variableNode(nullptr) {};
@@ -126,9 +128,8 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
}
const gd::ParameterMetadata* parameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
functionCall,
parameterIndex);
if (parameterMetadata == nullptr
@@ -142,9 +143,8 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
for (int previousIndex = parameterIndex - 1; previousIndex >= 0; previousIndex--) {
const gd::ParameterMetadata* previousParameterMetadata =
MetadataProvider::GetFunctionCallParameterMetadata(
platform,
globalObjectsContainer,
objectsContainer,
platform,
objectsContainersList,
functionCall,
previousIndex);
if (previousParameterMetadata != nullptr
@@ -162,8 +162,7 @@ class GD_CORE_API ExpressionVariableOwnerFinder : public ExpressionParser2NodeWo
gd::ExpressionNode *variableNode;
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ObjectsContainersList &objectsContainersList;
const gd::String &rootObjectName;
};

View File

@@ -31,16 +31,14 @@ namespace gd {
class GD_CORE_API ExpressionParameterMover
: public ExpressionParser2NodeWorker {
public:
ExpressionParameterMover(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
ExpressionParameterMover(const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String& behaviorType_,
const gd::String& objectType_,
const gd::String& functionName_,
std::size_t oldIndex_,
std::size_t newIndex_)
: hasDoneMoving(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
behaviorType(behaviorType_),
objectType(objectType_),
functionName(functionName_),
@@ -98,16 +96,16 @@ class GD_CORE_API ExpressionParameterMover
// This refactor only applies on events object functions
// and events object functions doesn't exist yet.
// This is a dead code.
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
const gd::String& thisObjectType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfObject(node.objectName);
if (thisObjectType == objectType) {
moveParameter(node.parameters, 1);
hasDoneMoving = true;
}
} else if (!behaviorType.empty() && !node.behaviorName.empty()) {
// Move parameter of a behavior function
const gd::String& thisBehaviorType = gd::GetTypeOfBehavior(
globalObjectsContainer, objectsContainer, node.behaviorName);
const gd::String& thisBehaviorType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfBehavior(node.behaviorName);
if (thisBehaviorType == behaviorType) {
moveParameter(node.parameters, 2);
hasDoneMoving = true;
@@ -126,8 +124,7 @@ class GD_CORE_API ExpressionParameterMover
private:
bool hasDoneMoving;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::String& behaviorType; // The behavior type of the function which
// must have a parameter moved (optional).
const gd::String& objectType; // The object type of the function which
@@ -155,8 +152,7 @@ bool ExpressionsParameterMover::DoVisitInstruction(gd::Instruction& instruction,
auto node = expression.GetRootNode();
if (node) {
ExpressionParameterMover mover(GetGlobalObjectsContainer(),
GetObjectsContainer(),
ExpressionParameterMover mover(GetProjectScopedContainers(),
behaviorType,
objectType,
functionName,

View File

@@ -23,22 +23,20 @@
namespace gd {
/**
* \brief Go through the nodes and change the given object name to a new one.
* \brief Go through the nodes and change the given function name to a new name.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionFunctionRenamer
: public ExpressionParser2NodeWorker {
public:
ExpressionFunctionRenamer(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
ExpressionFunctionRenamer(const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String& behaviorType_,
const gd::String& objectType_,
const gd::String& oldFunctionName_,
const gd::String& newFunctionName_)
: hasDoneRenaming(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
projectScopedContainers(projectScopedContainers_),
behaviorType(behaviorType_),
objectType(objectType_),
oldFunctionName(oldFunctionName_),
@@ -71,16 +69,18 @@ class GD_CORE_API ExpressionFunctionRenamer
node.expression->Visit(*this);
if (node.child) node.child->Visit(*this);
}
void OnVisitIdentifierNode(IdentifierNode& node) override {}
void OnVisitIdentifierNode(IdentifierNode& node) override {
// Nothing to do as this is either a variable, an object variable a property or something else
// but not an expression.
}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.behaviorFunctionName.empty()) {
// Behavior function name
if (!behaviorType.empty() &&
node.behaviorFunctionName == oldFunctionName) {
const gd::String& thisBehaviorType =
gd::GetTypeOfBehavior(globalObjectsContainer,
objectsContainer,
node.objectFunctionOrBehaviorName);
const gd::String& thisBehaviorType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfBehavior(
node.objectFunctionOrBehaviorName);
if (thisBehaviorType == behaviorType) {
node.behaviorFunctionName = newFunctionName;
hasDoneRenaming = true;
@@ -90,8 +90,8 @@ class GD_CORE_API ExpressionFunctionRenamer
// Object function name
if (behaviorType.empty() && !objectType.empty() &&
node.objectFunctionOrBehaviorName == oldFunctionName) {
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
const gd::String& thisObjectType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfObject(node.objectName);
if (thisObjectType == objectType) {
node.objectFunctionOrBehaviorName = newFunctionName;
hasDoneRenaming = true;
@@ -104,16 +104,16 @@ class GD_CORE_API ExpressionFunctionRenamer
if (behaviorType.empty() && !objectType.empty() &&
!node.objectName.empty()) {
// Replace an object function
const gd::String& thisObjectType = gd::GetTypeOfObject(
globalObjectsContainer, objectsContainer, node.objectName);
const gd::String& thisObjectType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfObject(node.objectName);
if (thisObjectType == objectType) {
node.functionName = newFunctionName;
hasDoneRenaming = true;
}
} else if (!behaviorType.empty() && !node.behaviorName.empty()) {
// Replace a behavior function
const gd::String& thisBehaviorType = gd::GetTypeOfBehavior(
globalObjectsContainer, objectsContainer, node.behaviorName);
const gd::String& thisBehaviorType = projectScopedContainers
.GetObjectsContainersList().GetTypeOfBehavior(node.behaviorName);
if (thisBehaviorType == behaviorType) {
node.functionName = newFunctionName;
hasDoneRenaming = true;
@@ -132,8 +132,7 @@ class GD_CORE_API ExpressionFunctionRenamer
private:
bool hasDoneRenaming;
const gd::ObjectsContainer& globalObjectsContainer;
const gd::ObjectsContainer& objectsContainer;
const gd::ProjectScopedContainers& projectScopedContainers;
const gd::String& behaviorType; // The behavior type for which the expression
// must be replaced (optional).
const gd::String& objectType; // The object type for which the expression
@@ -159,8 +158,7 @@ bool ExpressionsRenamer::DoVisitInstruction(gd::Instruction& instruction,
auto node = expression.GetRootNode();
if (node) {
ExpressionFunctionRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),
ExpressionFunctionRenamer renamer(GetProjectScopedContainers(),
behaviorType,
objectType,
oldFunctionName,

View File

@@ -34,13 +34,12 @@ class GD_CORE_API ExpressionIdentifierStringFinder
public:
ExpressionIdentifierStringFinder(
const gd::Platform &platform_,
const gd::ObjectsContainer &globalObjectsContainer_,
const gd::ObjectsContainer &objectsContainer_,
const gd::ProjectScopedContainers& projectScopedContainers_,
const gd::String &expressionPlainString_,
const gd::String &parameterType_, const gd::String &objectName_,
const gd::String &layerName_, const gd::String &oldName_)
: platform(platform_), globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
: platform(platform_),
projectScopedContainers(projectScopedContainers_),
expressionPlainString(expressionPlainString_),
parameterType(parameterType_), objectName(objectName_),
layerName(layerName_), oldName(oldName_){};
@@ -82,7 +81,7 @@ protected:
void OnVisitFunctionCallNode(FunctionCallNode &node) override {
gd::String lastLayerName;
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
platform, globalObjectsContainer, objectsContainer, node,
platform, projectScopedContainers.GetObjectsContainersList(), node,
[&](const gd::ParameterMetadata &parameterMetadata,
std::unique_ptr<gd::ExpressionNode> &parameterNode,
size_t parameterIndex, const gd::String &lastObjectName) {
@@ -123,8 +122,7 @@ protected:
private:
const gd::Platform &platform;
const gd::ObjectsContainer &globalObjectsContainer;
const gd::ObjectsContainer &objectsContainer;
const gd::ProjectScopedContainers &projectScopedContainers;
/// It's used to extract parameter content.
const gd::String &expressionPlainString;
const gd::String &oldName;
@@ -176,7 +174,7 @@ bool ProjectElementRenamer::DoVisitInstruction(gd::Instruction &instruction,
auto node = parameterValue.GetRootNode();
if (node) {
ExpressionIdentifierStringFinder finder(
platform, GetGlobalObjectsContainer(), GetObjectsContainer(),
platform, GetProjectScopedContainers(),
parameterValue.GetPlainString(), parameterType, objectName,
layerName, oldName);
node->Visit(finder);

View File

@@ -108,6 +108,33 @@ void UsedExtensionsFinder::OnVisitUnaryOperatorNode(UnaryOperatorNode& node) {
// Add variable extension and visit sub-expressions on variable nodes
void UsedExtensionsFinder::OnVisitVariableNode(VariableNode& node) {
result.GetUsedExtensions().insert("BuiltinVariables");
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetProjectScopedContainers(), rootType, node);
if (gd::ParameterMetadata::IsExpression("variable", type)) {
// Nothing to do (this can't reference an object)
} else {
GetProjectScopedContainers().MatchIdentifierWithName<void>(node.name,
[&]() {
// This represents an object.
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
project.GetCurrentPlatform(), node.name);
result.GetUsedExtensions().insert(metadata.GetExtension().GetName());
for (auto &&includeFile : metadata.GetMetadata().includeFiles) {
result.GetUsedIncludeFiles().insert(includeFile);
}
}, [&]() {
// This is a variable.
}, [&]() {
// This is a property.
}, [&]() {
// This is a parameter.
}, [&]() {
// This is something else.
});
}
if (node.child) node.child->Visit(*this);
};
@@ -127,9 +154,10 @@ void UsedExtensionsFinder::OnVisitVariableBracketAccessorNode(
// Add extensions bound to Objects/Behaviors/Functions
void UsedExtensionsFinder::OnVisitIdentifierNode(IdentifierNode &node) {
auto type = gd::ExpressionTypeFinder::GetType(
project.GetCurrentPlatform(), GetGlobalObjectsContainer(),
GetObjectsContainer(), rootType, node);
if (gd::ParameterMetadata::IsObject(type)) {
project.GetCurrentPlatform(), GetProjectScopedContainers(), rootType, node);
if (gd::ParameterMetadata::IsObject(type) ||
GetObjectsContainersList().HasObjectOrGroupNamed(node.identifierName)) {
// An object or object variable is used.
auto metadata = gd::MetadataProvider::GetExtensionAndObjectMetadata(
project.GetCurrentPlatform(), node.identifierName);
result.GetUsedExtensions().insert(metadata.GetExtension().GetName());

View File

@@ -293,8 +293,8 @@ void ResourceWorkerInObjectsWorker::DoVisitBehavior(gd::Behavior &behavior){
gd::ResourceWorkerInObjectsWorker
GetResourceWorkerOnObjects(const gd::Project &project,
gd::ArbitraryResourceWorker &worker) {
gd::ResourceWorkerInObjectsWorker eventsWorker(project, worker);
return eventsWorker;
gd::ResourceWorkerInObjectsWorker resourcesWorker(project, worker);
return resourcesWorker;
}
} // namespace gd

View File

@@ -0,0 +1,19 @@
#include "ObjectsUsingResourceCollector.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
namespace gd {
void ObjectsUsingResourceCollector::DoVisitObject(gd::Object& object) {
gd::ResourceNameMatcher resourceNameMatcher(resourceName);
object.GetConfiguration().ExposeResources(resourceNameMatcher);
if (resourceNameMatcher.AnyResourceMatches()) {
objectNames.push_back(object.GetName());
}
};
ObjectsUsingResourceCollector::~ObjectsUsingResourceCollector() {}
} // namespace gd

View File

@@ -0,0 +1,89 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef ProjectObjectsUsingResourceCollector_H
#define ProjectObjectsUsingResourceCollector_H
#include <vector>
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/String.h"
namespace gd {
class Object;
} // namespace gd
namespace gd {
class GD_CORE_API ObjectsUsingResourceCollector
: public ArbitraryObjectsWorker {
public:
ObjectsUsingResourceCollector(const gd::String& resourceName_)
: resourceName(resourceName_){};
virtual ~ObjectsUsingResourceCollector();
std::vector<gd::String>& GetObjectNames() { return objectNames; }
private:
void DoVisitObject(gd::Object& object) override;
std::vector<gd::String> objectNames;
gd::String resourceName;
};
class GD_CORE_API ResourceNameMatcher : public ArbitraryResourceWorker {
public:
ResourceNameMatcher(const gd::String& resourceName_)
: resourceName(resourceName_), matchesResourceName(false){};
virtual ~ResourceNameMatcher(){};
bool AnyResourceMatches() { return matchesResourceName; }
void Reset() { matchesResourceName = false; }
private:
virtual void ExposeFile(gd::String& resource) override{
/*Don't care, we just read resource names*/
};
virtual void ExposeImage(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeAudio(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeFont(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeJson(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeTilemap(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeTileset(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeVideo(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeBitmapFont(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
virtual void ExposeModel3D(gd::String& otherResourceName) override {
MatchResourceName(otherResourceName);
};
void MatchResourceName(gd::String& otherResourceName) {
if (otherResourceName == resourceName) matchesResourceName = true;
}
gd::String resourceName;
bool matchesResourceName;
};
}; // namespace gd
#endif // ProjectObjectsUsingResourceCollector_H

View File

@@ -16,7 +16,9 @@
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/String.h"
namespace gd {
@@ -86,15 +88,17 @@ void ProjectBrowserHelper::ExposeLayoutEvents(
void ProjectBrowserHelper::ExposeLayoutEvents(
gd::Project &project, gd::Layout &layout,
gd::ArbitraryEventsWorkerWithContext &worker) {
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Add layouts events
worker.Launch(layout.GetEvents(), project, layout);
worker.Launch(layout.GetEvents(), projectScopedContainers);
// Add external events events
for (std::size_t s = 0; s < project.GetExternalEventsCount(); s++) {
auto &externalEvents = project.GetExternalEvents(s);
if (externalEvents.GetAssociatedLayout() == layout.GetName()) {
worker.Launch(externalEvents.GetEvents(), project, layout);
worker.Launch(externalEvents.GetEvents(), projectScopedContainers);
}
}
}
@@ -108,15 +112,19 @@ void ProjectBrowserHelper::ExposeProjectEvents(
// Add layouts events
for (std::size_t s = 0; s < project.GetLayoutsCount(); s++) {
auto &layout = project.GetLayout(s);
worker.Launch(layout.GetEvents(), project, layout);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
worker.Launch(layout.GetEvents(), projectScopedContainers);
}
// Add external events events
for (std::size_t s = 0; s < project.GetExternalEventsCount(); s++) {
const auto &externalEvents = project.GetExternalEvents(s);
const gd::String &associatedLayout = externalEvents.GetAssociatedLayout();
if (project.HasLayoutNamed(associatedLayout)) {
worker.Launch(project.GetExternalEvents(s).GetEvents(), project,
project.GetLayout(associatedLayout));
auto &layout = project.GetLayout(associatedLayout);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
worker.Launch(project.GetExternalEvents(s).GetEvents(), projectScopedContainers);
}
}
// Add events based extensions
@@ -130,9 +138,11 @@ void ProjectBrowserHelper::ExposeProjectEvents(
gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
project, eventsFunctionsExtension, *eventsFunction,
globalObjectsAndGroups, objectsAndGroups);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddParameters(eventsFunction->GetParametersForEvents(eventsFunctionsExtension));
worker.Launch(eventsFunction->GetEvents(), globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
// Add (behavior) events functions
@@ -169,9 +179,13 @@ void ProjectBrowserHelper::ExposeEventsBasedBehaviorEvents(
gd::EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer(
project, eventsBasedBehavior, *eventsFunction, globalObjectsAndGroups,
objectsAndGroups);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddPropertiesContainer(eventsBasedBehavior.GetSharedPropertyDescriptors());
projectScopedContainers.AddPropertiesContainer(eventsBasedBehavior.GetPropertyDescriptors());
projectScopedContainers.AddParameters(eventsFunction->GetParametersForEvents(eventsBasedBehavior.GetEventsFunctions()));
worker.Launch(eventsFunction->GetEvents(), globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
}
@@ -185,9 +199,12 @@ void ProjectBrowserHelper::ExposeEventsBasedObjectEvents(
gd::EventsFunctionTools::ObjectEventsFunctionToObjectsContainer(
project, eventsBasedObject, *eventsFunction, globalObjectsAndGroups,
objectsAndGroups);
auto projectScopedContainers =
gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsAndGroups, objectsAndGroups);
projectScopedContainers.AddPropertiesContainer(eventsBasedObject.GetPropertyDescriptors());
projectScopedContainers.AddParameters(eventsFunction->GetParametersForEvents(eventsBasedObject.GetEventsFunctions()));
worker.Launch(eventsFunction->GetEvents(), globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(), projectScopedContainers);
}
}

View File

@@ -5,6 +5,8 @@
*/
#include "WholeProjectRefactorer.h"
#include <unordered_map>
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
@@ -15,6 +17,8 @@
#include "GDCore/IDE/Events/CustomObjectTypeRenamer.h"
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
#include "GDCore/IDE/Events/EventsRefactorer.h"
#include "GDCore/IDE/Events/EventsPropertyReplacer.h"
#include "GDCore/IDE/Events/EventsVariableReplacer.h"
#include "GDCore/IDE/Events/ExpressionsParameterMover.h"
#include "GDCore/IDE/Events/ExpressionsRenamer.h"
#include "GDCore/IDE/Events/InstructionsParameterMover.h"
@@ -46,6 +50,8 @@
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/ObjectGroup.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/ProjectScopedContainers.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
@@ -132,6 +138,72 @@ void WholeProjectRefactorer::EnsureObjectEventsFunctionsProperParameters(
}
}
VariablesChangeset WholeProjectRefactorer::ComputeChangesetForVariablesContainer(
gd::Project &project,
const gd::SerializerElement &oldSerializedVariablesContainer,
const gd::VariablesContainer &newVariablesContainer) {
gd::VariablesChangeset changeset;
gd::VariablesContainer oldVariablesContainer;
oldVariablesContainer.UnserializeFrom(oldSerializedVariablesContainer);
if (oldVariablesContainer.GetPersistentUuid() !=
newVariablesContainer.GetPersistentUuid()) {
gd::LogWarning(
_("Called ComputeChangesetForVariablesContainer on variables containers "
"that are different - they can't be compared."));
return changeset;
}
std::unordered_map<gd::String, gd::String> removedUuidAndNames;
for (std::size_t i = 0; i < oldVariablesContainer.Count(); ++i) {
const auto &variable = oldVariablesContainer.Get(i);
const auto &variableName = oldVariablesContainer.GetNameAt(i);
// All variables are candidate to be removed.
removedUuidAndNames[variable.GetPersistentUuid()] = variableName;
}
for (std::size_t i = 0; i < newVariablesContainer.Count(); ++i) {
const auto &variable = newVariablesContainer.Get(i);
const auto &variableName = newVariablesContainer.GetNameAt(i);
auto existingOldVariableUuidAndName =
removedUuidAndNames.find(variable.GetPersistentUuid());
if (existingOldVariableUuidAndName == removedUuidAndNames.end()) {
// This is a new variable.
} else {
const gd::String &oldName = existingOldVariableUuidAndName->second;
if (oldName != variableName) {
// This is a renamed variable.
changeset.oldToNewVariableNames[oldName] = variableName;
}
// Renamed or not, this is not a removed variable.
removedUuidAndNames.erase(variable.GetPersistentUuid());
}
}
for (const auto &removedUuidAndName : removedUuidAndNames) {
changeset.removedVariableNames.insert(removedUuidAndName.second);
}
return changeset;
}
void WholeProjectRefactorer::ApplyRefactoringForVariablesContainer(
gd::Project &project,
const gd::VariablesContainer &newVariablesContainer,
const gd::VariablesChangeset& changeset) {
gd::EventsVariableReplacer eventsVariableReplacer(
project.GetCurrentPlatform(),
newVariablesContainer,
changeset.oldToNewVariableNames,
changeset.removedVariableNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project,
eventsVariableReplacer);
}
void WholeProjectRefactorer::UpdateExtensionNameInEventsBasedBehavior(
gd::Project &project,
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
@@ -671,6 +743,15 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
@@ -698,7 +779,7 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
const gd::EventsFunctionsExtension &eventsFunctionsExtension,
const gd::EventsBasedBehavior &eventsBasedBehavior,
const gd::String &oldPropertyName, const gd::String &newPropertyName) {
auto &properties = eventsBasedBehavior.GetPropertyDescriptors();
auto &properties = eventsBasedBehavior.GetSharedPropertyDescriptors();
if (!properties.Has(oldPropertyName))
return;
@@ -732,6 +813,15 @@ void WholeProjectRefactorer::RenameEventsBasedBehaviorSharedProperty(
EventsBasedBehavior::GetSharedPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
gd::PlatformExtension::GetBehaviorEventsFunctionFullType(
@@ -780,6 +870,15 @@ void WholeProjectRefactorer::RenameEventsBasedObjectProperty(
EventsBasedObject::GetPropertyExpressionName(newPropertyName));
gd::ProjectBrowserHelper::ExposeProjectEvents(project, expressionRenamer);
std::unordered_map<gd::String, gd::String> oldToNewPropertyNames = {{oldPropertyName, newPropertyName}};
std::unordered_set<gd::String> removedPropertyNames;
gd::EventsPropertyReplacer eventsPropertyReplacer(
project.GetCurrentPlatform(),
properties,
oldToNewPropertyNames,
removedPropertyNames);
gd::ProjectBrowserHelper::ExposeProjectEvents(project, eventsPropertyReplacer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
gd::PlatformExtension::GetObjectEventsFunctionFullType(
@@ -1299,10 +1398,12 @@ void WholeProjectRefactorer::DoRenameObject(
void WholeProjectRefactorer::ObjectOrGroupRemovedInLayout(
gd::Project &project, gd::Layout &layout, const gd::String &objectName,
bool isObjectGroup, bool removeEventsAndGroups) {
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Remove object in the current layout
if (removeEventsAndGroups) {
gd::EventsRefactorer::RemoveObjectInEvents(project.GetCurrentPlatform(),
project, layout,
projectScopedContainers,
layout.GetEvents(), objectName);
}
if (!isObjectGroup) { // Object groups can't have instances or be in other
@@ -1321,8 +1422,9 @@ void WholeProjectRefactorer::ObjectOrGroupRemovedInLayout(
for (auto &externalEventsName :
GetAssociatedExternalEvents(project, layout.GetName())) {
auto &externalEvents = project.GetExternalEvents(externalEventsName);
gd::EventsRefactorer::RemoveObjectInEvents(
project.GetCurrentPlatform(), project, layout,
project.GetCurrentPlatform(), projectScopedContainers,
externalEvents.GetEvents(), objectName);
}
}
@@ -1344,9 +1446,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
const gd::String &newName, bool isObjectGroup) {
if (oldName == newName || newName.empty() || oldName.empty())
return;
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersForProjectAndLayout(project, layout);
// Rename object in the current layout
gd::EventsRefactorer::RenameObjectInEvents(
project.GetCurrentPlatform(), project, layout, layout.GetEvents(),
project.GetCurrentPlatform(), projectScopedContainers, layout.GetEvents(),
oldName, newName);
if (!isObjectGroup) { // Object groups can't have instances or be in other
@@ -1362,7 +1467,7 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInLayout(
GetAssociatedExternalEvents(project, layout.GetName())) {
auto &externalEvents = project.GetExternalEvents(externalEventsName);
gd::EventsRefactorer::RenameObjectInEvents(
project.GetCurrentPlatform(), project, layout,
project.GetCurrentPlatform(), projectScopedContainers,
externalEvents.GetEvents(), oldName, newName);
}
@@ -1512,10 +1617,13 @@ void WholeProjectRefactorer::ObjectOrGroupRemovedInEventsFunction(
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer, const gd::String &objectName,
bool isObjectGroup, bool removeEventsAndGroups) {
// Remove object in the current layout
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
// In practice, this is ok because we only deal with objects.
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
if (removeEventsAndGroups) {
gd::EventsRefactorer::RemoveObjectInEvents(
project.GetCurrentPlatform(), globalObjectsContainer, objectsContainer,
project.GetCurrentPlatform(), projectScopedContainers,
eventsFunction.GetEvents(), objectName);
}
if (!isObjectGroup) { // Object groups can't be in other groups
@@ -1547,9 +1655,12 @@ void WholeProjectRefactorer::ObjectOrGroupRenamedInEventsFunction(
gd::ObjectsContainer &globalObjectsContainer,
gd::ObjectsContainer &objectsContainer, const gd::String &oldName,
const gd::String &newName, bool isObjectGroup) {
// Rename object in the current layout
// In theory we should pass a ProjectScopedContainers to this function so it does not have to construct one.
// In practice, this is ok because we only deal with objects.
auto projectScopedContainers = gd::ProjectScopedContainers::MakeNewProjectScopedContainersFor(globalObjectsContainer, objectsContainer);
gd::EventsRefactorer::RenameObjectInEvents(
project.GetCurrentPlatform(), globalObjectsContainer, objectsContainer,
project.GetCurrentPlatform(), projectScopedContainers,
eventsFunction.GetEvents(), oldName, newName);
if (!isObjectGroup) { // Object groups can't be in other groups
@@ -1594,7 +1705,7 @@ void WholeProjectRefactorer::GlobalObjectOrGroupRemoved(
if (layout.HasObjectNamed(objectName))
continue;
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
ObjectOrGroupRemovedInLayout(project, layout, objectName, isObjectGroup,
removeEventsAndGroups);
}
}

View File

@@ -7,7 +7,9 @@
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <vector>
#include "GDCore/String.h"
namespace gd {
class Platform;
class Project;
@@ -18,6 +20,7 @@ class String;
class EventsFunctionsExtension;
class EventsFunction;
class ObjectsContainer;
class VariablesContainer;
class EventsBasedBehavior;
class EventsBasedObject;
class ArbitraryEventsWorker;
@@ -30,10 +33,20 @@ class Behavior;
class BehaviorMetadata;
class UnfilledRequiredBehaviorPropertyProblem;
class ProjectBrowser;
class SerializerElement;
} // namespace gd
namespace gd {
struct VariablesChangeset {
std::unordered_set<gd::String> removedVariableNames;
std::unordered_map<gd::String, gd::String> oldToNewVariableNames;
bool HasRemovedVariables() { return !removedVariableNames.empty(); }
VariablesChangeset& ClearRemovedVariables() { removedVariableNames.clear(); return *this; }
};
/**
* \brief Tool functions to do refactoring on the whole project after
* changes like deletion or renaming of an object.
@@ -45,6 +58,23 @@ namespace gd {
class GD_CORE_API WholeProjectRefactorer {
public:
/**
* \brief Compute the changes made on the variables of a variable container.
*/
static VariablesChangeset ComputeChangesetForVariablesContainer(
gd::Project &project,
const gd::SerializerElement &oldSerializedVariablesContainer,
const gd::VariablesContainer &newVariablesContainer);
/**
* \brief Refactor the project according to the changes (renaming or deletion)
* made to variables.
*/
static void ApplyRefactoringForVariablesContainer(
gd::Project &project,
const gd::VariablesContainer &newVariablesContainer,
const gd::VariablesChangeset& changeset);
/**
* \brief Refactor the project **before** an events function extension is
* renamed.

View File

@@ -4,6 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#include "AbstractEventsBasedEntity.h"
#include "EventsFunctionsContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/MakeUnique.h"
@@ -13,7 +14,10 @@ namespace gd {
AbstractEventsBasedEntity::AbstractEventsBasedEntity(
const gd::String& _name,
gd::EventsFunctionsContainer::FunctionOwner functionContainerSource)
: name(_name), fullName(""), eventsFunctionsContainer(functionContainerSource) {}
: name(_name),
fullName(""),
eventsFunctionsContainer(functionContainerSource),
propertyDescriptors(functionContainerSource) {}
void AbstractEventsBasedEntity::SerializeTo(SerializerElement& element) const {
element.SetAttribute("description", description);
@@ -27,8 +31,8 @@ void AbstractEventsBasedEntity::SerializeTo(SerializerElement& element) const {
"propertyDescriptor", element.AddChild("propertyDescriptors"));
}
void AbstractEventsBasedEntity::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
void AbstractEventsBasedEntity::UnserializeFrom(
gd::Project& project, const SerializerElement& element) {
description = element.GetStringAttribute("description");
name = element.GetStringAttribute("name");
fullName = element.GetStringAttribute("fullName");

View File

@@ -8,7 +8,7 @@
#include <vector>
#include "GDCore/Project/NamedPropertyDescriptor.h"
#include "GDCore/Tools/SerializableWithNameList.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/String.h"
namespace gd {
@@ -98,15 +98,14 @@ class GD_CORE_API AbstractEventsBasedEntity {
/**
* \brief Return a reference to the list of the properties.
*/
SerializableWithNameList<NamedPropertyDescriptor>& GetPropertyDescriptors() {
gd::PropertiesContainer& GetPropertyDescriptors() {
return propertyDescriptors;
}
/**
* \brief Return a const reference to the list of the properties.
*/
const SerializableWithNameList<NamedPropertyDescriptor>& GetPropertyDescriptors()
const {
const gd::PropertiesContainer& GetPropertyDescriptors() const {
return propertyDescriptors;
}
@@ -150,7 +149,7 @@ class GD_CORE_API AbstractEventsBasedEntity {
gd::String fullName;
gd::String description;
gd::EventsFunctionsContainer eventsFunctionsContainer;
SerializableWithNameList<NamedPropertyDescriptor> propertyDescriptors;
gd::PropertiesContainer propertyDescriptors;
gd::String extensionName;
};

View File

@@ -31,10 +31,10 @@ namespace gd {
*/
class GD_CORE_API BehaviorConfigurationContainer {
public:
BehaviorConfigurationContainer(){};
BehaviorConfigurationContainer(const gd::String& name_, const gd::String& type_)
: name(name_), type(type_){};
BehaviorConfigurationContainer() : folded(false){};
BehaviorConfigurationContainer(const gd::String& name_,
const gd::String& type_)
: name(name_), type(type_), folded(false){};
virtual ~BehaviorConfigurationContainer();
virtual BehaviorConfigurationContainer* Clone() const { return new BehaviorConfigurationContainer(*this); }
@@ -61,7 +61,7 @@ class GD_CORE_API BehaviorConfigurationContainer {
/**
* \brief Called when the IDE wants to know about the custom properties of the
* behavior.
*
*
* \return a std::map with properties names as key.
* \see gd::PropertyDescriptor
*/
@@ -104,6 +104,17 @@ class GD_CORE_API BehaviorConfigurationContainer {
const gd::SerializerElement& GetContent() const { return content; };
gd::SerializerElement& GetContent() { return content; };
/**
* \brief Set if the behavior configuration panel should be folded in the UI.
*/
void SetFolded(bool fold = true) { folded = fold; }
/**
* \brief True if the behavior configuration panel should be folded in the UI.
*/
bool IsFolded() const { return folded; }
protected:
/**
* \brief Called when the IDE wants to know about the custom properties of the
@@ -148,6 +159,7 @@ protected:
///< in the form "ExtensionName::BehaviorTypeName"
gd::SerializerElement content; // Storage for the behavior properties
bool folded;
};
} // namespace gd

View File

@@ -8,6 +8,7 @@
#include "GDCore/IDE/Project/ArbitraryResourceWorker.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -17,7 +18,7 @@
using namespace gd;
void CustomConfigurationHelper::InitializeContent(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &configurationContent) {
for (auto &&property : properties.GetInternalVector()) {
auto &element = configurationContent.AddChild(property->GetName());
@@ -35,7 +36,7 @@ void CustomConfigurationHelper::InitializeContent(
}
std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetProperties(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
const gd::SerializerElement &configurationContent) {
auto behaviorProperties = std::map<gd::String, gd::PropertyDescriptor>();
@@ -71,7 +72,7 @@ std::map<gd::String, gd::PropertyDescriptor> CustomConfigurationHelper::GetPrope
}
bool CustomConfigurationHelper::UpdateProperty(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &configurationContent,
const gd::String &propertyName,
const gd::String &newValue) {

View File

@@ -9,6 +9,7 @@
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Serialization/Serializer.h"
#include "GDCore/Serialization/SerializerElement.h"
@@ -25,15 +26,15 @@ public:
CustomConfigurationHelper() {}
static void InitializeContent(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &behaviorContent);
static std::map<gd::String, gd::PropertyDescriptor> GetProperties(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
const gd::SerializerElement &behaviorContent);
static bool UpdateProperty(
const gd::SerializableWithNameList<gd::NamedPropertyDescriptor> &properties,
const gd::PropertiesContainer &properties,
gd::SerializerElement &behaviorContent,
const gd::String &name,
const gd::String &value);

View File

@@ -13,7 +13,8 @@ namespace gd {
EventsBasedBehavior::EventsBasedBehavior()
: AbstractEventsBasedEntity(
"MyBehavior",
gd::EventsFunctionsContainer::FunctionOwner::Behavior) {}
gd::EventsFunctionsContainer::FunctionOwner::Behavior),
sharedPropertyDescriptors(gd::EventsFunctionsContainer::FunctionOwner::Behavior) {}
void EventsBasedBehavior::SerializeTo(SerializerElement& element) const {
AbstractEventsBasedEntity::SerializeTo(element);

View File

@@ -9,7 +9,7 @@
#include <vector>
#include "GDCore/Project/AbstractEventsBasedEntity.h"
#include "GDCore/Project/NamedPropertyDescriptor.h"
#include "GDCore/Tools/SerializableWithNameList.h"
#include "GDCore/Project/PropertiesContainer.h"
#include "GDCore/Project/EventsFunctionsContainer.h"
#include "GDCore/String.h"
namespace gd {
@@ -91,14 +91,14 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
/**
* \brief Return a reference to the list of shared properties.
*/
SerializableWithNameList<NamedPropertyDescriptor>& GetSharedPropertyDescriptors() {
gd::PropertiesContainer& GetSharedPropertyDescriptors() {
return sharedPropertyDescriptors;
}
/**
* \brief Return a const reference to the list of shared properties.
*/
const SerializableWithNameList<NamedPropertyDescriptor>& GetSharedPropertyDescriptors()
const gd::PropertiesContainer& GetSharedPropertyDescriptors()
const {
return sharedPropertyDescriptors;
}
@@ -140,7 +140,7 @@ class GD_CORE_API EventsBasedBehavior: public AbstractEventsBasedEntity {
private:
gd::String objectType;
bool isPrivate = false;
SerializableWithNameList<NamedPropertyDescriptor> sharedPropertyDescriptors;
gd::PropertiesContainer sharedPropertyDescriptors;
};
} // namespace gd

View File

@@ -17,7 +17,7 @@ EventsBasedObject::EventsBasedObject()
}
EventsBasedObject::~EventsBasedObject() {}
EventsBasedObject::EventsBasedObject(const gd::EventsBasedObject &_eventBasedObject)
: AbstractEventsBasedEntity(_eventBasedObject) {
// TODO Add a copy constructor in ObjectsContainer.
@@ -30,14 +30,19 @@ void EventsBasedObject::SerializeTo(SerializerElement& element) const {
AbstractEventsBasedEntity::SerializeTo(element);
SerializeObjectsTo(element.AddChild("objects"));
SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
}
void EventsBasedObject::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
const SerializerElement& element) {
defaultName = element.GetStringAttribute("defaultName");
AbstractEventsBasedEntity::UnserializeFrom(project, element);
UnserializeObjectsFrom(project, element.GetChild("objects"));
if (element.HasChild("objectsFolderStructure")) {
UnserializeFoldersFrom(project, element.GetChild("objectsFolderStructure", 0));
}
AddMissingObjectsInRootFolder();
}
} // namespace gd

View File

@@ -19,7 +19,7 @@ Layer::Layer()
isLocked(false),
isLightingLayer(false),
followBaseLayerCamera(false),
camera3DNearPlaneDistance(0.1),
camera3DNearPlaneDistance(3),
camera3DFarPlaneDistance(10000),
camera3DFieldOfView(45),
ambientLightColorR(200),

View File

@@ -13,19 +13,75 @@
namespace gd {
class Effect;
}
namespace gd {
class Camera;
}
namespace gd {
class SerializerElement;
}
namespace gd {
class EffectsContainer;
}
namespace gd {
/**
* \brief A camera is used to render a specific area of a layout.
*
* \see gd::Layout
* \ingroup PlatformDefinition
*/
class GD_CORE_API Camera {
public:
Camera();
~Camera(){};
/**
* \brief Change the viewport, i.e the area of the window where the camera
* will be displayed. \note The coordinates must be between 0 and 1.
*/
void SetViewport(float x1_, float y1_, float x2_, float y2_) {
x1 = x1_;
x2 = x2_;
y1 = y1_;
y2 = y2_;
};
void SetViewportX1(float x1_) { x1 = x1_; };
void SetViewportY1(float y1_) { y1 = y1_; };
void SetViewportX2(float x2_) { x2 = x2_; };
void SetViewportY2(float y2_) { y2 = y2_; };
float GetViewportX1() const { return x1; };
float GetViewportY1() const { return y1; };
float GetViewportX2() const { return x2; };
float GetViewportY2() const { return y2; };
/**
* \brief Change the size of the rendered area of the scene, in pixels.
*/
void SetSize(float width_, float height_) {
width = width_;
height = height_;
};
float GetWidth() const { return width; };
float GetHeight() const { return height; };
void SetUseDefaultSize(bool useDefaultSize = true) {
defaultSize = useDefaultSize;
};
bool UseDefaultSize() const { return defaultSize; }
void SetUseDefaultViewport(bool useDefaultViewport = true) {
defaultViewport = useDefaultViewport;
};
bool UseDefaultViewport() const { return defaultViewport; }
private:
bool defaultSize; ///< True if the camera use the default window size
bool defaultViewport; ///< True if the camera use the default viewport size
float x1;
float y1;
float x2;
float y2;
float width; ///< The width of the rendered area
float height; ///< The height of the rendered area
};
/**
* \brief Represents a layer of a layout.
*
@@ -241,68 +297,6 @@ struct LayerHasName : public std::binary_function<gd::Layer, gd::String, bool> {
}
};
/**
* \brief A camera is used to render a specific area of a layout.
*
* \see gd::Layout
* \ingroup PlatformDefinition
*/
class GD_CORE_API Camera {
public:
Camera();
~Camera(){};
/**
* \brief Change the viewport, i.e the area of the window where the camera
* will be displayed. \note The coordinates must be between 0 and 1.
*/
void SetViewport(float x1_, float y1_, float x2_, float y2_) {
x1 = x1_;
x2 = x2_;
y1 = y1_;
y2 = y2_;
};
void SetViewportX1(float x1_) { x1 = x1_; };
void SetViewportY1(float y1_) { y1 = y1_; };
void SetViewportX2(float x2_) { x2 = x2_; };
void SetViewportY2(float y2_) { y2 = y2_; };
float GetViewportX1() const { return x1; };
float GetViewportY1() const { return y1; };
float GetViewportX2() const { return x2; };
float GetViewportY2() const { return y2; };
/**
* \brief Change the size of the rendered area of the scene, in pixels.
*/
void SetSize(float width_, float height_) {
width = width_;
height = height_;
};
float GetWidth() const { return width; };
float GetHeight() const { return height; };
void SetUseDefaultSize(bool useDefaultSize = true) {
defaultSize = useDefaultSize;
};
bool UseDefaultSize() const { return defaultSize; }
void SetUseDefaultViewport(bool useDefaultViewport = true) {
defaultViewport = useDefaultViewport;
};
bool UseDefaultViewport() const { return defaultViewport; }
private:
bool defaultSize; ///< True if the camera use the default window size
bool defaultViewport; ///< True if the camera use the default viewport size
float x1;
float y1;
float x2;
float y2;
float width; ///< The width of the rendered area
float height; ///< The height of the rendered area
};
} // namespace gd
#endif // GDCORE_LAYER_H

View File

@@ -294,6 +294,7 @@ void Layout::SerializeTo(SerializerElement& element) const {
GetVariables().SerializeTo(element.AddChild("variables"));
GetInitialInstances().SerializeTo(element.AddChild("instances"));
SerializeObjectsTo(element.AddChild("objects"));
SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
gd::EventsListSerialization::SerializeEventsTo(events,
element.AddChild("events"));
@@ -353,6 +354,11 @@ void Layout::UnserializeFrom(gd::Project& project,
project, GetEvents(), element.GetChild("events", 0, "Events"));
UnserializeObjectsFrom(project, element.GetChild("objects", 0, "Objets"));
if (element.HasChild("objectsFolderStructure")) {
UnserializeFoldersFrom(project, element.GetChild("objectsFolderStructure", 0));
}
AddMissingObjectsInRootFolder();
initialInstances.UnserializeFrom(
element.GetChild("instances", 0, "Positions"));
variables.UnserializeFrom(element.GetChild("variables", 0, "Variables"));
@@ -444,13 +450,15 @@ gd::String GD_CORE_API GetTypeOfObject(const gd::ObjectsContainer& project,
bool searchInGroups) {
gd::String type;
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(name))
type = layout.GetObject(name).GetType();
else if (project.HasObjectNamed(name))
type = project.GetObject(name).GetType();
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its 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) {
@@ -523,7 +531,7 @@ std::vector<gd::String> GD_CORE_API GetBehaviorNamesInObjectOrGroup(
const gd::ObjectsContainer &project, const gd::ObjectsContainer &layout,
const gd::String &objectOrGroupName, const gd::String &behaviorType,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
auto &object = layout.GetObject(objectOrGroupName);
auto behaviorNames = object.GetAllBehaviorNames();
@@ -542,7 +550,9 @@ std::vector<gd::String> GD_CORE_API GetBehaviorNamesInObjectOrGroup(
return behaviorNames;
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -554,12 +564,14 @@ std::vector<gd::String> GD_CORE_API GetBehaviorNamesInObjectOrGroup(
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
std::vector<gd::String> behaviorNames;
return behaviorNames;
}
// Compute the intersection of the behaviors of all objects.
auto behaviorNames = GetBehaviorNamesInObjectOrGroup(
project, layout, groupsObjects[0], behaviorType, false);
for (size_t i = 1; i < groupsObjects.size(); i++) {
@@ -587,7 +599,7 @@ bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
const gd::String &objectOrGroupName,
const gd::String &behaviorName,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
return layout.GetObject(objectOrGroupName).HasBehaviorNamed(behaviorName);
}
@@ -599,7 +611,9 @@ bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
return false;
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -610,10 +624,12 @@ bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
return false;
}
// Check that all objects have the behavior.
for (auto &&object : groupsObjects) {
if (!HasBehaviorInObjectOrGroup(project, layout, object, behaviorName,
@@ -629,7 +645,7 @@ bool GD_CORE_API IsDefaultBehavior(const gd::ObjectsContainer& project,
gd::String objectOrGroupName,
gd::String behaviorName,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
auto &object = layout.GetObject(objectOrGroupName);
return object.HasBehaviorNamed(behaviorName) &&
@@ -645,7 +661,9 @@ bool GD_CORE_API IsDefaultBehavior(const gd::ObjectsContainer& project,
return false;
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -656,10 +674,12 @@ bool GD_CORE_API IsDefaultBehavior(const gd::ObjectsContainer& project,
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
return false;
}
// Check that all objects have the same type.
for (auto &&object : groupsObjects) {
if (!IsDefaultBehavior(project, layout, object, behaviorName,
@@ -675,7 +695,7 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
const gd::String& objectOrGroupName,
const gd::String& behaviorName,
bool searchInGroups) {
// Search in objects
// Search in objects.
if (layout.HasObjectNamed(objectOrGroupName)) {
auto &object = layout.GetObject(objectOrGroupName);
return object.HasBehaviorNamed(behaviorName) ?
@@ -691,7 +711,9 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
return "";
}
// Search in groups
// Search in groups.
// Currently, a group is considered as the "intersection" of all of its objects.
// Search "groups is the intersection of its objects" in the codebase.
const gd::ObjectsContainer *container;
if (layout.GetObjectGroups().Has(objectOrGroupName)) {
container = &layout;
@@ -702,10 +724,12 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
}
const vector<gd::String> &groupsObjects =
container->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
// Empty groups don't contain any behavior.
if (groupsObjects.empty()) {
return "";
}
// Check that all objects have the behavior with the same type.
auto behaviorType = GetTypeOfBehaviorInObjectOrGroup(
project, layout, groupsObjects[0], behaviorName, false);
@@ -742,7 +766,7 @@ gd::String GD_CORE_API GetTypeOfBehavior(const gd::ObjectsContainer& project,
vector<gd::String> GD_CORE_API
GetBehaviorsOfObject(const gd::ObjectsContainer& project,
const gd::ObjectsContainer& layout,
gd::String name,
const gd::String& name,
bool searchInGroups) {
bool behaviorsAlreadyInserted = false;
vector<gd::String> behaviors;
@@ -767,6 +791,8 @@ GetBehaviorsOfObject(const gd::ObjectsContainer& project,
}
// Search in groups
// 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) {

View File

@@ -435,6 +435,7 @@ 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.
* \deprecated Use gd::ObjectsContainersList::GetTypeOfObject instead.
*
* @return Type of the object/group.
*/
@@ -444,6 +445,7 @@ gd::String GD_CORE_API GetTypeOfObject(const ObjectsContainer& game,
bool searchInGroups = true);
/**
* \brief Check if an object or all objects of a group has a behavior.
* \deprecated Use gd::ObjectsContainersList::HasBehaviorInObjectOrGroup instead.
*/
bool GD_CORE_API HasBehaviorInObjectOrGroup(const gd::ObjectsContainer &project,
const gd::ObjectsContainer &layout,
@@ -479,6 +481,7 @@ gd::String GD_CORE_API GetTypeOfBehaviorInObjectOrGroup(const gd::ObjectsContain
/**
* \brief Get a type from a behavior name
* @return Type of the behavior.
* @deprecated - Use GetTypeOfBehaviorInObjectOrGroup instead.
*/
gd::String GD_CORE_API GetTypeOfBehavior(const ObjectsContainer& game,
const ObjectsContainer& layout,
@@ -495,7 +498,7 @@ gd::String GD_CORE_API GetTypeOfBehavior(const ObjectsContainer& game,
std::vector<gd::String> GD_CORE_API
GetBehaviorsOfObject(const ObjectsContainer& game,
const ObjectsContainer& layout,
gd::String objectName,
const gd::String& objectName,
bool searchInGroups = true);
} // namespace gd

View File

@@ -5,7 +5,6 @@
*/
#include "LoadingScreen.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {

View File

@@ -15,6 +15,7 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Project/PropertyDescriptor.h"
#include "GDCore/Tools/Log.h"
#include "GDCore/Tools/UUID/UUID.h"
namespace gd {
@@ -35,10 +36,10 @@ Object::Object(const gd::String& name_,
}
void Object::Init(const gd::Object& object) {
persistentUuid = object.persistentUuid;
name = object.name;
assetStoreId = object.assetStoreId;
objectVariables = object.objectVariables;
tags = object.tags;
effectsContainer = object.effectsContainer;
behaviors.clear();
@@ -127,10 +128,11 @@ gd::Behavior* Object::AddNewBehavior(const gd::Project& project,
void Object::UnserializeFrom(gd::Project& project,
const SerializerElement& element) {
persistentUuid = element.GetStringAttribute("persistentUuid");
SetType(element.GetStringAttribute("type"));
assetStoreId = element.GetStringAttribute("assetStoreId");
name = element.GetStringAttribute("name", name, "nom");
tags = element.GetStringAttribute("tags");
objectVariables.UnserializeFrom(
element.GetChild("variables", 0, "Variables"));
@@ -197,10 +199,12 @@ void Object::UnserializeFrom(gd::Project& project,
}
void Object::SerializeTo(SerializerElement& element) const {
if (!persistentUuid.empty())
element.SetStringAttribute("persistentUuid", persistentUuid);
element.SetAttribute("name", GetName());
element.SetAttribute("assetStoreId", GetAssetStoreId());
element.SetAttribute("type", GetType());
element.SetAttribute("tags", GetTags());
objectVariables.SerializeTo(element.AddChild("variables"));
effectsContainer.SerializeTo(element.AddChild("effects"));
@@ -227,4 +231,18 @@ void Object::SerializeTo(SerializerElement& element) const {
configuration->SerializeTo(element);
}
Object& Object::ResetPersistentUuid() {
persistentUuid = UUID::MakeUuid4();
objectVariables.ResetPersistentUuid();
return *this;
}
Object& Object::ClearPersistentUuid() {
persistentUuid = "";
objectVariables.ClearPersistentUuid();
return *this;
}
} // namespace gd

View File

@@ -120,14 +120,6 @@ class GD_CORE_API Object {
*/
const gd::String& GetType() const { return configuration->GetType(); }
/** \brief Change the tags of the object.
*/
void SetTags(const gd::String& tags_) { tags = tags_; }
/** \brief Return the tags of the object.
*/
const gd::String& GetTags() const { return tags; }
/** \brief Shortcut to check if the object is a 3D object.
*/
bool Is3DObject() const { return configuration->Is3DObject(); }
@@ -243,6 +235,18 @@ class GD_CORE_API Object {
* \see DoUnserializeFrom
*/
void UnserializeFrom(gd::Project& project, const SerializerElement& element);
/**
* \brief Reset the persistent UUID, used to recognize
* the same object between serialization.
*/
Object& ResetPersistentUuid();
/**
* \brief Remove the persistent UUID - when the object no
* longer need to be recognized between serializations.
*/
Object& ClearPersistentUuid();
///@}
protected:
@@ -256,9 +260,10 @@ class GD_CORE_API Object {
///< object.
gd::VariablesContainer
objectVariables; ///< List of the variables of the object
gd::String tags; ///< Comma-separated list of tags
gd::EffectsContainer
effectsContainer; ///< The effects container for the object.
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for computing changesets.
/**
* Initialize object using another object. Used by copy-ctor and assign-op.

View File

@@ -0,0 +1,248 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/Project/ObjectFolderOrObject.h"
#include <memory>
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/Tools/Log.h"
using namespace std;
namespace gd {
ObjectFolderOrObject ObjectFolderOrObject::badObjectFolderOrObject;
ObjectFolderOrObject::ObjectFolderOrObject()
: folderName("__NULL"), object(nullptr) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::String folderName_,
ObjectFolderOrObject* parent_)
: folderName(folderName_), parent(parent_), object(nullptr) {}
ObjectFolderOrObject::ObjectFolderOrObject(gd::Object* object_,
ObjectFolderOrObject* parent_)
: object(object_), parent(parent_) {}
ObjectFolderOrObject::~ObjectFolderOrObject() {}
bool ObjectFolderOrObject::HasObjectNamed(const gd::String& name) {
if (IsFolder()) {
return std::any_of(
children.begin(),
children.end(),
[&name](
std::unique_ptr<gd::ObjectFolderOrObject>& objectFolderOrObject) {
return objectFolderOrObject->HasObjectNamed(name);
});
}
if (!object) return false;
return object->GetName() == name;
}
ObjectFolderOrObject& ObjectFolderOrObject::GetObjectNamed(
const gd::String& name) {
if (object && object->GetName() == name) {
return *this;
}
if (IsFolder()) {
for (std::size_t j = 0; j < children.size(); j++) {
ObjectFolderOrObject& foundInChild = children[j]->GetObjectNamed(name);
if (&(foundInChild) != &badObjectFolderOrObject) {
return foundInChild;
}
}
}
return badObjectFolderOrObject;
}
void ObjectFolderOrObject::SetFolderName(const gd::String& name) {
if (!IsFolder()) return;
folderName = name;
}
ObjectFolderOrObject& ObjectFolderOrObject::GetChildAt(std::size_t index) {
if (index >= children.size()) return badObjectFolderOrObject;
return *children[index];
}
const ObjectFolderOrObject& ObjectFolderOrObject::GetChildAt(std::size_t index) const {
if (index >= children.size()) return badObjectFolderOrObject;
return *children[index];
}
ObjectFolderOrObject& ObjectFolderOrObject::GetObjectChild(
const gd::String& name) {
for (std::size_t j = 0; j < children.size(); j++) {
if (!children[j]->IsFolder()) {
if (children[j]->GetObject().GetName() == name) return *children[j];
};
}
return badObjectFolderOrObject;
}
void ObjectFolderOrObject::InsertObject(gd::Object* insertedObject,
std::size_t position) {
auto objectFolderOrObject =
gd::make_unique<ObjectFolderOrObject>(insertedObject, this);
if (position < children.size()) {
children.insert(children.begin() + position,
std::move(objectFolderOrObject));
} else {
children.push_back(std::move(objectFolderOrObject));
}
}
std::size_t ObjectFolderOrObject::GetChildPosition(
const ObjectFolderOrObject& child) const {
for (std::size_t j = 0; j < children.size(); j++) {
if (children[j].get() == &child) return j;
}
return gd::String::npos;
}
ObjectFolderOrObject& ObjectFolderOrObject::InsertNewFolder(
const gd::String& newFolderName, std::size_t position) {
auto newFolderPtr =
gd::make_unique<ObjectFolderOrObject>(newFolderName, this);
gd::ObjectFolderOrObject& newFolder = *(*(children.insert(
position < children.size() ? children.begin() + position : children.end(),
std::move(newFolderPtr))));
return newFolder;
};
void ObjectFolderOrObject::RemoveRecursivelyObjectNamed(
const gd::String& name) {
if (IsFolder()) {
children.erase(
std::remove_if(children.begin(),
children.end(),
[&name](std::unique_ptr<gd::ObjectFolderOrObject>&
objectFolderOrObject) {
return !objectFolderOrObject->IsFolder() &&
objectFolderOrObject->GetObject().GetName() ==
name;
}),
children.end());
for (auto& it : children) {
it->RemoveRecursivelyObjectNamed(name);
}
}
};
bool ObjectFolderOrObject::IsADescendantOf(
const ObjectFolderOrObject& otherObjectFolderOrObject) {
if (parent == nullptr) return false;
if (&(*parent) == &otherObjectFolderOrObject) return true;
return parent->IsADescendantOf(otherObjectFolderOrObject);
}
void ObjectFolderOrObject::MoveChild(std::size_t oldIndex,
std::size_t newIndex) {
if (!IsFolder()) return;
if (oldIndex >= children.size() || newIndex >= children.size()) return;
std::unique_ptr<gd::ObjectFolderOrObject> objectFolderOrObject =
std::move(children[oldIndex]);
children.erase(children.begin() + oldIndex);
children.insert(children.begin() + newIndex, std::move(objectFolderOrObject));
}
void ObjectFolderOrObject::RemoveFolderChild(
const ObjectFolderOrObject& childToRemove) {
if (!IsFolder() || !childToRemove.IsFolder() ||
childToRemove.GetChildrenCount() > 0) {
return;
}
std::vector<std::unique_ptr<gd::ObjectFolderOrObject>>::iterator it = find_if(
children.begin(),
children.end(),
[&childToRemove](std::unique_ptr<gd::ObjectFolderOrObject>& child) {
return child.get() == &childToRemove;
});
if (it == children.end()) return;
children.erase(it);
}
void ObjectFolderOrObject::MoveObjectFolderOrObjectToAnotherFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition) {
if (!newParentFolder.IsFolder()) return;
if (newParentFolder.IsADescendantOf(objectFolderOrObject)) return;
std::vector<std::unique_ptr<gd::ObjectFolderOrObject>>::iterator it =
find_if(children.begin(),
children.end(),
[&objectFolderOrObject](std::unique_ptr<gd::ObjectFolderOrObject>&
childObjectFolderOrObject) {
return childObjectFolderOrObject.get() == &objectFolderOrObject;
});
if (it == children.end()) return;
std::unique_ptr<gd::ObjectFolderOrObject> objectFolderOrObjectPtr =
std::move(*it);
children.erase(it);
objectFolderOrObjectPtr->parent = &newParentFolder;
newParentFolder.children.insert(
newPosition < newParentFolder.children.size()
? newParentFolder.children.begin() + newPosition
: newParentFolder.children.end(),
std::move(objectFolderOrObjectPtr));
}
void ObjectFolderOrObject::SerializeTo(SerializerElement& element) const {
if (IsFolder()) {
element.SetAttribute("folderName", GetFolderName());
if (children.size() > 0) {
SerializerElement& childrenElement = element.AddChild("children");
childrenElement.ConsiderAsArrayOf("objectFolderOrObject");
for (std::size_t j = 0; j < children.size(); j++) {
children[j]->SerializeTo(
childrenElement.AddChild("objectFolderOrObject"));
}
}
} else {
element.SetAttribute("objectName", GetObject().GetName());
}
}
void ObjectFolderOrObject::UnserializeFrom(
gd::Project& project,
const SerializerElement& element,
gd::ObjectsContainer& objectsContainer) {
children.clear();
gd::String potentialFolderName = element.GetStringAttribute("folderName", "");
if (!potentialFolderName.empty()) {
object = nullptr;
folderName = potentialFolderName;
if (element.HasChild("children")) {
const SerializerElement& childrenElements =
element.GetChild("children", 0);
childrenElements.ConsiderAsArrayOf("objectFolderOrObject");
for (std::size_t i = 0; i < childrenElements.GetChildrenCount(); ++i) {
std::unique_ptr<ObjectFolderOrObject> childObjectFolderOrObject =
make_unique<ObjectFolderOrObject>();
childObjectFolderOrObject->UnserializeFrom(
project, childrenElements.GetChild(i), objectsContainer);
childObjectFolderOrObject->parent = this;
children.push_back(std::move(childObjectFolderOrObject));
}
}
} else {
folderName = "";
gd::String objectName = element.GetStringAttribute("objectName");
if (objectsContainer.HasObjectNamed(objectName)) {
object = &objectsContainer.GetObject(objectName);
} else {
gd::LogError("Object with name " + objectName +
" not found in objects container.");
object = nullptr;
}
}
};
} // namespace gd

View File

@@ -0,0 +1,203 @@
/*
* GDevelop Core
* Copyright 2008-2023 Florian Rival (Florian.Rival@gmail.com). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_OBJECTFOLDEROROBJECT_H
#define GDCORE_OBJECTFOLDEROROBJECT_H
#include <memory>
#include <vector>
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
namespace gd {
class Project;
class Object;
class SerializerElement;
class ObjectsContainer;
} // namespace gd
namespace gd {
/**
* \brief Class representing a folder structure in order to organize objects
* in folders (to be used with an ObjectsContainer.)
*
* \see gd::ObjectsContainer
*/
class GD_CORE_API ObjectFolderOrObject {
public:
/**
* \brief Default constructor creating an empty instance. Useful for the null
* object pattern.
*/
ObjectFolderOrObject();
virtual ~ObjectFolderOrObject();
/**
* \brief Constructor for creating an instance representing a folder.
*/
ObjectFolderOrObject(gd::String folderName_,
ObjectFolderOrObject* parent_ = nullptr);
/**
* \brief Constructor for creating an instance representing an object.
*/
ObjectFolderOrObject(gd::Object* object_,
ObjectFolderOrObject* parent_ = nullptr);
/**
* \brief Returns the object behind the instance.
*/
gd::Object& GetObject() const { return *object; }
/**
* \brief Returns true if the instance represents a folder.
*/
bool IsFolder() const { return !folderName.empty(); }
/**
* \brief Returns the name of the folder.
*/
const gd::String& GetFolderName() const { return folderName; }
/**
* \brief Set the folder name. Does nothing if called on an instance not
* representing a folder.
*/
void SetFolderName(const gd::String& name);
/**
* \brief Returns true if the instance represents the object with the given
* name or if any of the children does (recursive search).
*/
bool HasObjectNamed(const gd::String& name);
/**
* \brief Returns the child instance holding the object with the given name
* (recursive search).
*/
ObjectFolderOrObject& GetObjectNamed(const gd::String& name);
/**
* \brief Returns the number of children. Returns 0 if the instance represents
* an object.
*/
std::size_t GetChildrenCount() const {
if (IsFolder()) return children.size();
return 0;
}
/**
* \brief Returns the child ObjectFolderOrObject at the given index.
*/
ObjectFolderOrObject& GetChildAt(std::size_t index);
/**
* \brief Returns the child ObjectFolderOrObject at the given index.
*/
const ObjectFolderOrObject& GetChildAt(std::size_t index) const;
/**
* \brief Returns the child ObjectFolderOrObject that represents the object
* with the given name. To use only if sure that the instance holds the object
* in its direct children (no recursive search).
*
* \note The equivalent method to get a folder by its name cannot be
* implemented because there is no unicity enforced on the folder name.
*/
ObjectFolderOrObject& GetObjectChild(const gd::String& name);
/**
* \brief Returns the parent of the instance. If the instance has no parent
* (root folder), the null object is returned.
*/
ObjectFolderOrObject& GetParent() {
if (parent == nullptr) {
return badObjectFolderOrObject;
}
return *parent;
};
/**
* \brief Returns true if the instance is a root folder (that's to say it
* has no parent).
*/
bool IsRootFolder() { return !object && !parent; }
/**
* \brief Moves a child from a position to a new one.
*/
void MoveChild(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Removes the given child from the instance's children. If the given
* child contains children of its own, does nothing.
*/
void RemoveFolderChild(const ObjectFolderOrObject& childToRemove);
/**
* \brief Removes the child representing the object with the given name from
* the instance children and recursively does it for every folder children.
*/
void RemoveRecursivelyObjectNamed(const gd::String& name);
/**
* \brief Inserts an instance representing the given object at the given
* position.
*/
void InsertObject(gd::Object* insertedObject,
std::size_t position = (size_t)-1);
/**
* \brief Inserts an instance representing a folder with the given name at the
* given position.
*/
ObjectFolderOrObject& InsertNewFolder(const gd::String& newFolderName,
std::size_t position);
/**
* \brief Returns true if the instance is a descendant of the given instance
* of ObjectFolderOrObject.
*/
bool IsADescendantOf(const ObjectFolderOrObject& otherObjectFolderOrObject);
/**
* \brief Returns the position of the given instance of ObjectFolderOrObject
* in the instance's children.
*/
std::size_t GetChildPosition(const ObjectFolderOrObject& child) const;
/**
* \brief Moves the given child ObjectFolderOrObject to the given folder at
* the given position.
*/
void MoveObjectFolderOrObjectToAnotherFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition);
/** \name Saving and loading
* Members functions related to saving and loading the objects of the class.
*/
///@{
/**
* \brief Serialize the ObjectFolderOrObject instance.
*/
void SerializeTo(SerializerElement& element) const;
/**
* \brief Unserialize the ObjectFolderOrObject instance.
*/
void UnserializeFrom(gd::Project& project,
const SerializerElement& element,
ObjectsContainer& objectsContainer);
///@}
private:
static gd::ObjectFolderOrObject badObjectFolderOrObject;
gd::ObjectFolderOrObject*
parent; // nullptr if root folder, points to the parent folder otherwise.
// Representing an object:
gd::Object* object; // nullptr if folderName is set.
// or representing a folder:
gd::String folderName; // Empty if object is set.
std::vector<std::unique_ptr<ObjectFolderOrObject>>
children; // Folder children.
};
} // namespace gd
#endif // GDCORE_OBJECTFOLDEROROBJECT_H

View File

@@ -162,4 +162,13 @@ void ObjectGroupsContainer::Move(std::size_t oldIndex, std::size_t newIndex) {
objectGroups.insert(objectGroups.begin() + newIndex, std::move(objectGroup));
}
void ObjectGroupsContainer::ForEachNameMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name)> fn) const {
for (const auto& objectGroup : objectGroups) {
if (objectGroup->GetName().FindCaseInsensitive(search) != gd::String::npos)
fn(objectGroup->GetName());
}
}
} // namespace gd

View File

@@ -116,6 +116,11 @@ class GD_CORE_API ObjectGroupsContainer {
* \brief Clear all groups of the container.
*/
inline void Clear() { objectGroups.clear(); }
/**
* \brief Call the callback for each group name matching the specified search.
*/
void ForEachNameMatchingSearch(const gd::String& search, std::function<void(const gd::String& name)> fn) const;
///@}
/** \name Saving and loading

View File

@@ -9,12 +9,15 @@
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectFolderOrObject.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Serialization/SerializerElement.h"
namespace gd {
ObjectsContainer::ObjectsContainer() {}
ObjectsContainer::ObjectsContainer() {
rootFolder = gd::make_unique<gd::ObjectFolderOrObject>("__ROOT");
}
ObjectsContainer::~ObjectsContainer() {}
@@ -24,6 +27,22 @@ void ObjectsContainer::SerializeObjectsTo(SerializerElement& element) const {
initialObjects[j]->SerializeTo(element.AddChild("object"));
}
}
void ObjectsContainer::SerializeFoldersTo(SerializerElement& element) const {
rootFolder->SerializeTo(element);
}
void ObjectsContainer::UnserializeFoldersFrom(
gd::Project& project, const SerializerElement& element) {
rootFolder->UnserializeFrom(project, element, *this);
}
void ObjectsContainer::AddMissingObjectsInRootFolder() {
for (std::size_t i = 0; i < initialObjects.size(); ++i) {
if (!rootFolder->HasObjectNamed(initialObjects[i]->GetName())) {
rootFolder->InsertObject(&(*initialObjects[i]));
}
}
}
void ObjectsContainer::UnserializeObjectsFrom(
gd::Project& project, const SerializerElement& element) {
@@ -48,17 +67,23 @@ void ObjectsContainer::UnserializeObjectsFrom(
bool ObjectsContainer::HasObjectNamed(const gd::String& name) const {
return (find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(gd::ObjectHasName(), name)) != initialObjects.end());
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
}) != initialObjects.end());
}
gd::Object& ObjectsContainer::GetObject(const gd::String& name) {
return *(*find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(gd::ObjectHasName(), name)));
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
}));
}
const gd::Object& ObjectsContainer::GetObject(const gd::String& name) const {
return *(*find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(gd::ObjectHasName(), name)));
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
}));
}
gd::Object& ObjectsContainer::GetObject(std::size_t index) {
return *initialObjects[index];
@@ -84,6 +109,22 @@ gd::Object& ObjectsContainer::InsertNewObject(const gd::Project& project,
: initialObjects.end(),
project.CreateObject(objectType, name))));
rootFolder->InsertObject(&newlyCreatedObject);
return newlyCreatedObject;
}
gd::Object& ObjectsContainer::InsertNewObjectInFolder(
const gd::Project& project,
const gd::String& objectType,
const gd::String& name,
gd::ObjectFolderOrObject& objectFolderOrObject,
std::size_t position) {
gd::Object& newlyCreatedObject = *(*(initialObjects.insert(
initialObjects.end(), project.CreateObject(objectType, name))));
objectFolderOrObject.InsertObject(&newlyCreatedObject, position);
return newlyCreatedObject;
}
@@ -97,16 +138,6 @@ gd::Object& ObjectsContainer::InsertObject(const gd::Object& object,
return newlyCreatedObject;
}
void ObjectsContainer::SwapObjects(std::size_t firstObjectIndex,
std::size_t secondObjectIndex) {
if (firstObjectIndex >= initialObjects.size() ||
secondObjectIndex >= initialObjects.size())
return;
std::iter_swap(initialObjects.begin() + firstObjectIndex,
initialObjects.begin() + secondObjectIndex);
}
void ObjectsContainer::MoveObject(std::size_t oldIndex, std::size_t newIndex) {
if (oldIndex >= initialObjects.size() || newIndex >= initialObjects.size())
return;
@@ -120,30 +151,59 @@ void ObjectsContainer::RemoveObject(const gd::String& name) {
std::vector<std::unique_ptr<gd::Object>>::iterator objectIt =
find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(ObjectHasName(), name));
[&](const std::unique_ptr<gd::Object>& object) {
return object->GetName() == name;
});
if (objectIt == initialObjects.end()) return;
rootFolder->RemoveRecursivelyObjectNamed(name);
initialObjects.erase(objectIt);
}
void ObjectsContainer::MoveObjectToAnotherContainer(
const gd::String& name,
void ObjectsContainer::MoveObjectFolderOrObjectToAnotherContainerInFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectsContainer& newContainer,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition) {
std::vector<std::unique_ptr<gd::Object>>::iterator objectIt =
find_if(initialObjects.begin(),
initialObjects.end(),
bind2nd(ObjectHasName(), name));
if (objectFolderOrObject.IsFolder() || !newParentFolder.IsFolder()) return;
std::vector<std::unique_ptr<gd::Object>>::iterator objectIt = find_if(
initialObjects.begin(),
initialObjects.end(),
[&objectFolderOrObject](std::unique_ptr<gd::Object>& object) {
return object->GetName() == objectFolderOrObject.GetObject().GetName();
});
if (objectIt == initialObjects.end()) return;
std::unique_ptr<gd::Object> object = std::move(*objectIt);
initialObjects.erase(objectIt);
newContainer.initialObjects.insert(
newPosition < newContainer.initialObjects.size()
? newContainer.initialObjects.begin() + newPosition
: newContainer.initialObjects.end(),
std::move(object));
newContainer.initialObjects.push_back(std::move(object));
objectFolderOrObject.GetParent().MoveObjectFolderOrObjectToAnotherFolder(
objectFolderOrObject, newParentFolder, newPosition);
}
std::vector<const ObjectFolderOrObject*>
ObjectsContainer::GetAllObjectFolderOrObjects() const {
std::vector<const ObjectFolderOrObject*> results;
std::function<void(const ObjectFolderOrObject& folder)> addChildrenOfFolder =
[&](const ObjectFolderOrObject& folder) {
for (size_t i = 0; i < folder.GetChildrenCount(); ++i) {
const auto& child = folder.GetChildAt(i);
results.push_back(&child);
if (child.IsFolder()) {
addChildrenOfFolder(child);
}
}
};
addChildrenOfFolder(*rootFolder);
return results;
}
} // namespace gd

View File

@@ -9,11 +9,12 @@
#include <vector>
#include "GDCore/String.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ObjectFolderOrObject.h"
namespace gd {
class Object;
class Project;
class SerializerElement;
}
} // namespace gd
#undef GetObject // Disable an annoying macro
namespace gd {
@@ -98,6 +99,19 @@ class GD_CORE_API ObjectsContainer {
const gd::String& objectType,
const gd::String& name,
std::size_t position);
/**
* \brief Add a new empty object of type \a objectType called \a name in the
* given folder at the specified position.<br>
*
* \note The object is created using the project's current platform.
* \return A reference to the object in the list.
*/
gd::Object& InsertNewObjectInFolder(
const gd::Project& project,
const gd::String& objectType,
const gd::String& name,
gd::ObjectFolderOrObject& objectFolderOrObject,
std::size_t position);
/**
* \brief Add a new object to the list
@@ -125,18 +139,18 @@ class GD_CORE_API ObjectsContainer {
void MoveObject(std::size_t oldIndex, std::size_t newIndex);
/**
* \brief Swap the position of the specified objects.
*/
void SwapObjects(std::size_t firstObjectIndex, std::size_t secondObjectIndex);
/**
* Move the specified object to another container, removing it from the current one
* and adding it to the new one at the specified position.
* Move the specified object to another container, removing it from the
* current one and adding it to the new one at the specified position in the
* given folder.
*
* \note This does not invalidate the references to the object (object is not moved in memory,
* as referenced by smart pointers internally).
* \note This does not invalidate the references to the object (object is not
* moved in memory, as referenced by smart pointers internally).
*/
void MoveObjectToAnotherContainer(const gd::String& name, gd::ObjectsContainer & newContainer, std::size_t newPosition);
void MoveObjectFolderOrObjectToAnotherContainerInFolder(
gd::ObjectFolderOrObject& objectFolderOrObject,
gd::ObjectsContainer& newContainer,
gd::ObjectFolderOrObject& newParentFolder,
std::size_t newPosition);
/**
* Provide a raw access to the vector containing the objects
@@ -153,20 +167,43 @@ class GD_CORE_API ObjectsContainer {
}
///@}
/**
* Returns a vector containing all object and folders in this container.
* Only use this for checking if you hold a valid `ObjectFolderOrObject` -
* don't use this for rendering or anything else.
*/
std::vector<const ObjectFolderOrObject*> GetAllObjectFolderOrObjects() const;
gd::ObjectFolderOrObject& GetRootFolder() {
return *rootFolder;
}
void AddMissingObjectsInRootFolder();
/** \name Saving and loading
* Members functions related to saving and loading the objects of the class.
*/
///@{
/**
* \brief Serialize instances container.
* \brief Serialize the objects container.
*/
void SerializeObjectsTo(SerializerElement& element) const;
/**
* \brief Unserialize the instances container.
* \brief Unserialize the objects container.
*/
void UnserializeObjectsFrom(gd::Project& project,
const SerializerElement& element);
/**
* \brief Serialize folder structure.
*/
void SerializeFoldersTo(SerializerElement& element) const;
/**
* \brief Unserialize folder structure.
*/
void UnserializeFoldersFrom(gd::Project& project,
const SerializerElement& element);
///@}
/** \name Objects groups management
@@ -190,6 +227,9 @@ class GD_CORE_API ObjectsContainer {
std::vector<std::unique_ptr<gd::Object> >
initialObjects; ///< Objects contained.
gd::ObjectGroupsContainer objectGroups;
private:
std::unique_ptr<gd::ObjectFolderOrObject> rootFolder;
};
} // namespace gd

View File

@@ -0,0 +1,406 @@
#include "ObjectsContainersList.h"
#include <vector>
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
ObjectsContainersList
ObjectsContainersList::MakeNewObjectsContainersListForProjectAndLayout(
const gd::Project& project, const gd::Layout& layout) {
ObjectsContainersList objectsContainersList;
objectsContainersList.Add(project);
objectsContainersList.Add(layout);
return objectsContainersList;
}
ObjectsContainersList
ObjectsContainersList::MakeNewObjectsContainersListForContainers(
const gd::ObjectsContainer& globalObjectsContainer,
const gd::ObjectsContainer& objectsContainer) {
ObjectsContainersList objectsContainersList;
objectsContainersList.Add(globalObjectsContainer);
objectsContainersList.Add(objectsContainer);
return objectsContainersList;
}
bool ObjectsContainersList::HasObjectOrGroupNamed(
const gd::String& name) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(name) || (*it)->GetObjectGroups().Has(name))
return true;
}
return false;
}
bool ObjectsContainersList::HasObjectNamed(const gd::String& name) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(name)) return true;
}
return false;
}
ObjectsContainersList::VariableExistence
ObjectsContainersList::HasObjectOrGroupWithVariableNamed(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
return variables.Has(variableName) ? VariableExistence::Exists
: VariableExistence::DoesNotExist;
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// This could be adapted if objects groups have variables in the future.
// Currently, a group is considered as the "intersection" of all of its
// objects. Search "groups is the intersection of its objects" in the
// codebase. Consider that a group has a variable if all objects of the
// group have it:
const auto& objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto& objectNames = objectGroup.GetAllObjectsNames();
if (objectNames.empty()) return VariableExistence::GroupIsEmpty;
bool existsOnAtLeastOneObject = false;
bool missingOnAtLeastOneObject = false;
for (const auto& objectName : objectNames) {
if (!HasObjectWithVariableNamed(objectName, variableName)) {
missingOnAtLeastOneObject = true;
if (existsOnAtLeastOneObject) {
return VariableExistence::ExistsOnlyOnSomeObjectsOfTheGroup;
}
} else {
existsOnAtLeastOneObject = true;
if (missingOnAtLeastOneObject) {
return VariableExistence::ExistsOnlyOnSomeObjectsOfTheGroup;
}
}
}
if (missingOnAtLeastOneObject) {
return VariableExistence::DoesNotExist;
}
return VariableExistence::Exists;
}
}
return VariableExistence::DoesNotExist;
}
bool ObjectsContainersList::HasObjectWithVariableNamed(
const gd::String& objectName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
const auto& variables = (*it)->GetObject(objectName).GetVariables();
return variables.Has(variableName);
}
}
return false;
}
bool ObjectsContainersList::HasVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
return &variablesContainer ==
&(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
// This would allow handling the renaming of variables of an object group.
}
}
return false;
}
const gd::VariablesContainer*
ObjectsContainersList::GetObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
return &(*it)->GetObject(objectOrGroupName).GetVariables();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// Could be adapted if objects groups have variables in the future.
// This would allow handling the renaming of variables of an object group.
}
}
return nullptr;
}
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectOrGroupVariable(
const gd::String& objectOrGroupName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
return variables.Get(variableName).GetType();
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// This could be adapted if objects groups have variables in the future.
// Currently, a group is considered as the "intersection" of all of its
// objects. Search "groups is the intersection of its objects" in the
// codebase. Consider that the first object having the variable will
// define its type.
const auto& objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto& objectNames = objectGroup.GetAllObjectsNames();
for (const auto& objectName : objectNames) {
if (HasObjectWithVariableNamed(objectName, variableName)) {
return GetTypeOfObjectVariable(objectName, variableName);
}
}
return Variable::Type::Number;
}
}
return Variable::Type::Number;
}
gd::Variable::Type ObjectsContainersList::GetTypeOfObjectVariable(const gd::String& objectName, const gd::String& variableName) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectName)) {
const auto& variables =
(*it)->GetObject(objectName).GetVariables();
return variables.Get(variableName).GetType();
}
}
return Variable::Type::Number;
}
void ObjectsContainersList::ForEachObjectOrGroupVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
variables.ForEachVariableMatchingSearch(search, fn);
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// This could be adapted if objects groups have variables in the future.
// Currently, a group is considered as the "intersection" of all of its
// objects. Search "groups is the intersection of its objects" in the
// codebase. Consider that a group has a variable if all objects of the
// group have it:
const auto& objectGroup = (*it)->GetObjectGroups().Get(objectOrGroupName);
const auto& objectNames = objectGroup.GetAllObjectsNames();
if (objectNames.empty()) return;
const auto& firstObjectName = objectNames.front();
ForEachObjectVariableMatchingSearch(
firstObjectName,
search,
[&](const gd::String& variableName, const gd::Variable& variable) {
for (const auto& objectName : objectGroup.GetAllObjectsNames()) {
if (!HasObjectWithVariableNamed(objectName, variableName)) {
return; // This variable is not shared by all objects of the
// group.
}
}
// This variable is shared by all objects in the group. Note that
// other objects can have it with a different type - we allow this.
fn(variableName, variable);
});
}
}
}
void ObjectsContainersList::ForEachObjectVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
const auto& variables =
(*it)->GetObject(objectOrGroupName).GetVariables();
variables.ForEachVariableMatchingSearch(search, fn);
}
}
}
void ObjectsContainersList::ForEachNameMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration)> fn)
const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
for (const auto& object : (*it)->GetObjects()) {
if (object->GetName().FindCaseInsensitive(search) != gd::String::npos)
fn(object->GetName(), &object->GetConfiguration());
}
(*it)->GetObjectGroups().ForEachNameMatchingSearch(
search, [&](const gd::String& name) { fn(name, nullptr); });
}
}
std::vector<gd::String> ObjectsContainersList::ExpandObjectName(
const gd::String& objectOrGroupName,
const gd::String& onlyObjectToSelectIfPresent) const {
std::vector<gd::String> realObjects;
// Check progressively each object container to find the object or the group
// with the specified name.
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
if ((*it)->HasObjectNamed(objectOrGroupName)) {
// We found the object, it's a single object with this name.
realObjects.push_back(objectOrGroupName);
break;
}
if ((*it)->GetObjectGroups().Has(objectOrGroupName)) {
// We found a group with this name, expand the object names inside of it.
realObjects =
(*it)->GetObjectGroups().Get(objectOrGroupName).GetAllObjectsNames();
break;
}
}
// If the "current object" is present, use it and only it.
if (!onlyObjectToSelectIfPresent.empty() &&
find(realObjects.begin(),
realObjects.end(),
onlyObjectToSelectIfPresent) != realObjects.end()) {
realObjects.clear();
realObjects.push_back(onlyObjectToSelectIfPresent);
}
// Ensure that all returned objects actually exists (i.e: if some groups have
// names refering to non existing objects, don't return them).
for (std::size_t i = 0; i < realObjects.size();) {
if (!HasObjectNamed(realObjects[i]))
realObjects.erase(realObjects.begin() + i);
else
++i;
}
return realObjects;
}
void ObjectsContainersList::ForEachObject(
std::function<void(const gd::Object& object)> fn) const {
for (auto it = objectsContainers.rbegin(); it != objectsContainers.rend();
++it) {
const auto& objectsContainer = **it;
for (const auto& object : objectsContainer.GetObjects()) {
fn(*object);
}
}
}
gd::String ObjectsContainersList::GetTypeOfObject(
const gd::String& objectName) const {
if (objectsContainers.size() != 2) {
std::cout << this << std::endl;
std::cout << objectsContainers.size() << std::endl;
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetTypeOfObject(
*objectsContainers[0], *objectsContainers[1], objectName, true);
}
bool ObjectsContainersList::HasBehaviorInObjectOrGroup(
const gd::String& objectOrGroupName, const gd::String& behaviorName) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::HasBehaviorInObjectOrGroup(*objectsContainers[0],
*objectsContainers[1],
objectOrGroupName,
behaviorName,
true);
}
gd::String ObjectsContainersList::GetTypeOfBehaviorInObjectOrGroup(
const gd::String& objectOrGroupName,
const gd::String& behaviorName,
bool searchInGroups) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetTypeOfBehaviorInObjectOrGroup(*objectsContainers[0],
*objectsContainers[1],
objectOrGroupName,
behaviorName,
searchInGroups);
}
gd::String ObjectsContainersList::GetTypeOfBehavior(
const gd::String& behaviorName, bool searchInGroups) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetTypeOfBehavior(*objectsContainers[0],
*objectsContainers[1],
behaviorName,
searchInGroups);
}
std::vector<gd::String> ObjectsContainersList::GetBehaviorsOfObject(
const gd::String& objectName, bool searchInGroups) const {
if (objectsContainers.size() != 2) {
// TODO: rework forwarded methods so they can work with any number of
// containers.
gd::LogFatalError(
"ObjectsContainersList::GetTypeOfObject called with objectsContainers "
"not being exactly 2. This is a logical error and will crash.");
}
return gd::GetBehaviorsOfObject(
*objectsContainers[0], *objectsContainers[1], objectName, searchInGroups);
}
} // namespace gd

View File

@@ -0,0 +1,183 @@
#pragma once
#include <vector>
#include "Variable.h"
namespace gd {
class String;
class Project;
class Layout;
class ObjectsContainer;
class VariablesContainer;
class Object;
class ObjectConfiguration;
} // namespace gd
namespace gd {
/**
* \brief A list of objects containers, useful for accessing objects in a
* scoped way, along with methods to access them.
*
* \see gd::Object
* \see gd::ObjectsContainer
* \see gd::Project
* \see gd::Layout
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API ObjectsContainersList {
public:
virtual ~ObjectsContainersList(){};
static ObjectsContainersList MakeNewObjectsContainersListForProjectAndLayout(
const gd::Project& project, const gd::Layout& layout);
static ObjectsContainersList MakeNewObjectsContainersListForContainers(
const gd::ObjectsContainer& globalObjectsContainer,
const gd::ObjectsContainer& objectsContainer);
/**
* \brief Check if the specified object or group exists.
*/
bool HasObjectOrGroupNamed(const gd::String& name) const;
enum VariableExistence {
DoesNotExist,
Exists,
GroupIsEmpty,
ExistsOnlyOnSomeObjectsOfTheGroup
};
/**
* \brief Check if the specified object or group has the specified variable in
* its declared variables.
*/
VariableExistence HasObjectOrGroupWithVariableNamed(
const gd::String& objectOrGroupName,
const gd::String& variableName) const;
/**
* \brief Check if the specified object or group has the specified variables
* container.
*/
bool HasVariablesContainer(
const gd::String& objectOrGroupName,
const gd::VariablesContainer& variablesContainer) const;
/**
* \brief Return the container of the variables for the specified object or
* group of objects.
*/
const gd::VariablesContainer* GetObjectOrGroupVariablesContainer(
const gd::String& objectOrGroupName) const;
/**
* \brief Get a type from an object/group variable.
*/
gd::Variable::Type GetTypeOfObjectOrGroupVariable(const gd::String& objectOrGroupName, const gd::String& variableName) const;
/**
* \brief Get a type from an object/group name.
* \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.
*
* @return Type of the object/group.
*/
gd::String GetTypeOfObject(const gd::String& objectName) const;
/**
* \brief Check if an object or all object of a group has a behavior.
*/
bool HasBehaviorInObjectOrGroup(const gd::String& objectOrGroupName,
const gd::String& behaviorName) const;
/**
* \brief Get the type of a behavior if an object or all objects of a group
* has it.
*/
gd::String GetTypeOfBehaviorInObjectOrGroup(
const gd::String& objectOrGroupName,
const gd::String& behaviorName,
bool searchInGroups = true) const;
/**
* \brief Get a type from a behavior name
* @return Type of the behavior.
* @deprecated - Use GetTypeOfBehaviorInObjectOrGroup instead.
*/
gd::String GetTypeOfBehavior(const gd::String& behaviorName,
bool searchInGroups = true) const;
/**
* \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.
*
* @return Vector containing names of behaviors
*/
std::vector<gd::String> GetBehaviorsOfObject(
const gd::String& objectName, bool searchInGroups = true) const;
/**
* \brief Return a list containing all objects refered to by the group.
* If an object name is passed, then only this object name is returned.
*
* If \a onlyObjectToSelectIfPresent is set and present in the group(s),
* only this object will be returned. This is useful for considering this
* object as the "currently selected" object, when generating a condition or
* an action.
*/
std::vector<gd::String> ExpandObjectName(
const gd::String& objectOrGroupName,
const gd::String& onlyObjectToSelectIfPresent = "") const;
void ForEachObject(std::function<void(const gd::Object& object)> fn) const;
/**
* \brief Call the callback for each object or group name matching the
* search passed in parameter.
*/
void ForEachNameMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name,
const gd::ObjectConfiguration* objectConfiguration)>
fn) const;
/**
* \brief Call the callback for each variable of the object (or group)
* matching the search passed in parameter.
*/
void ForEachObjectOrGroupVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const;
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
ObjectsContainersList(){};
private:
bool HasObjectNamed(const gd::String& name) const;
bool HasObjectWithVariableNamed(const gd::String& objectName,
const gd::String& variableName) const;
gd::Variable::Type GetTypeOfObjectVariable(const gd::String& objectName, const gd::String& variableName) const;
void ForEachObjectVariableMatchingSearch(
const gd::String& objectOrGroupName,
const gd::String& search,
std::function<void(const gd::String& variableName,
const gd::Variable& variable)> fn) const;
void Add(const gd::ObjectsContainer& objectsContainer) {
objectsContainers.push_back(&objectsContainer);
};
std::vector<const gd::ObjectsContainer*> objectsContainers;
};
} // namespace gd

View File

@@ -48,11 +48,6 @@ using namespace std;
namespace gd {
// By default, disallow unicode in identifiers, but this can be set to true
// by the IDE. In the future, this will be set to true by default, keeping backward compatibility.
// We keep it disabled by default to progressively ask users to test it in real projects.
bool Project::allowUsageOfUnicodeIdentifierNames = false;
Project::Project()
: name(_("Project")),
version("1.0.0"),
@@ -86,26 +81,24 @@ Project::~Project() {}
void Project::ResetProjectUuid() { projectUuid = UUID::MakeUuid4(); }
std::unique_ptr<gd::Object>
Project::CreateObject(const gd::String &objectType, const gd::String &name) const {
std::unique_ptr<gd::Object> object =
gd::make_unique<Object>(name, objectType, CreateObjectConfiguration(objectType));
std::unique_ptr<gd::Object> Project::CreateObject(
const gd::String& objectType, const gd::String& name) const {
std::unique_ptr<gd::Object> object = gd::make_unique<Object>(
name, objectType, CreateObjectConfiguration(objectType));
auto &platform = GetCurrentPlatform();
auto &project = *this;
auto addDefaultBehavior =
[&platform,
&project,
&object,
&objectType](const gd::String& behaviorType) {
auto &behaviorMetadata =
auto& platform = GetCurrentPlatform();
auto& project = *this;
auto addDefaultBehavior = [&platform, &project, &object, &objectType](
const gd::String& behaviorType) {
auto& behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(platform, behaviorType);
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
gd::LogWarning("Object: " + objectType + " has an unknown default behavior: " + behaviorType);
gd::LogWarning("Object: " + objectType +
" has an unknown default behavior: " + behaviorType);
return;
}
auto* behavior = object->AddNewBehavior(project, behaviorType,
behaviorMetadata.GetDefaultName());
auto* behavior = object->AddNewBehavior(
project, behaviorType, behaviorMetadata.GetDefaultName());
behavior->SetDefaultBehavior(true);
};
@@ -114,18 +107,17 @@ Project::CreateObject(const gd::String &objectType, const gd::String &name) cons
addDefaultBehavior("ResizableCapability::ResizableBehavior");
addDefaultBehavior("ScalableCapability::ScalableBehavior");
addDefaultBehavior("FlippableCapability::FlippableBehavior");
}
else {
auto &objectMetadata = gd::MetadataProvider::GetObjectMetadata(platform, objectType);
} else {
auto& objectMetadata =
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
if (MetadataProvider::IsBadObjectMetadata(objectMetadata)) {
gd::LogWarning("Object: " + name + " has an unknown type: " + objectType);
}
for (auto &behaviorType : objectMetadata.GetDefaultBehaviors()) {
for (auto& behaviorType : objectMetadata.GetDefaultBehaviors()) {
addDefaultBehavior(behaviorType);
}
}
return std::move(object);
}
@@ -849,6 +841,11 @@ void Project::UnserializeFrom(const SerializerElement& element) {
resourcesManager.UnserializeFrom(
element.GetChild("resources", 0, "Resources"));
UnserializeObjectsFrom(*this, element.GetChild("objects", 0, "Objects"));
if (element.HasChild("objectsFolderStructure")) {
UnserializeFoldersFrom(*this, element.GetChild("objectsFolderStructure", 0));
}
AddMissingObjectsInRootFolder();
GetVariables().UnserializeFrom(element.GetChild("variables", 0, "Variables"));
scenes.clear();
@@ -1000,6 +997,7 @@ void Project::SerializeTo(SerializerElement& element) const {
resourcesManager.SerializeTo(element.AddChild("resources"));
SerializeObjectsTo(element.AddChild("objects"));
SerializeFoldersTo(element.AddChild("objectsFolderStructure"));
GetObjectGroups().SerializeTo(element.AddChild("objectsGroups"));
GetVariables().SerializeTo(element.AddChild("variables"));
@@ -1038,28 +1036,18 @@ void Project::SerializeTo(SerializerElement& element) const {
externalSourceFilesElement.AddChild("sourceFile"));
}
void Project::AllowUsageOfUnicodeIdentifierNames(bool enable) {
allowUsageOfUnicodeIdentifierNames = enable;
}
bool Project::IsNameSafe(const gd::String& name) {
if (name.empty()) return false;
if (isdigit(name[0])) return false;
if (!allowUsageOfUnicodeIdentifierNames) {
gd::String legacyAllowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
return !(name.find_first_not_of(legacyAllowedCharacters) != gd::String::npos);
} else {
for (auto character : name) {
if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
return false;
}
for (auto character : name) {
if (!GrammarTerminals::IsAllowedInIdentifier(character)) {
return false;
}
return true;
}
return true;
}
gd::String Project::GetSafeName(const gd::String& name) {
@@ -1069,18 +1057,13 @@ gd::String Project::GetSafeName(const gd::String& name) {
if (isdigit(name[0])) newName = "_" + newName;
gd::String legacyAllowedCharacters =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
for (size_t i = 0;i < newName.size();++i) {
// Note that iterating on the characters is not super efficient (O(n^2), which
// could be avoided with an iterator), but this function is not critical for performance
// (only used to generate a name when a user creates a new entity or rename one).
for (size_t i = 0; i < newName.size(); ++i) {
// Note that iterating on the characters is not super efficient (O(n^2),
// which could be avoided with an iterator), but this function is not
// critical for performance (only used to generate a name when a user
// creates a new entity or rename one).
auto character = newName[i];
bool isAllowed =
allowUsageOfUnicodeIdentifierNames
? GrammarTerminals::IsAllowedInIdentifier(character)
: legacyAllowedCharacters.find(character) != gd::String::npos;
bool isAllowed = GrammarTerminals::IsAllowedInIdentifier(character);
// Replace all unallowed letters by an underscore.
if (!isAllowed) {

View File

@@ -11,12 +11,12 @@
#include "GDCore/Project/ExtensionProperties.h"
#include "GDCore/Project/LoadingScreen.h"
#include "GDCore/Project/Watermark.h"
#include "GDCore/Project/ObjectGroupsContainer.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/PlatformSpecificAssets.h"
#include "GDCore/Project/ResourcesManager.h"
#include "GDCore/Project/VariablesContainer.h"
#include "GDCore/Project/Watermark.h"
#include "GDCore/String.h"
namespace gd {
class Platform;
@@ -82,7 +82,9 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Change the project description
*/
void SetDescription(const gd::String& description_) { description = description_; };
void SetDescription(const gd::String& description_) {
description = description_;
};
/**
* \brief Get the project description
@@ -124,7 +126,9 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Get the author usernames of the project.
*/
const std::vector<gd::String>& GetAuthorUsernames() const { return authorUsernames; };
const std::vector<gd::String>& GetAuthorUsernames() const {
return authorUsernames;
};
/**
* \brief Get the author usernames of the project, to modify them (non-const).
@@ -135,7 +139,9 @@ class GD_CORE_API Project : public ObjectsContainer {
* Define the project as playable with a keyboard.
* \param enable True to define the project as playable with a keyboard.
*/
void SetPlayableWithKeyboard(bool playable = true) { isPlayableWithKeyboard = playable; }
void SetPlayableWithKeyboard(bool playable = true) {
isPlayableWithKeyboard = playable;
}
/**
* Check if the project is defined as playable with a keyboard.
@@ -146,7 +152,9 @@ class GD_CORE_API Project : public ObjectsContainer {
* Define the project as playable with a gamepad.
* \param enable True to define the project as playable with a gamepad.
*/
void SetPlayableWithGamepad(bool playable = true) { isPlayableWithGamepad = playable; }
void SetPlayableWithGamepad(bool playable = true) {
isPlayableWithGamepad = playable;
}
/**
* Check if the project is defined as playable with a gamepad.
@@ -157,7 +165,9 @@ class GD_CORE_API Project : public ObjectsContainer {
* Define the project as playable on a mobile.
* \param enable True to define the project as playable on a mobile.
*/
void SetPlayableWithMobile(bool playable = true) { isPlayableWithMobile = playable; }
void SetPlayableWithMobile(bool playable = true) {
isPlayableWithMobile = playable;
}
/**
* Check if the project is defined as playable on a mobile.
@@ -391,17 +401,23 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* Set the antialiasing mode used by the game ("none" or "MSAA").
*/
void SetAntialiasingMode(const gd::String& antialiasingMode_) { antialiasingMode = antialiasingMode_; }
void SetAntialiasingMode(const gd::String& antialiasingMode_) {
antialiasingMode = antialiasingMode_;
}
/**
* Return true if antialising is enabled on mobiles.
*/
bool IsAntialisingEnabledOnMobile() const { return isAntialisingEnabledOnMobile; }
bool IsAntialisingEnabledOnMobile() const {
return isAntialisingEnabledOnMobile;
}
/**
* Set whether antialising is enabled on mobiles or not.
*/
void SetAntialisingEnabledOnMobile(bool enable) { isAntialisingEnabledOnMobile = enable; }
void SetAntialisingEnabledOnMobile(bool enable) {
isAntialisingEnabledOnMobile = enable;
}
/**
* \brief Return if the project should set 0 as Z-order for objects created
@@ -905,7 +921,8 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Return the events based object with a given type.
*/
const gd::EventsBasedObject& GetEventsBasedObject(const gd::String& type) const;
const gd::EventsBasedObject& GetEventsBasedObject(
const gd::String& type) const;
/**
* \brief Check if events based behavior with a given type exists.
@@ -920,7 +937,8 @@ class GD_CORE_API Project : public ObjectsContainer {
/**
* \brief Return the events based behavior with a given type.
*/
const gd::EventsBasedBehavior& GetEventsBasedBehavior(const gd::String& type) const;
const gd::EventsBasedBehavior& GetEventsBasedBehavior(
const gd::String& type) const;
///@}
@@ -969,20 +987,6 @@ class GD_CORE_API Project : public ObjectsContainer {
*/
///@{
/**
* Check if unicode names are allowed in identifier names.
* \see IsNameSafe
* \see GetSafeName
*/
static bool IsUsageOfUnicodeIdentifierNamesAllowed() { return allowUsageOfUnicodeIdentifierNames; };
/**
* Set if unicode names are allowed in identifier names.
* \see IsNameSafe
* \see GetSafeName
*/
static void AllowUsageOfUnicodeIdentifierNames(bool enable);
/**
* Return true if \a name is valid (can be used safely for an object,
* behavior, events function name, etc...).
@@ -990,8 +994,8 @@ class GD_CORE_API Project : public ObjectsContainer {
static bool IsNameSafe(const gd::String& name);
/**
* Return a name, based on the one passed in parameter, that can be safely used
* for an object, behavior, events function name, etc...
* Return a name, based on the one passed in parameter, that can be safely
* used for an object, behavior, events function name, etc...
*/
static gd::String GetSafeName(const gd::String& name);
///@}
@@ -1068,8 +1072,8 @@ class GD_CORE_API Project : public ObjectsContainer {
bool adaptGameResolutionAtRuntime; ///< Should the game resolution be adapted
///< to the window size at runtime
gd::String
sizeOnStartupMode; ///< How to adapt the game size to the screen. Can be
///< "adaptWidth", "adaptHeight" or empty
sizeOnStartupMode; ///< How to adapt the game size to the screen. Can be
///< "adaptWidth", "adaptHeight" or empty
gd::String antialiasingMode;
bool isAntialisingEnabledOnMobile;
gd::String projectUuid; ///< UUID useful to identify the game in online
@@ -1095,19 +1099,18 @@ class GD_CORE_API Project : public ObjectsContainer {
externalSourceFiles; ///< List of external source files used.
gd::String author; ///< Game author name, for publishing purpose.
std::vector<gd::String>
authorIds; ///< Game author ids, from GDevelop users DB.
authorIds; ///< Game author ids, from GDevelop users DB.
std::vector<gd::String>
authorUsernames; ///< Game author usernames, from GDevelop users DB.
std::vector<gd::String>
categories; ///< Game categories
bool isPlayableWithKeyboard; ///< The project is playable with a keyboard.
bool isPlayableWithGamepad; ///< The project is playable with a gamepad.
bool isPlayableWithMobile; ///< The project is playable on a mobile.
gd::String packageName; ///< Game package name
gd::String templateSlug; ///< The slug of the template from which the game is
///< created.
gd::String orientation; ///< Lock game orientation (on mobile devices).
///< "default", "landscape" or "portrait".
authorUsernames; ///< Game author usernames, from GDevelop users DB.
std::vector<gd::String> categories; ///< Game categories
bool isPlayableWithKeyboard; ///< The project is playable with a keyboard.
bool isPlayableWithGamepad; ///< The project is playable with a gamepad.
bool isPlayableWithMobile; ///< The project is playable on a mobile.
gd::String packageName; ///< Game package name
gd::String templateSlug; ///< The slug of the template from which the game is
///< created.
gd::String orientation; ///< Lock game orientation (on mobile devices).
///< "default", "landscape" or "portrait".
bool
folderProject; ///< True if folder project, false if single file project.
gd::String
@@ -1128,8 +1131,6 @@ class GD_CORE_API Project : public ObjectsContainer {
///< time the project was saved.
mutable unsigned int gdBuildVersion; ///< The GD build version used the last
///< time the project was saved.
static bool allowUsageOfUnicodeIdentifierNames;
};
} // namespace gd

View File

@@ -0,0 +1,174 @@
#pragma once
#include <set>
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "ObjectsContainersList.h"
#include "PropertiesContainersList.h"
#include "VariablesContainersList.h"
namespace gd {
class Project;
class ObjectsContainer;
class ObjectsContainersList;
class VariablesContainersList;
class PropertiesContainersList;
class NamedPropertyDescriptor;
} // namespace gd
namespace gd {
/**
* \brief Holds references to variables, objects, properties and other
* containers.
*
* This is useful to access elements of a project from a specific location,
* honoring the scope of each element.
*
* For example, in an expression, when an identifier is written, this class is
* used to know what this identifier refers too.
*/
class ProjectScopedContainers {
public:
ProjectScopedContainers(
const gd::ObjectsContainersList &objectsContainersList_,
const gd::VariablesContainersList &variablesContainersList_,
const gd::PropertiesContainersList &propertiesContainersList_)
: objectsContainersList(objectsContainersList_),
variablesContainersList(variablesContainersList_),
propertiesContainersList(propertiesContainersList_){};
virtual ~ProjectScopedContainers(){};
static ProjectScopedContainers
MakeNewProjectScopedContainersForProjectAndLayout(const gd::Project &project,
const gd::Layout &layout) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForProjectAndLayout(
project, layout),
VariablesContainersList::
MakeNewVariablesContainersListForProjectAndLayout(project, layout),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
static ProjectScopedContainers MakeNewProjectScopedContainersFor(
const gd::ObjectsContainer &globalObjectsContainers,
const gd::ObjectsContainer &objectsContainers) {
ProjectScopedContainers projectScopedContainers(
ObjectsContainersList::MakeNewObjectsContainersListForContainers(
globalObjectsContainers, objectsContainers),
VariablesContainersList::MakeNewEmptyVariablesContainersList(),
PropertiesContainersList::MakeNewEmptyPropertiesContainersList());
return projectScopedContainers;
}
ProjectScopedContainers &AddPropertiesContainer(
const gd::PropertiesContainer &container) {
propertiesContainersList.Add(container);
return *this;
}
ProjectScopedContainers &AddParameters(
const std::vector<gd::ParameterMetadata> &parameters) {
parametersVectorsList.push_back(&parameters);
return *this;
}
template <class ReturnType>
ReturnType MatchIdentifierWithName(
const gd::String &name,
std::function<ReturnType()> objectCallback,
std::function<ReturnType()> variableCallback,
std::function<ReturnType()> propertyCallback,
std::function<ReturnType()> parameterCallback,
std::function<ReturnType()> notFoundCallback) const {
if (objectsContainersList.HasObjectOrGroupNamed(name))
return objectCallback();
else if (variablesContainersList.Has(name))
return variableCallback();
else if (ParameterMetadataTools::Has(parametersVectorsList, name))
return parameterCallback();
else if (propertiesContainersList.Has(name))
return propertyCallback();
return notFoundCallback();
};
void ForEachIdentifierMatchingSearch(
const gd::String &search,
std::function<void(const gd::String &name,
const ObjectConfiguration *objectConfiguration)>
objectCallback,
std::function<void(const gd::String &name, const gd::Variable &variable)>
variableCallback,
std::function<void(const gd::NamedPropertyDescriptor &property)>
propertyCallback,
std::function<void(const gd::ParameterMetadata &parameter)>
parameterCallback) const {
std::set<gd::String> namesAlreadySeen;
objectsContainersList.ForEachNameMatchingSearch(
search,
[&](const gd::String &name,
const ObjectConfiguration *objectConfiguration) {
if (namesAlreadySeen.count(name) == 0) {
namesAlreadySeen.insert(name);
objectCallback(name, objectConfiguration);
}
});
variablesContainersList.ForEachVariableMatchingSearch(
search, [&](const gd::String &name, const gd::Variable &variable) {
if (namesAlreadySeen.count(name) == 0) {
namesAlreadySeen.insert(name);
variableCallback(name, variable);
}
});
gd::ParameterMetadataTools::ForEachParameterMatchingSearch(
parametersVectorsList,
search,
[&](const gd::ParameterMetadata &parameter) {
if (namesAlreadySeen.count(parameter.GetName()) == 0) {
namesAlreadySeen.insert(parameter.GetName());
parameterCallback(parameter);
}
});
propertiesContainersList.ForEachPropertyMatchingSearch(
search, [&](const gd::NamedPropertyDescriptor &property) {
if (namesAlreadySeen.count(property.GetName()) == 0) {
namesAlreadySeen.insert(property.GetName());
propertyCallback(property);
}
});
};
const gd::ObjectsContainersList &GetObjectsContainersList() const {
return objectsContainersList;
};
const gd::VariablesContainersList &GetVariablesContainersList() const {
return variablesContainersList;
};
const gd::PropertiesContainersList &GetPropertiesContainersList() const {
return propertiesContainersList;
};
const std::vector<const std::vector<gd::ParameterMetadata> *> &GetParametersVectorsList() const {
return parametersVectorsList;
};
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
ProjectScopedContainers(){};
private:
gd::ObjectsContainersList objectsContainersList;
gd::VariablesContainersList variablesContainersList;
gd::PropertiesContainersList propertiesContainersList;
std::vector<const std::vector<gd::ParameterMetadata> *> parametersVectorsList;
};
} // namespace gd

View File

@@ -0,0 +1,50 @@
#pragma once
#include "EventsFunctionsContainer.h"
#include "GDCore/Tools/SerializableWithNameList.h"
#include "NamedPropertyDescriptor.h"
namespace gd {
/**
* \brief A container of properties, used for custom behaviors, custom objects,
* extensions...
*
* \see gd::NamedPropertyDescriptor
*
* \ingroup PlatformDefinition
*/
class PropertiesContainer
: public SerializableWithNameList<NamedPropertyDescriptor> {
public:
PropertiesContainer(EventsFunctionsContainer::FunctionOwner owner)
: SerializableWithNameList<NamedPropertyDescriptor>(), owner(owner) {}
PropertiesContainer(const PropertiesContainer& other)
: SerializableWithNameList<NamedPropertyDescriptor>(other),
owner(other.owner) {}
PropertiesContainer& operator=(const PropertiesContainer& other) {
if (this != &other) {
SerializableWithNameList<NamedPropertyDescriptor>::operator=(other);
owner = other.owner;
}
return *this;
}
void ForEachPropertyMatchingSearch(
const gd::String& search,
std::function<void(const gd::NamedPropertyDescriptor& property)> fn)
const {
for (const auto& property : elements) {
if (property->GetName().FindCaseInsensitive(search) != gd::String::npos)
fn(*property);
}
}
EventsFunctionsContainer::FunctionOwner GetOwner() const { return owner; }
private:
EventsFunctionsContainer::FunctionOwner owner;
};
} // namespace gd

View File

@@ -0,0 +1,67 @@
#include "PropertiesContainersList.h"
#include <functional>
#include <vector>
#include "PropertiesContainer.h"
namespace gd {
NamedPropertyDescriptor PropertiesContainersList::badNamedPropertyDescriptor;
PropertiesContainer PropertiesContainersList::badPropertiesContainer(gd::EventsFunctionsContainer::FunctionOwner::Extension);
PropertiesContainersList
PropertiesContainersList::MakeNewPropertiesContainersListFor(
const gd::PropertiesContainer& propertiesContainer) {
PropertiesContainersList propertiesContainersList;
propertiesContainersList.Add(propertiesContainer);
return propertiesContainersList;
}
PropertiesContainersList
PropertiesContainersList::MakeNewEmptyPropertiesContainersList() {
PropertiesContainersList propertiesContainersList;
return propertiesContainersList;
}
bool PropertiesContainersList::Has(const gd::String& name) const {
for (auto it = propertiesContainers.rbegin();
it != propertiesContainers.rend();
++it) {
if ((*it)->Has(name)) return true;
}
return false;
}
std::pair<std::reference_wrapper<const gd::PropertiesContainer>,
std::reference_wrapper<const NamedPropertyDescriptor>>
PropertiesContainersList::Get(const gd::String& name) const {
for (auto it = propertiesContainers.rbegin();
it != propertiesContainers.rend();
++it) {
if ((*it)->Has(name)) return {**it, (*it)->Get(name)};
}
return {badPropertiesContainer, badNamedPropertyDescriptor};
}
bool PropertiesContainersList::HasPropertiesContainer(const gd::PropertiesContainer& propertiesContainer) const {
for (auto it = propertiesContainers.rbegin(); it != propertiesContainers.rend();
++it) {
if (*it == &propertiesContainer) return true;
}
return false;
}
void PropertiesContainersList::ForEachPropertyMatchingSearch(
const gd::String& search,
std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const {
for (auto it = propertiesContainers.rbegin(); it != propertiesContainers.rend();
++it) {
(*it)->ForEachPropertyMatchingSearch(search, fn);
}
}
} // namespace gd

View File

@@ -0,0 +1,84 @@
#pragma once
#include <functional>
#include <vector>
#include "PropertiesContainer.h"
namespace gd {
class String;
class Project;
class Layout;
class NamedPropertyDescriptor;
} // namespace gd
namespace gd {
/**
* \brief A list of property containers, useful for accessing properties in a
* scoped way.
*
* \see gd::NamedPropertyDescriptor
* \see gd::Project
* \see gd::Layout
*
* \ingroup PlatformDefinition
*/
class GD_CORE_API PropertiesContainersList {
public:
virtual ~PropertiesContainersList(){};
static PropertiesContainersList MakeNewPropertiesContainersListFor(
const gd::PropertiesContainer& propertiesContainer);
static PropertiesContainersList MakeNewEmptyPropertiesContainersList();
/**
* \brief Add a new container of properties in the list.
* Add containers in order from the most global one to the most local one.
*/
void Add(const gd::PropertiesContainer& propertiesContainer) {
propertiesContainers.push_back(&propertiesContainer);
};
/**
* \brief Return true if the specified property is in one of the containers.
*/
bool Has(const gd::String& name) const;
/**
* \brief Return a reference to the property called \a name, and the
* properties container holding it.
*/
std::pair<std::reference_wrapper<const gd::PropertiesContainer>,
std::reference_wrapper<const NamedPropertyDescriptor>>
Get(const gd::String& name) const;
/**
* \brief Return true if the specified properties container is present.
*/
bool HasPropertiesContainer(const gd::PropertiesContainer& propertiesContainer) const;
/**
* Get the properties container at the bottom of the scope (so the most
* "local" one).
*/
const PropertiesContainer* GetBottomMostPropertiesContainer() const {
if (propertiesContainers.empty()) return nullptr;
return propertiesContainers.back();
}
/**
* \brief Call the callback for each property having a name matching the specified search.
*/
void ForEachPropertyMatchingSearch(const gd::String& search, std::function<void(const gd::NamedPropertyDescriptor& property)> fn) const;
/** Do not use - should be private but accessible to let Emscripten create a
* temporary. */
PropertiesContainersList(){};
private:
std::vector<const gd::PropertiesContainer*> propertiesContainers;
static NamedPropertyDescriptor badNamedPropertyDescriptor;
static PropertiesContainer badPropertiesContainer;
};
} // namespace gd

View File

@@ -10,6 +10,7 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/Tools/UUID/UUID.h"
using namespace std;
@@ -228,6 +229,9 @@ void Variable::SerializeTo(SerializerElement& element) const {
element.SetStringAttribute("type", TypeAsString(GetType()));
if (IsFolded()) element.SetBoolAttribute("folded", true);
if (!persistentUuid.empty())
element.SetStringAttribute("persistentUuid", persistentUuid);
if (type == Type::String) {
element.SetStringAttribute("value", GetString());
} else if (type == Type::Number) {
@@ -254,6 +258,8 @@ void Variable::SerializeTo(SerializerElement& element) const {
void Variable::UnserializeFrom(const SerializerElement& element) {
type = StringAsType(element.GetStringAttribute("type", "string"));
persistentUuid = element.GetStringAttribute("persistentUuid");
// Compatibility with GD <= 5.0.0-beta102
// Before, everything was stored as strings.
// We can unserialize primitives as string as they can be converted from/to
@@ -290,7 +296,12 @@ void Variable::UnserializeFrom(const SerializerElement& element) {
PushNew().UnserializeFrom(childElement);
}
}
} // namespace gd
}
Variable& Variable::ResetPersistentUuid() {
persistentUuid = UUID::MakeUuid4();
return *this;
}
std::vector<gd::String> Variable::GetAllChildrenNames() const {
std::vector<gd::String> names;
@@ -338,7 +349,8 @@ Variable::Variable(const Variable& other)
str(other.str),
folded(other.folded),
boolVal(other.boolVal),
type(other.type) {
type(other.type),
persistentUuid(other.persistentUuid) {
CopyChildren(other);
}
@@ -349,6 +361,7 @@ Variable& Variable::operator=(const Variable& other) {
folded = other.folded;
boolVal = other.boolVal;
type = other.type;
persistentUuid = other.persistentUuid;
CopyChildren(other);
}

View File

@@ -338,6 +338,24 @@ class GD_CORE_API Variable {
* \brief Unserialize the variable.
*/
void UnserializeFrom(const SerializerElement& element);
/**
* \brief Reset the persistent UUID used to recognize
* the same variable between serialization.
*/
Variable& ResetPersistentUuid();
/**
* \brief Remove the persistent UUID - when the variable no
* longer needs to be recognized between serializations.
*/
Variable& ClearPersistentUuid() { persistentUuid = ""; return *this; };
/**
* \brief Get the persistent UUID used to recognize
* the same variable between serialization.
*/
const gd::String& GetPersistentUuid() const { return persistentUuid; };
///@}
private:
@@ -361,6 +379,8 @@ class GD_CORE_API Variable {
mutable std::vector<std::shared_ptr<Variable>>
childrenArray; ///< Children, when the variable is considered as an
///< array.
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for computing changesets.
/**
* Initialize children by copying them from another variable. Used by

View File

@@ -12,6 +12,7 @@
#include "GDCore/Serialization/SerializerElement.h"
#include "GDCore/String.h"
#include "GDCore/TinyXml/tinyxml.h"
#include "GDCore/Tools/UUID/UUID.h"
namespace gd {
@@ -161,7 +162,20 @@ void VariablesContainer::Move(std::size_t oldIndex, std::size_t newIndex) {
variables.insert(variables.begin() + newIndex, nameAndVariable);
}
void VariablesContainer::ForEachVariableMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name, const gd::Variable& variable)>
fn) const {
for (const auto& nameAndVariable : variables) {
if (nameAndVariable.first.FindCaseInsensitive(search) != gd::String::npos)
fn(nameAndVariable.first, *nameAndVariable.second);
}
}
void VariablesContainer::SerializeTo(SerializerElement& element) const {
if (!persistentUuid.empty())
element.SetStringAttribute("persistentUuid", persistentUuid);
element.ConsiderAsArrayOf("variable");
for (std::size_t j = 0; j < variables.size(); j++) {
SerializerElement& variableElement = element.AddChild("variable");
@@ -171,6 +185,8 @@ void VariablesContainer::SerializeTo(SerializerElement& element) const {
}
void VariablesContainer::UnserializeFrom(const SerializerElement& element) {
persistentUuid = element.GetStringAttribute("persistentUuid");
Clear();
element.ConsiderAsArrayOf("variable", "Variable");
for (std::size_t j = 0; j < element.GetChildrenCount(); j++) {
@@ -183,6 +199,24 @@ void VariablesContainer::UnserializeFrom(const SerializerElement& element) {
}
}
VariablesContainer& VariablesContainer::ResetPersistentUuid() {
persistentUuid = UUID::MakeUuid4();
for (auto& variable : variables) {
variable.second->ResetPersistentUuid();
}
return *this;
}
VariablesContainer& VariablesContainer::ClearPersistentUuid() {
persistentUuid = "";
for (auto& variable : variables) {
variable.second->ClearPersistentUuid();
}
return *this;
}
VariablesContainer::VariablesContainer(const VariablesContainer& other) {
Init(other);
}
@@ -195,6 +229,7 @@ VariablesContainer& VariablesContainer::operator=(
}
void VariablesContainer::Init(const gd::VariablesContainer& other) {
persistentUuid = other.persistentUuid;
variables.clear();
for (auto& it : other.variables) {
variables.push_back(

View File

@@ -137,6 +137,11 @@ class GD_CORE_API VariablesContainer {
* \brief Clear all variables of the container.
*/
inline void Clear() { variables.clear(); }
/**
* \brief Call the callback for each variable with a name matching the specified search.
*/
void ForEachVariableMatchingSearch(const gd::String& search, std::function<void(const gd::String& name, const gd::Variable& variable)> fn) const;
///@}
/** \name Saving and loading
@@ -152,10 +157,30 @@ class GD_CORE_API VariablesContainer {
* \brief Unserialize the variable container.
*/
void UnserializeFrom(const SerializerElement& element);
/**
* \brief Reset the persistent UUID, used to recognize
* the same variables between serialization.
*/
VariablesContainer& ResetPersistentUuid();
/**
* \brief Remove the persistent UUID - when the variables no
* longer need to be recognized between serializations.
*/
VariablesContainer& ClearPersistentUuid();
/**
* \brief Get the persistent UUID used to recognize
* the same variables between serialization.
*/
const gd::String& GetPersistentUuid() const { return persistentUuid; };
///@}
private:
std::vector<std::pair<gd::String, std::shared_ptr<gd::Variable>>> variables;
mutable gd::String persistentUuid; ///< A persistent random version 4 UUID,
///< useful for computing changesets.
static gd::Variable badVariable;
static gd::String badName;

View File

@@ -0,0 +1,64 @@
#include "VariablesContainersList.h"
#include <vector>
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/Project/Variable.h"
namespace gd {
Variable VariablesContainersList::badVariable;
VariablesContainersList
VariablesContainersList::MakeNewVariablesContainersListForProjectAndLayout(
const gd::Project& project, const gd::Layout& layout) {
VariablesContainersList variablesContainersList;
variablesContainersList.Add(project.GetVariables());
variablesContainersList.Add(layout.GetVariables());
return variablesContainersList;
}
VariablesContainersList
VariablesContainersList::MakeNewEmptyVariablesContainersList() {
VariablesContainersList variablesContainersList;
return variablesContainersList;
}
bool VariablesContainersList::Has(const gd::String& name) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
if ((*it)->Has(name)) return true;
}
return false;
}
const Variable& VariablesContainersList::Get(const gd::String& name) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
if ((*it)->Has(name)) return (*it)->Get(name);
}
return badVariable;
}
bool VariablesContainersList::HasVariablesContainer(const gd::VariablesContainer& variablesContainer) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
if (*it == &variablesContainer) return true;
}
return false;
}
void VariablesContainersList::ForEachVariableMatchingSearch(
const gd::String& search,
std::function<void(const gd::String& name, const gd::Variable& variable)> fn) const {
for (auto it = variablesContainers.rbegin(); it != variablesContainers.rend();
++it) {
(*it)->ForEachVariableMatchingSearch(search, fn);
}
}
} // namespace gd

Some files were not shown because too many files have changed in this diff Show More