Skip to content

Commit

Permalink
Fix default behaviors not added properly to objects in functions
Browse files Browse the repository at this point in the history
  • Loading branch information
4ian committed Nov 29, 2024
1 parent eae75bd commit cadcf34
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 50 deletions.
20 changes: 15 additions & 5 deletions Core/GDCore/Extensions/Metadata/ParameterMetadataTools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
// Keep track of all objects and their behaviors names, so we can remove
// those who are in the container but not in the parameters anymore.
std::set<gd::String> allObjectNames;
std::map<gd::String, std::set<gd::String>> allObjectBehaviorNames;
std::map<gd::String, std::set<gd::String>> allObjectNonDefaultBehaviorNames;

gd::String lastObjectName;
for (std::size_t i = 0; i < parameters.GetParametersCount(); ++i) {
Expand All @@ -49,11 +49,15 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
}

if (outputObjectsContainer.HasObjectNamed(objectName)) {
// Keep the existing object - behaviors will be added or removed later.
// Keep the existing object, ensure the default behaviors
// are all present (and no more than required by the object type).
// Non default behaviors coming from parameters will be added or removed later.
project.EnsureObjectDefaultBehaviors(outputObjectsContainer.GetObject(objectName));
} else {
// Create a new object (and its default behaviors) if needed.
outputObjectsContainer.InsertNewObject(
project,
parameter.GetExtraInfo(),
objectType,
objectName,
outputObjectsContainer.GetObjectsCount());
}
Expand All @@ -71,7 +75,7 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
const gd::String& behaviorType = parameter.GetExtraInfo();

gd::Object& object = outputObjectsContainer.GetObject(lastObjectName);
allObjectBehaviorNames[lastObjectName].insert(behaviorName);
allObjectNonDefaultBehaviorNames[lastObjectName].insert(behaviorName);

// Check if we can keep the existing behavior.
if (object.HasBehaviorNamed(behaviorName)) {
Expand Down Expand Up @@ -108,8 +112,14 @@ void ParameterMetadataTools::ParametersToObjectsContainer(
}

auto& object = outputObjectsContainer.GetObject(objectName);
const auto& allBehaviorNames = allObjectNonDefaultBehaviorNames[objectName];
for (const auto& behaviorName : object.GetAllBehaviorNames()) {
const auto& allBehaviorNames = allObjectBehaviorNames[objectName];
if (object.GetBehavior(behaviorName).IsDefaultBehavior()) {
// Default behaviors are already ensured to be all present
// (and no more than required by the object type).
continue;
}

if (allBehaviorNames.find(behaviorName) == allBehaviorNames.end()) {
object.RemoveBehavior(behaviorName);
}
Expand Down
62 changes: 49 additions & 13 deletions Core/GDCore/Project/Project.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,11 @@ 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));

void Project::EnsureObjectDefaultBehaviors(gd::Object& object) const {
auto& platform = GetCurrentPlatform();
auto& project = *this;
auto& objectType = object.GetType();

auto addDefaultBehavior = [&platform, &project, &object, &objectType](
const gd::String& behaviorType) {
auto& behaviorMetadata =
Expand All @@ -97,24 +95,62 @@ std::unique_ptr<gd::Object> Project::CreateObject(
" has an unknown default behavior: " + behaviorType);
return;
}
auto* behavior = object->AddNewBehavior(
project, behaviorType, behaviorMetadata.GetDefaultName());
behavior->SetDefaultBehavior(true);

const gd::String& behaviorName = behaviorMetadata.GetDefaultName();

// Check if we can keep a behavior that would have been already set up on the object.
if (object.HasBehaviorNamed(behaviorName)) {
const auto& behavior = object.GetBehavior(behaviorName);

if (!behavior.IsDefaultBehavior() || behavior.GetTypeName() != behaviorType) {
// Behavior type has changed, remove it so it is re-created.
object.RemoveBehavior(behaviorName);
}
}

if (!object.HasBehaviorNamed(behaviorName)) {
auto* behavior = object.AddNewBehavior(
project, behaviorType, behaviorName);
behavior->SetDefaultBehavior(true);
}
};

auto &objectMetadata =
gd::MetadataProvider::GetObjectMetadata(platform, objectType);
if (!MetadataProvider::IsBadObjectMetadata(objectMetadata)) {
for (auto &behaviorType : objectMetadata.GetDefaultBehaviors()) {
// Add all default behaviors.
const auto& defaultBehaviorTypes = objectMetadata.GetDefaultBehaviors();
for (auto &behaviorType : defaultBehaviorTypes) {
addDefaultBehavior(behaviorType);
}

// Ensure there are no default behaviors that would not be required left on the object.
for (const auto& behaviorName : object.GetAllBehaviorNames()) {
auto& behavior = object.GetBehavior(behaviorName);
if (!behavior.IsDefaultBehavior()) {
// Non default behaviors are not handled by this function.
continue;
}

if (defaultBehaviorTypes.find(behavior.GetTypeName()) == defaultBehaviorTypes.end()) {
object.RemoveBehavior(behaviorName);
}
}
}
// During project deserialization, event-based object metadata are not yet
// generated. Default behaviors will be added by
// MetadataDeclarationHelper::UpdateCustomObjectDefaultBehaviors
else if (!project.HasEventsBasedObject(objectType)) {
gd::LogWarning("Object: " + name + " has an unknown type: " + 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));

EnsureObjectDefaultBehaviors(*object);

return std::move(object);
}
Expand Down Expand Up @@ -926,8 +962,8 @@ void Project::UnserializeFrom(const SerializerElement& element) {

std::vector<gd::String> Project::GetUnserializingOrderExtensionNames(
const gd::SerializerElement &eventsFunctionsExtensionsElement) {
// Some extension have custom objects, which have child objects coming from other extension.

// Some extension have custom objects, which have child objects coming from other extension.
// These child objects must be loaded completely before the parent custom obejct can be unserialized.
// This implies: an order on the extension unserialization (and no cycles).

Expand All @@ -937,8 +973,8 @@ std::vector<gd::String> Project::GetUnserializingOrderExtensionNames(
for (std::size_t i = 0; i < eventsFunctionsExtensions.size(); ++i) {
remainingExtensionNames[i] = eventsFunctionsExtensions.at(i)->GetName();
}
// Helper allowing to find if an extension has an object that depends on

// Helper allowing to find if an extension has an object that depends on
// at least one other object from another extension that is not loaded yet.
auto isDependentFromRemainingExtensions =
[&remainingExtensionNames](
Expand Down
18 changes: 10 additions & 8 deletions Core/GDCore/Project/Project.h
Original file line number Diff line number Diff line change
Expand Up @@ -523,13 +523,7 @@ class GD_CORE_API Project {
std::unique_ptr<gd::Object> CreateObject(const gd::String& type,
const gd::String& name) const;

/**
* Create an object configuration of the given type.
*
* \param type The type of the object
*/
std::unique_ptr<gd::ObjectConfiguration> CreateObjectConfiguration(
const gd::String& type) const;
void EnsureObjectDefaultBehaviors(gd::Object& object) const;

/**
* Create an event of the given type.
Expand Down Expand Up @@ -1078,12 +1072,20 @@ class GD_CORE_API Project {

/**
* @brief Get the project extensions names in the order they have to be unserialized.
*
*
* Child-objects need the event-based objects they use to be loaded completely
* before they are unserialized.
*/
std::vector<gd::String> GetUnserializingOrderExtensionNames(const gd::SerializerElement &eventsFunctionsExtensionsElement);

/**
* Create an object configuration of the given type.
*
* \param type The type of the object
*/
std::unique_ptr<gd::ObjectConfiguration> CreateObjectConfiguration(
const gd::String& type) const;

gd::String name; ///< Game name
gd::String description; ///< Game description
gd::String version; ///< Game version number (used for some exports)
Expand Down
139 changes: 115 additions & 24 deletions GDevelop.js/__tests__/Core.js
Original file line number Diff line number Diff line change
Expand Up @@ -4295,19 +4295,37 @@ describe('libGD.js', function () {
expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true);
expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(true);

const objectWithoutType = objectsContainer.getObject('MyObjectWithoutType');
expect(objectWithoutType.getType()).toBe(
''
const objectWithoutType = objectsContainer.getObject(
'MyObjectWithoutType'
);
expect(objectWithoutType.getType()).toBe('');
const mySpriteObject = objectsContainer.getObject('MySpriteObject');
expect(objectsContainer.getObject('MySpriteObject').getType()).toBe(
'Sprite'
);

// Check that behaviors were also added.
expect(objectsContainer.getObject('MySpriteObject').getAllBehaviorNames().toJSArray()).toEqual(
['MyFirstBehavior', 'MySecondBehavior']
);
// Check that behaviors were also added AND that default behaviors are also present.
expect(
objectsContainer
.getObject('MySpriteObject')
.getAllBehaviorNames()
.toJSArray()
).toEqual([
'Animation',
'Effect',
'Flippable',
'MyFirstBehavior',
'MySecondBehavior',
'Opacity',
'Resizable',
'Scale',
]);
expect(
objectsContainer
.getObject('MyObjectWithoutType')
.getAllBehaviorNames()
.toJSArray()
).toEqual([]);

// Call a second time and verify no changes.
gd.ParameterMetadataTools.parametersToObjectsContainer(
Expand All @@ -4318,19 +4336,45 @@ describe('libGD.js', function () {
expect(objectsContainer.getObjectsCount()).toBe(2);
expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true);
expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(true);
expect(objectsContainer.getObject('MySpriteObject').getAllBehaviorNames().toJSArray()).toEqual(
['MyFirstBehavior', 'MySecondBehavior']
);
expect(
objectsContainer
.getObject('MySpriteObject')
.getAllBehaviorNames()
.toJSArray()
).toEqual([
'Animation',
'Effect',
'Flippable',
'MyFirstBehavior',
'MySecondBehavior',
'Opacity',
'Resizable',
'Scale',
]);
expect(
objectsContainer
.getObject('MyObjectWithoutType')
.getAllBehaviorNames()
.toJSArray()
).toEqual([]);

// Verify that objects are even stable in memory.
expect(objectWithoutType).toBe(objectsContainer.getObject('MyObjectWithoutType'));
expect(gd.getPointer(objectWithoutType)).toBe(gd.getPointer(objectsContainer.getObject('MyObjectWithoutType')));
expect(objectWithoutType).toBe(
objectsContainer.getObject('MyObjectWithoutType')
);
expect(gd.getPointer(objectWithoutType)).toBe(
gd.getPointer(objectsContainer.getObject('MyObjectWithoutType'))
);
expect(mySpriteObject).toBe(objectsContainer.getObject('MySpriteObject'));
expect(gd.getPointer(mySpriteObject)).toBe(gd.getPointer(objectsContainer.getObject('MySpriteObject')));
expect(gd.getPointer(mySpriteObject)).toBe(
gd.getPointer(objectsContainer.getObject('MySpriteObject'))
);

// Change an object type, rename a behavior and add a new object.
parameters.getParameter("MyObjectWithoutType").setExtraInfo('Sprite');
parameters.getParameter("MySecondBehavior").setName("MyRenamedSecondBehavior");
parameters.getParameter('MyObjectWithoutType').setExtraInfo('Sprite');
parameters
.getParameter('MySecondBehavior')
.setName('MyRenamedSecondBehavior');
parameters
.addNewParameter('MySpriteObject3')
.setType('objectList')
Expand All @@ -4347,19 +4391,58 @@ describe('libGD.js', function () {
expect(objectsContainer.hasObjectNamed('MyObjectWithoutType')).toBe(true);
expect(objectsContainer.hasObjectNamed('MySpriteObject')).toBe(true);
expect(objectsContainer.hasObjectNamed('MySpriteObject3')).toBe(true);
expect(objectsContainer.getObject('MySpriteObject').getAllBehaviorNames().toJSArray()).toEqual(
['MyFirstBehavior', 'MyRenamedSecondBehavior']
expect(
objectsContainer
.getObject('MySpriteObject')
.getAllBehaviorNames()
.toJSArray()
).toEqual([
'Animation',
'Effect',
'Flippable',
'MyFirstBehavior',
'MyRenamedSecondBehavior',
'Opacity',
'Resizable',
'Scale',
]);
const updatedObjectWithoutType = objectsContainer.getObject(
'MyObjectWithoutType'
);
const updatedObjectWithoutType = objectsContainer.getObject('MyObjectWithoutType');
expect(updatedObjectWithoutType.getType()).toEqual('Sprite');
expect(
updatedObjectWithoutType.getAllBehaviorNames().toJSArray()
).toEqual([
'Animation',
'Effect',
'Flippable',
'Opacity',
'Resizable',
'Scale',
]);

const mySpriteObject3 = objectsContainer.getObject('MySpriteObject3');
expect(mySpriteObject3.getType()).toEqual('Sprite');
expect(
mySpriteObject3.getAllBehaviorNames().toJSArray()
).toEqual([
'Animation',
'Effect',
'Flippable',
'Opacity',
'Resizable',
'Scale',
]);

// Verify that objects are changed in memory if changed in parameters.
expect(objectWithoutType).not.toBe(updatedObjectWithoutType);
expect(gd.getPointer(objectWithoutType)).not.toBe(gd.getPointer(updatedObjectWithoutType));
expect(gd.getPointer(objectWithoutType)).not.toBe(
gd.getPointer(updatedObjectWithoutType)
);
expect(mySpriteObject).toBe(objectsContainer.getObject('MySpriteObject'));
expect(gd.getPointer(mySpriteObject)).toBe(gd.getPointer(objectsContainer.getObject('MySpriteObject')));
expect(gd.getPointer(mySpriteObject)).toBe(
gd.getPointer(objectsContainer.getObject('MySpriteObject'))
);

// Remove an object and check that it is removed from the objects container.
parameters.removeParameter('MySpriteObject');
Expand All @@ -4378,10 +4461,18 @@ describe('libGD.js', function () {
expect(objectsContainer.hasObjectNamed('MySpriteObject3')).toBe(true);

// Verify that objects are still stable in memory.
expect(updatedObjectWithoutType).toBe(objectsContainer.getObject('MyObjectWithoutType'));
expect(gd.getPointer(updatedObjectWithoutType)).toBe(gd.getPointer(objectsContainer.getObject('MyObjectWithoutType')));
expect(mySpriteObject3).toBe(objectsContainer.getObject('MySpriteObject3'));
expect(gd.getPointer(mySpriteObject3)).toBe(gd.getPointer(objectsContainer.getObject('MySpriteObject3')));
expect(updatedObjectWithoutType).toBe(
objectsContainer.getObject('MyObjectWithoutType')
);
expect(gd.getPointer(updatedObjectWithoutType)).toBe(
gd.getPointer(objectsContainer.getObject('MyObjectWithoutType'))
);
expect(mySpriteObject3).toBe(
objectsContainer.getObject('MySpriteObject3')
);
expect(gd.getPointer(mySpriteObject3)).toBe(
gd.getPointer(objectsContainer.getObject('MySpriteObject3'))
);

objectsContainer.delete();
eventsFunction.delete();
Expand Down

0 comments on commit cadcf34

Please sign in to comment.