Add support for behaviors based on other behaviors ("behavior composition") (#2781)

* This allows [custom behaviors](http://wiki.compilgames.net/doku.php/gdevelop5/behaviors/events-based-behaviors), that you can create in your project or get from extensions, to require the presence of one or multiple other behaviors on an object.
  * This is an advanced feature that is helpful to create behaviors that are based on other. For example, a behavior "Platformer enemy" using the "Platformer object" behavior and adding specific actions, conditions and logic to make an enemy chase the player.
  * If you create a behavior and want to use this, just go to the properties of this behavior and add a new property. Choose the type "Required Behavior" for this property. You can then use this new behavior in the events of the behavior you just edited.
* To use a behavior based on another, you don't need to do anything special! Just add it to your object as usual: any missing behavior will be added to your object, so you can start using it immediately.
This commit is contained in:
D8H
2021-09-06 19:51:42 +01:00
committed by GitHub
parent 183a0bd08e
commit d40e360b8a
63 changed files with 3427 additions and 195 deletions
@@ -393,4 +393,14 @@ const gd::String& BehaviorMetadata::GetName() const {
return instance->GetTypeName();
}
gd::Behavior& BehaviorMetadata::Get() const {
if (!instance)
gd::LogFatalError(
"Trying to get a behavior from a BehaviorMetadata that has no "
"behavior. This will crash - please double check that the "
"BehaviorMetadata is valid.");
return *instance;
}
} // namespace gd
@@ -240,7 +240,7 @@ class GD_CORE_API BehaviorMetadata {
/**
* \brief Return the associated gd::Behavior, handling behavior contents.
*/
gd::Behavior& Get() const { return *instance; }
gd::Behavior& Get() const;
/**
* \brief Return the associated gd::BehaviorsSharedData, handling behavior
@@ -4,6 +4,7 @@
* reserved. This project is released under the MIT License.
*/
#include "ParameterMetadataTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
@@ -60,6 +61,24 @@ void ParameterMetadataTools::IterateOverParameters(
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
const gd::String& lastObjectName)> fn) {
IterateOverParametersWithIndex(
parameters,
parametersMetadata,
[&fn](const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
fn(parameterMetadata, parameterValue, lastObjectName);
});
}
void ParameterMetadataTools::IterateOverParametersWithIndex(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn) {
gd::String lastObjectName = "";
for (std::size_t pNb = 0; pNb < parametersMetadata.size(); ++pNb) {
const gd::ParameterMetadata& parameterMetadata = parametersMetadata[pNb];
@@ -70,7 +89,7 @@ void ParameterMetadataTools::IterateOverParameters(
? parameterMetadata.GetDefaultValue()
: parameterValue;
fn(parameterMetadata, parameterValueOrDefault, lastObjectName);
fn(parameterMetadata, parameterValueOrDefault, pNb, lastObjectName);
// Memorize the last object name. By convention, parameters that require
// an object (mainly, "objectvar" and "behavior") should be placed after
@@ -35,6 +35,19 @@ class GD_CORE_API ParameterMetadataTools {
const gd::String& parameterValue,
const gd::String& lastObjectName)> fn);
/**
* Iterate over a list of parameters and their values.
* Callback function is called with the parameter metadata, its value
* and if applicable the name of the object it's linked to.
*/
static void IterateOverParametersWithIndex(
const std::vector<gd::Expression>& parameters,
const std::vector<gd::ParameterMetadata>& parametersMetadata,
std::function<void(const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName)> fn);
/**
* Given a parameter, return, if applicable, the index of the object parameter
* it's linked to.
@@ -0,0 +1,164 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival ([email protected]). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
#include <map>
#include <memory>
#include <vector>
#include "GDCore/Events/Event.h"
#include "GDCore/Events/EventsList.h"
#include "GDCore/Events/Parsers/ExpressionParser2.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodePrinter.h"
#include "GDCore/Events/Parsers/ExpressionParser2NodeWorker.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/IDE/Events/ExpressionValidator.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
/**
* \brief Go through the nodes and rename any reference to an object behavior.
*
* \see gd::ExpressionParser2
*/
class GD_CORE_API ExpressionBehaviorRenamer
: public ExpressionParser2NodeWorker {
public:
ExpressionBehaviorRenamer(const gd::ObjectsContainer& globalObjectsContainer_,
const gd::ObjectsContainer& objectsContainer_,
const gd::String& objectName_,
const gd::String& oldBehaviorName_,
const gd::String& newBehaviorName_)
: hasDoneRenaming(false),
globalObjectsContainer(globalObjectsContainer_),
objectsContainer(objectsContainer_),
objectName(objectName_),
oldBehaviorName(oldBehaviorName_),
newBehaviorName(newBehaviorName_){};
virtual ~ExpressionBehaviorRenamer(){};
bool HasDoneRenaming() const { return hasDoneRenaming; }
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 {
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 {}
void OnVisitObjectFunctionNameNode(ObjectFunctionNameNode& node) override {
if (!node.behaviorFunctionName.empty()) {
// Behavior function name
if (node.objectName == objectName && node.objectFunctionOrBehaviorName == oldBehaviorName) {
node.objectFunctionOrBehaviorName = newBehaviorName;
hasDoneRenaming = true;
}
}
}
void OnVisitFunctionCallNode(FunctionCallNode& node) override {
if (!node.behaviorName.empty()) {
// Behavior function call
if (node.objectName == objectName && node.behaviorName == oldBehaviorName) {
node.behaviorName = newBehaviorName;
hasDoneRenaming = true;
}
}
for (auto& parameter : node.parameters) {
parameter->Visit(*this);
}
}
void OnVisitEmptyNode(EmptyNode& node) override {}
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;
const gd::String& newBehaviorName;
};
bool EventsBehaviorRenamer::DoVisitInstruction(gd::Instruction& instruction,
bool isCondition) {
const auto& metadata = isCondition
? gd::MetadataProvider::GetConditionMetadata(
platform, instruction.GetType())
: gd::MetadataProvider::GetActionMetadata(
platform, instruction.GetType());
gd::ParameterMetadataTools::IterateOverParametersWithIndex(
instruction.GetParameters(),
metadata.GetParameters(),
[&](const gd::ParameterMetadata& parameterMetadata,
const gd::String& parameterValue,
size_t parameterIndex,
const gd::String& lastObjectName) {
const gd::String& type = parameterMetadata.type;
if (gd::ParameterMetadata::IsBehavior(type)) {
if (lastObjectName == objectName) {
if (parameterValue == oldBehaviorName) {
instruction.SetParameter(parameterIndex,
gd::Expression(newBehaviorName));
}
}
} else {
gd::ExpressionParser2 parser(
platform, GetGlobalObjectsContainer(), GetObjectsContainer());
auto node =
gd::ParameterMetadata::IsExpression("number", type)
? parser.ParseExpression("number", parameterValue)
: (gd::ParameterMetadata::IsExpression("string", type)
? parser.ParseExpression("string", parameterValue)
: std::unique_ptr<gd::ExpressionNode>());
if (node) {
ExpressionBehaviorRenamer renamer(GetGlobalObjectsContainer(),
GetObjectsContainer(),
objectName,
oldBehaviorName,
newBehaviorName);
node->Visit(renamer);
if (renamer.HasDoneRenaming()) {
instruction.SetParameter(
parameterIndex,
ExpressionParser2NodePrinter::PrintNode(*node));
}
}
}
});
return false;
}
EventsBehaviorRenamer::~EventsBehaviorRenamer() {}
} // namespace gd
@@ -0,0 +1,52 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival ([email protected]). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef EventsBehaviorRenamer_H
#define EventsBehaviorRenamer_H
#include <map>
#include <memory>
#include <vector>
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/String.h"
namespace gd {
class BaseEvent;
class Platform;
class EventsList;
} // namespace gd
namespace gd {
/**
* \brief Replace in expressions and in parameters of actions or conditions, references
* to the name of a behavior of an object by another name.
*
* \ingroup IDE
*/
class GD_CORE_API EventsBehaviorRenamer : public ArbitraryEventsWorkerWithContext {
public:
EventsBehaviorRenamer(const gd::Platform &platform_,
const gd::String& objectName_,
const gd::String& oldBehaviorName_,
const gd::String& newBehaviorName_) :
platform(platform_),
objectName(objectName_),
oldBehaviorName(oldBehaviorName_),
newBehaviorName(newBehaviorName_)
{};
virtual ~EventsBehaviorRenamer();
private:
bool DoVisitInstruction(gd::Instruction &instruction,
bool isCondition) override;
const gd::Platform &platform;
gd::String objectName;
gd::String oldBehaviorName;
gd::String newBehaviorName;
};
} // namespace gd
#endif // EventsBehaviorRenamer_H
+42 -1
View File
@@ -4,16 +4,20 @@
* reserved. This project is released under the MIT License.
*/
#include "EventsFunctionTools.h"
#include "GDCore/Events/Expression.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/EventsFunction.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/ObjectsContainer.h"
#include "GDCore/Project/Project.h"
#include "GDCore/String.h"
#include "GDCore/Tools/Log.h"
namespace gd {
void EventsFunctionTools::EventsFunctionToObjectsContainer(
void EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
@@ -31,4 +35,41 @@ void EventsFunctionTools::EventsFunctionToObjectsContainer(
outputObjectsContainer.GetObjectGroups() = eventsFunction.GetObjectGroups();
}
void EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
gd::ObjectsContainer& outputObjectsContainer) {
// The context is build the same way as free function...
FreeEventsFunctionToObjectsContainer(project,
eventsFunction,
outputGlobalObjectsContainer,
outputObjectsContainer);
// ...and has an "Object" by convention...
if (!outputObjectsContainer.HasObjectNamed("Object")) {
gd::LogWarning("No \"Object\" in a function of an events based behavior: " +
eventsFunction.GetName() +
". This means this function is likely misconfigured (check "
"its parameters).");
return;
}
// ...with behaviors from properties.
gd::Object& thisObject = outputObjectsContainer.GetObject("Object");
for (size_t i = 0;
i < eventsBasedBehavior.GetPropertyDescriptors().GetCount();
i++) {
const NamedPropertyDescriptor& propertyDescriptor =
eventsBasedBehavior.GetPropertyDescriptors().Get(i);
const std::vector<gd::String>& extraInfo =
propertyDescriptor.GetExtraInfo();
if (propertyDescriptor.GetType() == "Behavior" && extraInfo.size() > 0) {
gd::String behaviorName = propertyDescriptor.GetName();
thisObject.AddNewBehavior(project, extraInfo.at(0), behaviorName);
}
}
}
} // namespace gd
+18 -2
View File
@@ -13,6 +13,7 @@ class Project;
class ObjectsContainer;
class ParameterMetadata;
class EventsFunction;
class EventsBasedBehavior;
class Expression;
} // namespace gd
@@ -23,18 +24,33 @@ namespace gd {
class GD_CORE_API EventsFunctionTools {
public:
/**
* \brief Given an events function, initialize the given objects container
* \brief Given a free events function, initialize the given objects container
* with objects described in the events function parameters and in
* the events function groups.
*
* This is useful to create the "context" of a function, before code
* generation for example.
*/
static void EventsFunctionToObjectsContainer(
static void FreeEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
gd::ObjectsContainer& outputObjectsContainer);
/**
* \brief Given a behavior events function, initialize the given objects container
* with objects described in the events function parameters, in
* the events function groups and in the behavior properties (for additional
* required behaviors on the object).
*
* This is useful to create the "context" of a function, before code
* generation for example.
*/
static void BehaviorEventsFunctionToObjectsContainer(
gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction,
gd::ObjectsContainer& outputGlobalObjectsContainer,
gd::ObjectsContainer& outputObjectsContainer);
};
} // namespace gd
@@ -0,0 +1,12 @@
/*
* GDevelop Core
* Copyright 2008-2021 Florian Rival ([email protected]). All rights
* reserved. This project is released under the MIT License.
*/
#include "UnfilledRequiredBehaviorPropertyProblem.h"
namespace gd {
UnfilledRequiredBehaviorPropertyProblem::~UnfilledRequiredBehaviorPropertyProblem(){};
} // namespace gd
@@ -0,0 +1,80 @@
/*
* GDevelop Core
* Copyright 2008-2021 Florian Rival ([email protected]). All rights
* reserved. This project is released under the MIT License.
*/
#ifndef GDCORE_UNFILLEDREQUIREDBEHAVIORPROPERTYPROBLEM_H
#define GDCORE_UNFILLEDREQUIREDBEHAVIORPROPERTYPROBLEM_H
#include "GDCore/String.h"
namespace gd {
class Project;
class Object;
class BehaviorContent;
} // namespace gd
namespace gd {
/**
* \brief A problem when a required behavior property is not fill correctly.
*/
class GD_CORE_API UnfilledRequiredBehaviorPropertyProblem {
public:
UnfilledRequiredBehaviorPropertyProblem(
const gd::Project& sourceProject_,
gd::Object& sourceObject_,
gd::BehaviorContent& sourceBehaviorContent_,
const gd::String& sourcePropertyName_,
const gd::String& expectedBehaviorTypeName_)
: sourceProject(sourceProject_),
sourceObject(sourceObject_),
sourceBehaviorContent(sourceBehaviorContent_),
sourcePropertyName(sourcePropertyName_),
expectedBehaviorTypeName(expectedBehaviorTypeName_){};
virtual ~UnfilledRequiredBehaviorPropertyProblem();
/**
* \brief Return the project where the problem appears
*/
virtual const gd::Project& GetSourceProject() const { return sourceProject; }
/**
* \brief Return the object where the problem appears.
*/
virtual gd::Object& GetSourceObject() const { return sourceObject; }
/**
* \brief Return the behavior where the problem appears.
*/
virtual gd::BehaviorContent& GetSourceBehaviorContent() const {
return sourceBehaviorContent;
}
/**
* \brief Return the property where the problem appears.
*/
virtual const gd::String& GetSourcePropertyName() const {
return sourcePropertyName;
}
/**
* \brief Return the behavior type name that is expected for the required
* behavior property.
*/
virtual const gd::String& GetExpectedBehaviorTypeName() const {
return expectedBehaviorTypeName;
}
private:
const gd::Project& sourceProject;
gd::Object& sourceObject;
gd::BehaviorContent& sourceBehaviorContent;
const gd::String sourcePropertyName;
const gd::String expectedBehaviorTypeName;
};
} // namespace gd
#endif // GDCORE_UNFILLEDREQUIREDBEHAVIORPROPERTYPROBLEM_H
+345 -48
View File
@@ -5,9 +5,12 @@
*/
#include "WholeProjectRefactorer.h"
#include "GDCore/Extensions/Metadata/BehaviorMetadata.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/DependenciesAnalyzer.h"
#include "GDCore/IDE/Events/ArbitraryEventsWorker.h"
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
#include "GDCore/IDE/Events/EventsRefactorer.h"
#include "GDCore/IDE/Events/ExpressionsParameterMover.h"
#include "GDCore/IDE/Events/ExpressionsRenamer.h"
@@ -15,6 +18,9 @@
#include "GDCore/IDE/Events/InstructionsTypeRenamer.h"
#include "GDCore/IDE/EventsFunctionTools.h"
#include "GDCore/IDE/Project/ArbitraryObjectsWorker.h"
#include "GDCore/IDE/UnfilledRequiredBehaviorPropertyProblem.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/BehaviorContent.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
@@ -51,6 +57,10 @@ gd::String GetBehaviorFullType(const gd::String& extensionName,
namespace gd {
// By convention, the first parameter of an events based behavior method is
// always called "Object".
const gd::String WholeProjectRefactorer::behaviorObjectParameterName = "Object";
void WholeProjectRefactorer::ExposeProjectEvents(
gd::Project& project, gd::ArbitraryEventsWorker& worker) {
// See also gd::Project::ExposeResources for a method that traverse the whole
@@ -115,7 +125,7 @@ void WholeProjectRefactorer::ExposeProjectEvents(
for (auto&& eventsFunction : eventsFunctionsExtension.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::EventsFunctionToObjectsContainer(
gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
project, *eventsFunction, globalObjectsAndGroups, objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(),
@@ -127,22 +137,31 @@ void WholeProjectRefactorer::ExposeProjectEvents(
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
auto& behaviorEventsFunctions = eventsBasedBehavior->GetEventsFunctions();
for (auto&& eventsFunction :
behaviorEventsFunctions.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::EventsFunctionToObjectsContainer(
project, *eventsFunction, globalObjectsAndGroups, objectsAndGroups);
worker.Launch(eventsFunction->GetEvents(),
globalObjectsAndGroups,
objectsAndGroups);
}
ExposeEventsBasedBehaviorEvents(project, *eventsBasedBehavior, worker);
}
}
}
void WholeProjectRefactorer::ExposeEventsBasedBehaviorEvents(
gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
gd::ArbitraryEventsWorkerWithContext& worker) {
auto& behaviorEventsFunctions = eventsBasedBehavior.GetEventsFunctions();
for (auto&& eventsFunction : behaviorEventsFunctions.GetInternalVector()) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer(
project,
eventsBasedBehavior,
*eventsFunction,
globalObjectsAndGroups,
objectsAndGroups);
worker.Launch(
eventsFunction->GetEvents(), globalObjectsAndGroups, objectsAndGroups);
}
}
void WholeProjectRefactorer::ExposeProjectObjects(
gd::Project& project, gd::ArbitraryObjectsWorker& worker) {
worker.Launch(project);
@@ -192,7 +211,7 @@ void WholeProjectRefactorer::EnsureBehaviorEventsFunctionsProperParameters(
parameters[0]
.SetType("object")
.SetName("Object")
.SetName(behaviorObjectParameterName)
.SetDescription("Object")
.SetExtraInfo(eventsBasedBehavior.GetObjectType());
parameters[1]
@@ -470,7 +489,7 @@ void WholeProjectRefactorer::MoveBehaviorEventsFunctionParameter(
}
}
void WholeProjectRefactorer::RenameBehaviorProperty(
void WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior,
@@ -479,41 +498,294 @@ void WholeProjectRefactorer::RenameBehaviorProperty(
auto& properties = eventsBasedBehavior.GetPropertyDescriptors();
if (!properties.Has(oldPropertyName)) return;
// Order is important: we first rename the expressions then the instructions,
// to avoid being unable to fetch the metadata (the types of parameters) of
// instructions after they are renamed.
gd::ExpressionsRenamer expressionRenamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
expressionRenamer.SetReplacedBehaviorExpression(
GetBehaviorFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName()),
EventsBasedBehavior::GetPropertyExpressionName(oldPropertyName),
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
ExposeProjectEvents(project, expressionRenamer);
if (properties.Get(oldPropertyName).GetType() == "Behavior") {
// This is a property representing another behavior that must exist on the
// object.
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyActionName(oldPropertyName)),
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyActionName(newPropertyName)));
ExposeProjectEvents(project, actionRenamer);
// This other "required behavior" uses the property name, that is about to
// change, as its name.
// So we must change all reference to this name in the events of the
// behavior functions.
gd::EventsBehaviorRenamer behaviorRenamer(project.GetCurrentPlatform(),
behaviorObjectParameterName,
oldPropertyName,
newPropertyName);
gd::InstructionsTypeRenamer conditionRenamer = gd::InstructionsTypeRenamer(
project,
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyConditionName(oldPropertyName)),
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyConditionName(newPropertyName)));
ExposeProjectEvents(project, conditionRenamer);
ExposeEventsBasedBehaviorEvents(
project, eventsBasedBehavior, behaviorRenamer);
} else {
// Properties that represent primitive values will be used through
// their related actions/conditions/expressions. Rename these.
// Order is important: we first rename the expressions then the
// instructions, to avoid being unable to fetch the metadata (the types of
// parameters) of instructions after they are renamed.
gd::ExpressionsRenamer expressionRenamer =
gd::ExpressionsRenamer(project.GetCurrentPlatform());
expressionRenamer.SetReplacedBehaviorExpression(
GetBehaviorFullType(eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName()),
EventsBasedBehavior::GetPropertyExpressionName(oldPropertyName),
EventsBasedBehavior::GetPropertyExpressionName(newPropertyName));
ExposeProjectEvents(project, expressionRenamer);
gd::InstructionsTypeRenamer actionRenamer = gd::InstructionsTypeRenamer(
project,
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyActionName(oldPropertyName)),
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyActionName(newPropertyName)));
ExposeProjectEvents(project, actionRenamer);
gd::InstructionsTypeRenamer conditionRenamer = gd::InstructionsTypeRenamer(
project,
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyConditionName(oldPropertyName)),
GetBehaviorEventsFunctionFullType(
eventsFunctionsExtension.GetName(),
eventsBasedBehavior.GetName(),
EventsBasedBehavior::GetPropertyConditionName(newPropertyName)));
ExposeProjectEvents(project, conditionRenamer);
}
}
void WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
gd::Project& project,
gd::Object& object,
const gd::String& behaviorType,
const gd::String& behaviorName) {
if (object.AddNewBehavior(project, behaviorType, behaviorName) == nullptr) {
// The behavior type/metadata can't be found.
return;
};
const gd::Platform& platform = project.GetCurrentPlatform();
const gd::BehaviorMetadata& behaviorMetadata =
MetadataProvider::GetBehaviorMetadata(platform, behaviorType);
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
// Should not happen because the behavior was added successfully (so its
// metadata are valid) - but double check anyway and bail out if the
// behavior metadata are invalid.
return;
}
gd::Behavior& behavior = behaviorMetadata.Get();
gd::BehaviorContent& behaviorContent = object.GetBehavior(behaviorName);
for (auto const& keyValue :
behavior.GetProperties(behaviorContent.GetContent())) {
const gd::String& propertyName = keyValue.first;
const gd::PropertyDescriptor& property = keyValue.second;
if (property.GetType().LowerCase() == "behavior") {
const std::vector<gd::String>& extraInfo = property.GetExtraInfo();
if (extraInfo.size() == 0) {
// very unlikely
continue;
}
const gd::String& requiredBehaviorType = extraInfo.at(0);
const auto behaviorContents =
WholeProjectRefactorer::GetBehaviorsWithType(object,
requiredBehaviorType);
const gd::String* defaultBehaviorName = nullptr;
if (behaviorContents.size() == 0) {
const gd::BehaviorMetadata& requiredBehaviorMetadata =
MetadataProvider::GetBehaviorMetadata(platform,
requiredBehaviorType);
const gd::String& requiredBehaviorName =
requiredBehaviorMetadata.GetDefaultName();
WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
project, object, requiredBehaviorType, requiredBehaviorName);
defaultBehaviorName = &requiredBehaviorName;
} else {
defaultBehaviorName = &behaviorContents.at(0);
}
behavior.UpdateProperty(
behaviorContent.GetContent(), propertyName, *defaultBehaviorName);
}
}
}
std::vector<gd::String> WholeProjectRefactorer::GetBehaviorsWithType(
const gd::Object& object, const gd::String& type) {
std::vector<gd::String> behaviors;
for (auto& behaviorName : object.GetAllBehaviorNames()) {
const gd::BehaviorContent& behaviorContent =
object.GetBehavior(behaviorName);
if (behaviorContent.GetTypeName() == type) {
behaviors.push_back(behaviorName);
}
}
return behaviors;
}
std::vector<gd::String> WholeProjectRefactorer::FindDependentBehaviorNames(
const gd::Project& project,
const gd::Object& object,
const gd::String& behaviorName) {
std::unordered_set<gd::String> dependentBehaviorNames;
WholeProjectRefactorer::FindDependentBehaviorNames(
project, object, behaviorName, dependentBehaviorNames);
std::vector<gd::String> results;
results.insert(results.end(),
dependentBehaviorNames.begin(),
dependentBehaviorNames.end());
return results;
}
void WholeProjectRefactorer::FindDependentBehaviorNames(
const gd::Project& project,
const gd::Object& object,
const gd::String& behaviorName,
std::unordered_set<gd::String>& dependentBehaviorNames) {
const gd::Platform& platform = project.GetCurrentPlatform();
for (auto const& objectBehaviorName : object.GetAllBehaviorNames()) {
const gd::BehaviorContent& behaviorContent =
object.GetBehavior(objectBehaviorName);
const auto& behaviorMetadata = MetadataProvider::GetBehaviorMetadata(
platform, behaviorContent.GetTypeName());
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
// Ignore this behavior as it's unknown.
continue;
}
gd::Behavior& behavior = behaviorMetadata.Get();
for (auto const& keyValue :
behavior.GetProperties(behaviorContent.GetContent())) {
const gd::String& propertyName = keyValue.first;
const gd::PropertyDescriptor& property = keyValue.second;
if (property.GetType().LowerCase() == "behavior" &&
property.GetValue() == behaviorName &&
dependentBehaviorNames.find(objectBehaviorName) ==
dependentBehaviorNames.end()) {
dependentBehaviorNames.insert(objectBehaviorName);
WholeProjectRefactorer::FindDependentBehaviorNames(
project, object, objectBehaviorName, dependentBehaviorNames);
}
}
}
};
std::vector<gd::UnfilledRequiredBehaviorPropertyProblem>
WholeProjectRefactorer::FindInvalidRequiredBehaviorProperties(
const gd::Project& project) {
std::vector<gd::UnfilledRequiredBehaviorPropertyProblem>
invalidRequiredBehaviorProperties;
auto findInvalidRequiredBehaviorPropertiesInObjects =
[&project, &invalidRequiredBehaviorProperties](
const std::vector<std::unique_ptr<gd::Object> >& objectsList) {
for (auto& object : objectsList) {
for (auto& behaviorContentKeyValuePair :
object->GetAllBehaviorContents()) {
gd::BehaviorContent& behaviorContent =
*behaviorContentKeyValuePair.second;
const auto& behaviorMetadata =
gd::MetadataProvider::GetBehaviorMetadata(
project.GetCurrentPlatform(),
behaviorContent.GetTypeName());
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
std::cout << "Could not find metadata for behavior with type \""
<< behaviorContent.GetTypeName() << "\"" << std::endl;
continue;
}
const auto& behavior = behaviorMetadata.Get();
for (auto const& keyValue :
behavior.GetProperties(behaviorContent.GetContent())) {
const gd::String& propertyName = keyValue.first;
const gd::PropertyDescriptor& property = keyValue.second;
if (property.GetType().LowerCase() != "behavior") {
continue;
}
const gd::String& requiredBehaviorName = property.GetValue();
const std::vector<gd::String>& extraInfo =
property.GetExtraInfo();
if (extraInfo.size() == 0) {
// very unlikely
continue;
}
const gd::String& requiredBehaviorType = extraInfo.at(0);
if (requiredBehaviorName == "" ||
!object->HasBehaviorNamed(requiredBehaviorName) ||
object->GetBehavior(requiredBehaviorName).GetTypeName() !=
requiredBehaviorType) {
auto problem = UnfilledRequiredBehaviorPropertyProblem(
project,
*object,
behaviorContent,
propertyName,
requiredBehaviorType);
invalidRequiredBehaviorProperties.push_back(problem);
}
}
}
}
};
// Find in global objects
findInvalidRequiredBehaviorPropertiesInObjects(project.GetObjects());
// Find in layout objects.
for (std::size_t i = 0; i < project.GetLayoutsCount(); ++i) {
const gd::Layout& layout = project.GetLayout(i);
findInvalidRequiredBehaviorPropertiesInObjects(layout.GetObjects());
}
return invalidRequiredBehaviorProperties;
}
bool WholeProjectRefactorer::FixInvalidRequiredBehaviorProperties(
gd::Project& project) {
const auto& invalidRequiredBehaviorProblems =
FindInvalidRequiredBehaviorProperties(project);
for (const auto& problem : invalidRequiredBehaviorProblems) {
auto& object = problem.GetSourceObject();
auto suggestedBehaviorNames =
GetBehaviorsWithType(object, problem.GetExpectedBehaviorTypeName());
auto& behaviorContent = problem.GetSourceBehaviorContent();
auto& behaviorMetadata = MetadataProvider::GetBehaviorMetadata(
project.GetCurrentPlatform(), behaviorContent.GetTypeName());
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
continue;
}
auto& behavior = behaviorMetadata.Get();
if (suggestedBehaviorNames.empty()) {
// No matching behavior on the object.
// Add required behaviors on the object.
auto& expectedBehaviorMetadata = MetadataProvider::GetBehaviorMetadata(
project.GetCurrentPlatform(), problem.GetExpectedBehaviorTypeName());
if (MetadataProvider::IsBadBehaviorMetadata(behaviorMetadata)) {
continue;
}
const gd::String& newBehaviorName =
expectedBehaviorMetadata.GetDefaultName();
AddBehaviorAndRequiredBehaviors(project,
object,
problem.GetExpectedBehaviorTypeName(),
newBehaviorName);
behavior.UpdateProperty(behaviorContent.GetContent(),
problem.GetSourcePropertyName(),
newBehaviorName);
} else {
// There is a matching behavior on the object use it by default.
behavior.UpdateProperty(
behaviorContent.GetContent(),
problem.GetSourcePropertyName(),
// It's unlikely the object has 2 behaviors of the same type.
suggestedBehaviorNames[0]);
}
}
return !invalidRequiredBehaviorProblems.empty();
}
void WholeProjectRefactorer::RenameEventsBasedBehavior(
@@ -678,6 +950,31 @@ void WholeProjectRefactorer::DoRenameBehavior(
}
};
// Rename behavior in required behavior properties
for (std::size_t e = 0; e < project.GetEventsFunctionsExtensionsCount();
e++) {
auto& eventsFunctionsExtension = project.GetEventsFunctionsExtension(e);
for (auto&& eventsBasedBehavior :
eventsFunctionsExtension.GetEventsBasedBehaviors()
.GetInternalVector()) {
for (size_t i = 0;
i < eventsBasedBehavior->GetPropertyDescriptors().GetCount();
i++) {
NamedPropertyDescriptor& propertyDescriptor =
eventsBasedBehavior->GetPropertyDescriptors().Get(i);
std::vector<gd::String>& extraInfo = propertyDescriptor.GetExtraInfo();
if (propertyDescriptor.GetType() == "Behavior" &&
extraInfo.size() > 0) {
const gd::String& requiredBehaviorType = extraInfo[0];
if (requiredBehaviorType == oldBehaviorType) {
extraInfo[0] = newBehaviorType;
}
}
}
}
}
// Rename behavior in global objects
renameBehaviorTypeInObjects(project.GetObjects());
+70 -5
View File
@@ -6,10 +6,13 @@
#ifndef GDCORE_WHOLEPROJECTREFACTORER_H
#define GDCORE_WHOLEPROJECTREFACTORER_H
#include <set>
#include <unordered_set>
#include <vector>
namespace gd {
class Platform;
class Project;
class Layout;
class Object;
class String;
class EventsFunctionsExtension;
class EventsFunction;
@@ -18,6 +21,10 @@ class EventsBasedBehavior;
class ArbitraryEventsWorker;
class ArbitraryObjectsWorker;
class ArbitraryEventsWorkerWithContext;
class Behavior;
class BehaviorContent;
class BehaviorMetadata;
class UnfilledRequiredBehaviorPropertyProblem;
} // namespace gd
namespace gd {
@@ -51,13 +58,24 @@ class GD_CORE_API WholeProjectRefactorer {
gd::ArbitraryEventsWorkerWithContext& worker);
/**
* \brief Call the specified worker on all ObjectContainers of the project (global,
* layouts...)
* \brief Call the specified worker on all events of the events based behavior
*
* This should be the preferred way to traverse all the events of an events
* based behavior
*/
static void ExposeEventsBasedBehaviorEvents(
gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
gd::ArbitraryEventsWorkerWithContext& worker);
/**
* \brief Call the specified worker on all ObjectContainers of the project
* (global, layouts...)
*
* This should be the preferred way to traverse all the objects of a project.
*/
static void ExposeProjectObjects(
gd::Project& project, gd::ArbitraryObjectsWorker& worker);
static void ExposeProjectObjects(gd::Project& project,
gd::ArbitraryObjectsWorker& worker);
/**
* \brief Refactor the project **before** an events function extension is
@@ -140,13 +158,52 @@ class GD_CORE_API WholeProjectRefactorer {
* This is because the property is expected to have its old name for the
* refactoring.
*/
static void RenameBehaviorProperty(
static void RenameEventsBasedBehaviorProperty(
gd::Project& project,
const gd::EventsFunctionsExtension& eventsFunctionsExtension,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::String& oldPropertyName,
const gd::String& newPropertyName);
/**
* \brief Add a behavior to an object and add required behaviors if necessary
* to fill every behavior properties of the added behaviors.
*/
static void AddBehaviorAndRequiredBehaviors(gd::Project& project,
gd::Object& object,
const gd::String& behaviorType,
const gd::String& behaviorName);
/**
* \brief Find every behavior of the object that needs the given behaviors
* directly or indirectly (because of "required behavior" properties).
*/
static std::vector<gd::String> FindDependentBehaviorNames(
const gd::Project& project,
const gd::Object& object,
const gd::String& behaviorName);
/**
* \brief Find the names of the behaviors with the specified type on the object.
*/
static std::vector<gd::String> GetBehaviorsWithType(const gd::Object& object,
const gd::String& type);
/**
* \brief Find in the project objects having behaviors with "behavior" properties that
* don't have a valid value (i.e: pointing to a non existing behavior, or of a wrong type).
*/
static std::vector<gd::UnfilledRequiredBehaviorPropertyProblem>
FindInvalidRequiredBehaviorProperties(const gd::Project& project);
/**
* \brief Fix in the project objects having behaviors with "behavior" properties that
* don't have a valid value (i.e: pointing to a non existing behavior, or of a wrong type),
* by setting a proper behavior, or adding missing behaviors to these objects.
*/
static bool
FixInvalidRequiredBehaviorProperties(gd::Project& project);
/**
* \brief Refactor the project **before** a behavior is renamed.
*
@@ -267,6 +324,14 @@ class GD_CORE_API WholeProjectRefactorer {
const gd::String& oldBehaviorType,
const gd::String& newBehaviorType);
static void FindDependentBehaviorNames(
const gd::Project& project,
const gd::Object& object,
const gd::String& behaviorName,
std::unordered_set<gd::String>& dependentBehaviorNames);
static const gd::String behaviorObjectParameterName;
WholeProjectRefactorer(){};
};
+5
View File
@@ -100,9 +100,14 @@ class GD_CORE_API PropertyDescriptor {
const gd::String& GetType() const { return type; }
const gd::String& GetLabel() const { return label; }
const gd::String& GetDescription() const { return description; }
const std::vector<gd::String>& GetExtraInfo() const {
return extraInformation;
}
std::vector<gd::String>& GetExtraInfo() {
return extraInformation;
}
/**
* \brief Set if the property should be shown or hidden in the editor.
+8 -4
View File
@@ -9,19 +9,23 @@
namespace gd {
void GD_CORE_API LogWarning(const gd::String& msg) {
std::cout << "WARNING: " << msg;
std::cout << "WARNING: " << msg << std::endl;
}
void GD_CORE_API LogError(const gd::String& msg) {
std::cout << "ERROR: " << msg;
std::cout << "ERROR: " << msg << std::endl;
}
void GD_CORE_API LogFatalError(const gd::String& msg) {
std::cout << "FATAL ERROR: " << msg << std::endl;
}
void GD_CORE_API LogMessage(const gd::String& msg) {
std::cout << "MESSAGE: " << msg;
std::cout << "MESSAGE: " << msg << std::endl;
}
void GD_CORE_API LogStatus(const gd::String& msg) {
std::cout << "STATUS: " << msg;
std::cout << "STATUS: " << msg << std::endl;
}
} // namespace gd
+7
View File
@@ -22,11 +22,18 @@ void GD_CORE_API LogWarning(const gd::String& msg);
*/
void GD_CORE_API LogError(const gd::String& msg);
/**
* \brief Standard function that should be used when emitting an error to be
* displayed to the user before a crash/undefined behavior.
*/
void GD_CORE_API LogFatalError(const gd::String& msg);
/**
* \brief Standard function that should be used when emitting a message to be
* displayed to the user.
*/
void GD_CORE_API LogMessage(const gd::String& msg);
/**
* \brief Standard function that should be used when emitting a status message
* to be displayed to the user.
+162 -38
View File
@@ -12,8 +12,76 @@
#include "GDCore/Tools/Localization.h"
#include "catch.hpp"
void SetupProjectWithDummyPlatform(gd::Project &project,
gd::Platform &platform) {
class BehaviorWithRequiredBehaviorProperty : public gd::Behavior {
public:
BehaviorWithRequiredBehaviorProperty() {};
virtual ~BehaviorWithRequiredBehaviorProperty(){};
virtual Behavior* Clone() const override {
return new BehaviorWithRequiredBehaviorProperty(*this);
}
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
const gd::SerializerElement& behaviorContent) const override {
std::map<gd::String, gd::PropertyDescriptor> properties;
properties["requiredBehaviorProperty"]
.SetLabel("A required behavior")
.SetValue(behaviorContent.GetStringAttribute("requiredBehaviorProperty"))
.SetType("Behavior")
.AddExtraInfo("MyExtension::MyBehavior");
return properties;
}
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value) override {
if (name == _("requiredBehaviorProperty")) {
behaviorContent.SetAttribute("requiredBehaviorProperty", value);
return true;
}
return false;
}
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override {
behaviorContent.SetAttribute("requiredBehaviorProperty", "");
}
};
class BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior
: public gd::Behavior {
public:
BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior() {};
virtual ~BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior(){};
virtual Behavior* Clone() const override {
return new BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior(
*this);
}
virtual std::map<gd::String, gd::PropertyDescriptor> GetProperties(
const gd::SerializerElement& behaviorContent) const override {
std::map<gd::String, gd::PropertyDescriptor> properties;
properties["requiredBehaviorProperty"]
.SetLabel("A required behavior")
.SetValue(behaviorContent.GetStringAttribute("requiredBehaviorProperty"))
.SetType("Behavior")
.AddExtraInfo("MyExtension::BehaviorWithRequiredBehaviorProperty");
return properties;
}
virtual bool UpdateProperty(gd::SerializerElement& behaviorContent,
const gd::String& name,
const gd::String& value) override {
if (name == _("requiredBehaviorProperty")) {
behaviorContent.SetAttribute("requiredBehaviorProperty", value);
return true;
}
return false;
}
virtual void InitializeContent(
gd::SerializerElement& behaviorContent) override {
behaviorContent.SetAttribute("requiredBehaviorProperty", "");
}
};
void SetupProjectWithDummyPlatform(gd::Project& project,
gd::Platform& platform) {
// Don't show extension loading logs for tests (too verbose).
platform.EnableExtensionLoadingLogs(false);
@@ -85,18 +153,19 @@ void SetupProjectWithDummyPlatform(gd::Project &project,
.AddParameter("expression", "", "", true)
.SetFunctionName("getNumberWith3Params");
extension
->AddStrExpression("GetStringWith2ObjectParamAnd2ObjectVarParam",
"Get string with twice an object param and an objectvar param",
"",
"",
"")
->AddStrExpression(
"GetStringWith2ObjectParamAnd2ObjectVarParam",
"Get string with twice an object param and an objectvar param",
"",
"",
"")
.AddParameter("object", _("Object 1 parameter"))
.AddParameter("objectvar", _("Variable for object 1"))
.AddParameter("object", _("Object 2 parameter"))
.AddParameter("objectvar", _("Variable for object 2"))
.SetFunctionName("getStringWith2ObjectParamAnd2ObjectVarParam");
auto &object = extension->AddObject<gd::Object>(
auto& object = extension->AddObject<gd::Object>(
"Sprite", "Dummy Sprite", "Dummy sprite object", "");
object
.AddExpression("GetObjectVariableAsNumber",
@@ -140,36 +209,91 @@ void SetupProjectWithDummyPlatform(gd::Project &project,
.AddParameter("object", _("Object parameter"))
.AddParameter("objectPtr", _("Object parameter"))
.SetFunctionName("getObjectStringWith2ObjectParam");
auto behavior =
extension->AddBehavior("MyBehavior",
"Dummy behavior",
"MyBehavior",
"A dummy behavior for tests",
"",
"",
"",
gd::make_unique<gd::Behavior>(),
gd::make_unique<gd::BehaviorsSharedData>());
behavior
.AddAction("BehaviorDoSomething",
"Do something on behavior",
"This does something with the behavior",
"Do something with the behavior please",
"",
"",
"")
.AddParameter("expression", "Parameter 1 (a number)")
.SetFunctionName("behaviorDoSomething");
behavior
.AddStrExpression("GetBehaviorStringWith1Param",
"Get string from behavior with 1 param",
"",
"",
"")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "MyExtension::MyBehavior")
.AddParameter("expression", _("Number parameter"))
.SetFunctionName("getBehaviorStringWith1Param");
{
auto& behavior =
extension->AddBehavior("MyBehavior",
"Dummy behavior",
"MyBehavior",
"A dummy behavior for tests",
"",
"",
"",
gd::make_unique<gd::Behavior>(),
gd::make_unique<gd::BehaviorsSharedData>());
behavior
.AddAction("BehaviorDoSomething",
"Do something on behavior",
"This does something with the behavior",
"Do something with the behavior please",
"",
"",
"")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "MyExtension::MyBehavior")
.AddParameter("expression", "Parameter 1 (a number)")
.SetFunctionName("behaviorDoSomething");
behavior
.AddStrExpression("GetBehaviorStringWith1Param",
"Get string from behavior with 1 param",
"",
"",
"")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "MyExtension::MyBehavior")
.AddParameter("expression", _("Number parameter"))
.SetFunctionName("getBehaviorStringWith1Param");
behavior
.AddExpression("GetBehaviorNumberWith1Param",
"Get number from behavior with 1 param",
"",
"",
"")
.AddParameter("object", _("Object"))
.AddParameter("behavior", _("Behavior"), "MyExtension::MyBehavior")
.AddParameter("expression", _("Number parameter"))
.SetFunctionName("getBehaviorNumberWith1Param");
}
{
auto& behavior =
extension->AddBehavior("MyOtherBehavior",
"Another Dummy behavior",
"MyOtherBehavior",
"Another dummy behavior for tests",
"",
"",
"",
gd::make_unique<gd::Behavior>(),
gd::make_unique<gd::BehaviorsSharedData>());
}
{
auto& behavior = extension->AddBehavior(
"BehaviorWithRequiredBehaviorProperty",
"BehaviorWithRequiredBehaviorProperty",
"BehaviorWithRequiredBehaviorProperty",
"A dummy behavior requiring another behavior (MyBehavior)",
"",
"",
"",
gd::make_unique<BehaviorWithRequiredBehaviorProperty>(),
gd::make_unique<gd::BehaviorsSharedData>());
}
{
auto& behavior = extension->AddBehavior(
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior",
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior",
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior",
"A dummy behavior requiring another behavior "
"(BehaviorWithRequiredBehaviorProperty) that itself requires another "
"behavior (MyBehavior)",
"",
"",
"",
gd::make_unique<
BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior>(),
gd::make_unique<gd::BehaviorsSharedData>());
}
platform.AddExtension(baseObjectExtension);
platform.AddExtension(extension);
+137
View File
@@ -0,0 +1,137 @@
/*
* GDevelop Core
* Copyright 2008-2016 Florian Rival ([email protected]). All rights
* reserved. This project is released under the MIT License.
*/
#include "GDCore/IDE/Events/EventsBehaviorRenamer.h"
#include <algorithm>
#include "DummyPlatform.h"
#include "GDCore/Events/Builtin/StandardEvent.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
#include "GDCore/Project/Project.h"
#include "catch.hpp"
namespace {
gd::StandardEvent &EnsureStandardEvent(gd::BaseEvent &baseEvent) {
gd::StandardEvent *standardEvent =
dynamic_cast<gd::StandardEvent *>(&baseEvent);
INFO("The inspected event is "
<< (standardEvent ? "a standard event" : "not a standard event"));
REQUIRE(standardEvent != nullptr);
return *standardEvent;
}
} // namespace
TEST_CASE("EventsBehaviorRenamer (expressions)", "[common]") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
auto &object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior2");
// Add an event with an expression using the behaviors.
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::DoSomething");
instruction.SetParametersCount(1);
instruction.SetParameter(
0,
gd::Expression("4 + "
"Object1.MyBehavior::GetBehaviorNumberWith1Param(1) + "
"Object1.MyBehavior2::GetBehaviorNumberWith1Param(1)"));
event.GetActions().Insert(instruction);
layout1.GetEvents().InsertEvent(event);
// Rename the first behavior.
gd::EventsBehaviorRenamer behaviorRenamer(
platform, "Object1", "MyBehavior", "MyRenamedBehavior");
behaviorRenamer.Launch(layout1.GetEvents(), project, layout1);
// Verify the expression were updated.
REQUIRE(EnsureStandardEvent(layout1.GetEvents().GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() ==
"4 + Object1.MyRenamedBehavior::GetBehaviorNumberWith1Param(1) + "
"Object1.MyBehavior2::GetBehaviorNumberWith1Param(1)");
}
TEST_CASE("EventsBehaviorRenamer (instructions)", "[common]") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &layout1 = project.InsertNewLayout("Layout1", 0);
auto &object1 =
layout1.InsertNewObject(project, "MyExtension::Sprite", "Object1", 0);
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
object1.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior2");
// Add an action using MyBehavior.
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::BehaviorDoSomething");
instruction.SetParametersCount(3);
instruction.SetParameter(0, gd::Expression("Object1"));
instruction.SetParameter(1, gd::Expression("MyBehavior"));
instruction.SetParameter(2, gd::Expression("123"));
event.GetActions().Insert(instruction);
layout1.GetEvents().InsertEvent(event);
}
// Add an action using MyBehavior2.
{
gd::StandardEvent event;
gd::Instruction instruction;
instruction.SetType("MyExtension::BehaviorDoSomething");
instruction.SetParametersCount(3);
instruction.SetParameter(0, gd::Expression("Object1"));
instruction.SetParameter(1, gd::Expression("MyBehavior2"));
instruction.SetParameter(2, gd::Expression("123"));
event.GetActions().Insert(instruction);
layout1.GetEvents().InsertEvent(event);
}
// Rename MyBehavior.
gd::EventsBehaviorRenamer behaviorRenamer(
platform, "Object1", "MyBehavior", "MyRenamedBehavior");
behaviorRenamer.Launch(layout1.GetEvents(), project, layout1);
// Ensure the action using MyBehavior has its parameter renamed.
REQUIRE(EnsureStandardEvent(layout1.GetEvents().GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() == "Object1");
REQUIRE(EnsureStandardEvent(layout1.GetEvents().GetEvent(0))
.GetActions()
.Get(0)
.GetParameter(1)
.GetPlainString() == "MyRenamedBehavior");
// Ensure MyBehavior2 is left untouched
REQUIRE(EnsureStandardEvent(layout1.GetEvents().GetEvent(1))
.GetActions()
.Get(0)
.GetParameter(0)
.GetPlainString() == "Object1");
REQUIRE(EnsureStandardEvent(layout1.GetEvents().GetEvent(1))
.GetActions()
.Get(0)
.GetParameter(1)
.GetPlainString() == "MyBehavior2");
}
+539 -13
View File
@@ -7,13 +7,19 @@
* @file Tests covering project refactoring
*/
#include "GDCore/IDE/WholeProjectRefactorer.h"
#include <algorithm>
#include "DummyPlatform.h"
#include "GDCore/Events/Builtin/LinkEvent.h"
#include "GDCore/Events/Builtin/StandardEvent.h"
#include "GDCore/Events/Event.h"
#include "GDCore/Extensions/Metadata/MetadataProvider.h"
#include "GDCore/Extensions/Metadata/ParameterMetadataTools.h"
#include "GDCore/Extensions/Platform.h"
#include "GDCore/Extensions/PlatformExtension.h"
#include "GDCore/IDE/UnfilledRequiredBehaviorPropertyProblem.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsFunctionsExtension.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/ExternalLayout.h"
@@ -113,13 +119,29 @@ gd::EventsFunctionsExtension &SetupProjectWithEventsFunctionExtension(
eventsBasedBehavior.SetFullName("My events based behavior");
eventsBasedBehavior.SetDescription("An events based behavior for test");
// Add functions
// Add functions, and parameters that should be there by convention.
auto &behaviorEventsFunctions = eventsBasedBehavior.GetEventsFunctions();
behaviorEventsFunctions.InsertNewEventsFunction("MyBehaviorEventsFunction",
0);
behaviorEventsFunctions
.InsertNewEventsFunction("MyBehaviorEventsFunctionExpression", 1)
.SetFunctionType(gd::EventsFunction::Expression);
auto &behaviorAction = behaviorEventsFunctions.InsertNewEventsFunction(
"MyBehaviorEventsFunction", 0);
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata().SetName("Object").SetType("object"));
behaviorAction.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Behavior")
.SetType("behavior")
.SetExtraInfo("MyExtension::MyEventsBasedBehavior"));
auto &behaviorExpression =
behaviorEventsFunctions
.InsertNewEventsFunction("MyBehaviorEventsFunctionExpression", 1)
.SetFunctionType(gd::EventsFunction::Expression);
behaviorExpression.GetParameters().push_back(
gd::ParameterMetadata().SetName("Object").SetType("object"));
behaviorExpression.GetParameters().push_back(
gd::ParameterMetadata()
.SetName("Behavior")
.SetType("behavior")
.SetExtraInfo("MyExtension::MyEventsBasedBehavior"));
// Add property
eventsBasedBehavior.GetPropertyDescriptors()
@@ -703,7 +725,7 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
.GetEvent(0)) ==
"1 + MyEventsExtension::MyEventsFunctionExpression(456, 123)");
}
SECTION("Events based Behavior type renamed") {
SECTION("Events based Behavior renamed (instructions update)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
@@ -757,6 +779,52 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"ObjectWithMyBehavior.MyBehavior::"
"MyBehaviorEventsFunctionExpression(123, 456, 789)");
}
SECTION("Events based Behavior renamed (other behaviors properties update)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &eventsExtension = SetupProjectWithEventsFunctionExtension(project);
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
// Set up another events based behavior having a "required behavior"
// property refering to the behavior.
auto &otherEventsExtension =
project.InsertNewEventsFunctionsExtension("MyOtherEventsExtension", 0);
auto &otherEventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().InsertNew(
"MyOtherEventsBasedBehavior");
auto &otherEventsBasedBehaviorFirstProperty =
otherEventsBasedBehavior.GetPropertyDescriptors()
.InsertNew("SomeRequiredBehavior")
.SetType("Behavior")
.AddExtraInfo("MyEventsExtension::MyEventsBasedBehavior");
// Also add another "required behavior" property referring to another
// unrelated behavior.
auto &otherEventsBasedBehaviorSecondProperty =
otherEventsBasedBehavior.GetPropertyDescriptors()
.InsertNew("SomeRequiredBehavior")
.SetType("Behavior")
.AddExtraInfo("SomeOtherExtension::SomeOtherBehavior");
gd::WholeProjectRefactorer::RenameEventsBasedBehavior(
project,
eventsExtension,
"MyEventsBasedBehavior",
"MyRenamedEventsBasedBehavior");
// Check the other events-based behavior has its property updated.
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().size() == 1);
REQUIRE(otherEventsBasedBehaviorFirstProperty.GetExtraInfo().at(0) ==
"MyEventsExtension::MyRenamedEventsBasedBehavior");
// Check the other events-based behavior has its other property left
// untouched.
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().size() == 1);
REQUIRE(otherEventsBasedBehaviorSecondProperty.GetExtraInfo().at(0) ==
"SomeOtherExtension::SomeOtherBehavior");
}
SECTION("(Events based Behavior) events function renamed") {
gd::Project project;
gd::Platform platform;
@@ -882,7 +950,8 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"ObjectWithMyBehavior::MyBehavior."
"MyBehaviorEventsFunctionExpression(123, 456, 789)");
}
SECTION("(Events based Behavior) property renamed") {
SECTION(
"(Events based Behavior) property (not a required behavior) renamed") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
@@ -890,11 +959,12 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
auto &eventsBasedBehavior =
eventsExtension.GetEventsBasedBehaviors().Get("MyEventsBasedBehavior");
gd::WholeProjectRefactorer::RenameBehaviorProperty(project,
eventsExtension,
eventsBasedBehavior,
"MyProperty",
"MyRenamedProperty");
gd::WholeProjectRefactorer::RenameEventsBasedBehaviorProperty(
project,
eventsExtension,
eventsBasedBehavior,
"MyProperty",
"MyRenamedProperty");
// Check if events-based behaviors property has been renamed in
// instructions
@@ -919,3 +989,459 @@ TEST_CASE("WholeProjectRefactorer", "[common]") {
"ObjectWithMyBehavior.MyBehavior::PropertyMyRenamedProperty()");
}
}
TEST_CASE("WholeProjectRefactorer (FindInvalidRequiredBehaviorProperties)",
"[common]") {
SECTION("Find nothing if there are no missing required behavior") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
std::vector<gd::UnfilledRequiredBehaviorPropertyProblem> problems =
gd::WholeProjectRefactorer::FindInvalidRequiredBehaviorProperties(
project);
REQUIRE(problems.size() == 0);
}
SECTION("Find unfilled required behavior properties") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
// Insert an object using a behavior requiring another behavior.
// But don't fill the property, which is a problem.
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project,
"MyExtension::BehaviorWithRequiredBehaviorProperty",
"MyBehaviorWithRequiredBehaviorProperty");
std::vector<gd::UnfilledRequiredBehaviorPropertyProblem> problems =
gd::WholeProjectRefactorer::FindInvalidRequiredBehaviorProperties(
project);
REQUIRE(problems.size() == 1);
REQUIRE(problems[0].GetSourceObject().GetName() == "MyObject");
REQUIRE(problems[0].GetSourceBehaviorContent().GetName() ==
"MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(problems[0].GetSourcePropertyName() == "requiredBehaviorProperty");
REQUIRE(problems[0].GetExpectedBehaviorTypeName() ==
"MyExtension::MyBehavior");
}
SECTION("Find nothing if the required behavior properties are filled") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
// Insert an object using a behavior requiring another behavior.
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project,
"MyExtension::BehaviorWithRequiredBehaviorProperty",
"MyBehaviorWithRequiredBehaviorProperty");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
// Fill the required behavior property on the object.
gd::Behavior &behavior =
gd::MetadataProvider::GetBehaviorMetadata(
platform, "MyExtension::BehaviorWithRequiredBehaviorProperty")
.Get();
gd::BehaviorContent &behaviorContent =
object.GetBehavior("MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(behavior.UpdateProperty(behaviorContent.GetContent(),
"requiredBehaviorProperty",
"MyBehavior") == true);
std::vector<gd::UnfilledRequiredBehaviorPropertyProblem> problems =
gd::WholeProjectRefactorer::FindInvalidRequiredBehaviorProperties(
project);
REQUIRE(problems.size() == 0);
}
SECTION("Find wrongly filled (wrong type) required behavior properties") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
// Insert an object using a behavior requiring another behavior.
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project,
"MyExtension::BehaviorWithRequiredBehaviorProperty",
"MyBehaviorWithRequiredBehaviorProperty");
object.AddNewBehavior(
project, "MyExtension::MyOtherBehavior", "MyOtherBehavior");
// Fill the required behavior property on the object with the wrong behavior
// name
gd::Behavior &behavior =
gd::MetadataProvider::GetBehaviorMetadata(
platform, "MyExtension::BehaviorWithRequiredBehaviorProperty")
.Get();
gd::BehaviorContent &behaviorContent =
object.GetBehavior("MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(behavior.UpdateProperty(behaviorContent.GetContent(),
"requiredBehaviorProperty",
"MyOtherBehavior") == true);
std::vector<gd::UnfilledRequiredBehaviorPropertyProblem> problems =
gd::WholeProjectRefactorer::FindInvalidRequiredBehaviorProperties(
project);
REQUIRE(problems.size() == 1);
REQUIRE(problems[0].GetSourceObject().GetName() == "MyObject");
REQUIRE(problems[0].GetSourceBehaviorContent().GetName() ==
"MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(problems[0].GetSourcePropertyName() == "requiredBehaviorProperty");
REQUIRE(problems[0].GetExpectedBehaviorTypeName() ==
"MyExtension::MyBehavior");
}
SECTION("Find wrongly filled (not existing) required behavior properties") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
// Insert an object using a behavior requiring another behavior.
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project,
"MyExtension::BehaviorWithRequiredBehaviorProperty",
"MyBehaviorWithRequiredBehaviorProperty");
// Fill the required behavior property on the object with the wrong behavior
// name
gd::Behavior &behavior =
gd::MetadataProvider::GetBehaviorMetadata(
platform, "MyExtension::BehaviorWithRequiredBehaviorProperty")
.Get();
gd::BehaviorContent &behaviorContent =
object.GetBehavior("MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(behavior.UpdateProperty(behaviorContent.GetContent(),
"requiredBehaviorProperty",
"MyNotExistingBehavior") == true);
std::vector<gd::UnfilledRequiredBehaviorPropertyProblem> problems =
gd::WholeProjectRefactorer::FindInvalidRequiredBehaviorProperties(
project);
REQUIRE(problems.size() == 1);
REQUIRE(problems[0].GetSourceObject().GetName() == "MyObject");
REQUIRE(problems[0].GetSourceBehaviorContent().GetName() ==
"MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(problems[0].GetSourcePropertyName() == "requiredBehaviorProperty");
REQUIRE(problems[0].GetExpectedBehaviorTypeName() ==
"MyExtension::MyBehavior");
}
}
TEST_CASE("WholeProjectRefactorer (FixInvalidRequiredBehaviorProperties)",
"[common]") {
SECTION("Fix nothing if there are no missing required behavior") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
REQUIRE(gd::WholeProjectRefactorer::FixInvalidRequiredBehaviorProperties(
project) == false);
}
SECTION(
"Fix unfilled required behavior properties by adding a behavior if "
"necessary") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
// Insert an object using a behavior requiring another behavior.
// But don't fill the property, which is a problem.
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project,
"MyExtension::BehaviorWithRequiredBehaviorProperty",
"MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(gd::WholeProjectRefactorer::FixInvalidRequiredBehaviorProperties(
project) == true);
// Check the behavior is still there.
REQUIRE(object.HasBehaviorNamed("MyBehaviorWithRequiredBehaviorProperty"));
// Check that the property was filled with the newly added behavior.
REQUIRE(object.GetBehavior("MyBehaviorWithRequiredBehaviorProperty")
.GetContent()
.HasAttribute("requiredBehaviorProperty"));
REQUIRE(object.GetBehavior("MyBehaviorWithRequiredBehaviorProperty")
.GetContent()
.GetStringAttribute("requiredBehaviorProperty") ==
"MyBehavior");
// And also the new behavior that was missing (inserted using its default
// name).
REQUIRE(object.GetAllBehaviorNames().size() == 2);
REQUIRE(object.HasBehaviorNamed("MyBehavior"));
REQUIRE(object.GetBehavior("MyBehavior").GetTypeName() ==
"MyExtension::MyBehavior");
// Check there is no other fix to do.
REQUIRE(gd::WholeProjectRefactorer::FixInvalidRequiredBehaviorProperties(
project) == false);
}
SECTION(
"Fix wrongly filled required behavior properties without adding a "
"behavior, if not necessary") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
// Insert an object using a behavior requiring another behavior.
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
object.AddNewBehavior(project,
"MyExtension::BehaviorWithRequiredBehaviorProperty",
"MyBehaviorWithRequiredBehaviorProperty");
object.AddNewBehavior(project, "MyExtension::MyBehavior", "MyBehavior");
// Wrongly fill the required behavior property on the object.
gd::Behavior &behavior =
gd::MetadataProvider::GetBehaviorMetadata(
platform, "MyExtension::BehaviorWithRequiredBehaviorProperty")
.Get();
gd::BehaviorContent &behaviorContent =
object.GetBehavior("MyBehaviorWithRequiredBehaviorProperty");
REQUIRE(behavior.UpdateProperty(behaviorContent.GetContent(),
"requiredBehaviorProperty",
"ThisIsInvalid") == true);
// Check a fix is done
REQUIRE(gd::WholeProjectRefactorer::FixInvalidRequiredBehaviorProperties(
project) == true);
REQUIRE(object.HasBehaviorNamed("MyBehaviorWithRequiredBehaviorProperty"));
// Check that the property was filled with the existing behavior.
REQUIRE(behavior.GetProperties(behaviorContent.GetContent())
.at("requiredBehaviorProperty")
.GetValue() == "MyBehavior");
// Check that the existing behavior is unchanged.
REQUIRE(object.GetAllBehaviorNames().size() == 2);
REQUIRE(object.HasBehaviorNamed("MyBehavior"));
REQUIRE(object.GetBehavior("MyBehavior").GetTypeName() ==
"MyExtension::MyBehavior");
// Check there is no other fix to do.
REQUIRE(gd::WholeProjectRefactorer::FixInvalidRequiredBehaviorProperties(
project) == false);
}
}
TEST_CASE("WholeProjectRefactorer (AddBehaviorAndRequiredBehaviors)",
"[common]") {
SECTION(
"Does not add anything else if the newly added behavior has no required "
"behavior properties") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
// Add a simple behavior.
gd::WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
project, object, "MyExtension::MyBehavior", "MyBehavior");
REQUIRE(object.HasBehaviorNamed("MyBehavior"));
REQUIRE(object.GetAllBehaviorNames().size() == 1);
}
SECTION("Does not add anything else if the newly added behavior is unknown") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
// Add a simple behavior.
gd::WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
project,
object,
"MyExtension::MyNotExistingBehavior",
"MyNotExistingBehavior");
REQUIRE(object.GetAllBehaviorNames().size() == 0);
}
SECTION("Add a behavior and its required behaviors on an object") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
// Add the behavior that requires a behavior.
gd::WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
project,
object,
"MyExtension::"
"BehaviorWithRequiredBehaviorProperty",
"BehaviorWithRequiredBehaviorProperty");
// Required behavior are added.
REQUIRE(object.HasBehaviorNamed("MyBehavior"));
REQUIRE(object.HasBehaviorNamed("BehaviorWithRequiredBehaviorProperty"));
// Check that required behavior properties were filled properly too.
const auto &metadata1 = gd::MetadataProvider::GetBehaviorMetadata(
platform,
"MyExtension::"
"BehaviorWithRequiredBehaviorProperty");
REQUIRE(!gd::MetadataProvider::IsBadBehaviorMetadata(metadata1));
const auto &behaviorWithRequiredBehaviorPropertyProperties =
metadata1.Get().GetProperties(
object.GetBehavior("BehaviorWithRequiredBehaviorProperty")
.GetContent());
REQUIRE(behaviorWithRequiredBehaviorPropertyProperties
.at("requiredBehaviorProperty")
.GetValue() == "MyBehavior");
}
SECTION(
"Add a behavior and its required behaviors on an object (transitively)") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
// Add the behavior that requires a behavior that requires another.
gd::WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
project,
object,
"MyExtension::"
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior",
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior");
// Required behavior are added transitively.
REQUIRE(object.HasBehaviorNamed("MyBehavior"));
REQUIRE(object.HasBehaviorNamed("BehaviorWithRequiredBehaviorProperty"));
REQUIRE(object.HasBehaviorNamed(
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior"));
// Check that required behavior properties were filled properly too.
const auto &metadata1 = gd::MetadataProvider::GetBehaviorMetadata(
platform,
"MyExtension::"
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior");
const auto &metadata2 = gd::MetadataProvider::GetBehaviorMetadata(
platform, "MyExtension::BehaviorWithRequiredBehaviorProperty");
REQUIRE(!gd::MetadataProvider::IsBadBehaviorMetadata(metadata1));
REQUIRE(!gd::MetadataProvider::IsBadBehaviorMetadata(metadata2));
const auto &
behaviorWithRequiredBehaviorPropertyRequiringAnotherBehaviorProperties =
metadata1.Get().GetProperties(
object
.GetBehavior("BehaviorWithRequiredBehaviorPropertyRequiring"
"AnotherBehavior")
.GetContent());
const auto &behaviorWithRequiredBehaviorPropertyProperties =
metadata2.Get().GetProperties(
object.GetBehavior("BehaviorWithRequiredBehaviorProperty")
.GetContent());
REQUIRE(
behaviorWithRequiredBehaviorPropertyRequiringAnotherBehaviorProperties
.at("requiredBehaviorProperty")
.GetValue() == "BehaviorWithRequiredBehaviorProperty");
REQUIRE(behaviorWithRequiredBehaviorPropertyProperties
.at("requiredBehaviorProperty")
.GetValue() == "MyBehavior");
}
}
TEST_CASE("WholeProjectRefactorer (FindDependentBehaviorNames)", "[common]") {
SECTION("Find behaviors that are dependent on another") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
// Add the behavior that requires a behavior that requires another.
gd::WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
project,
object,
"MyExtension::"
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior",
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior");
// Required behavior are added transitively.
REQUIRE(object.HasBehaviorNamed("MyBehavior"));
REQUIRE(object.HasBehaviorNamed("BehaviorWithRequiredBehaviorProperty"));
REQUIRE(object.HasBehaviorNamed(
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior"));
// Find dependent behaviors
{
const auto &behaviorNames =
gd::WholeProjectRefactorer::FindDependentBehaviorNames(
project, object, "MyBehavior");
REQUIRE(behaviorNames.size() == 2);
REQUIRE(std::find(behaviorNames.begin(),
behaviorNames.end(),
"BehaviorWithRequiredBehaviorProperty") !=
behaviorNames.end());
REQUIRE(
std::find(
behaviorNames.begin(),
behaviorNames.end(),
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior") !=
behaviorNames.end());
}
{
const auto &behaviorNames =
gd::WholeProjectRefactorer::FindDependentBehaviorNames(
project, object, "BehaviorWithRequiredBehaviorProperty");
REQUIRE(behaviorNames.size() == 1);
REQUIRE(
std::find(
behaviorNames.begin(),
behaviorNames.end(),
"BehaviorWithRequiredBehaviorPropertyRequiringAnotherBehavior") !=
behaviorNames.end());
}
}
}
TEST_CASE("WholeProjectRefactorer (FindDependentBehaviorNames failing cases)",
"[common]") {
SECTION("Handle non existing behaviors") {
gd::Project project;
gd::Platform platform;
SetupProjectWithDummyPlatform(project, platform);
auto &object =
project.InsertNewObject(project, "MyExtension::Sprite", "MyObject", 0);
// Add the behavior that requires a behavior that requires another.
gd::WholeProjectRefactorer::AddBehaviorAndRequiredBehaviors(
project,
object,
"MyExtension::"
"BehaviorWithRequiredBehaviorProperty",
"BehaviorWithRequiredBehaviorProperty");
// Required behavior are added transitively.
REQUIRE(object.HasBehaviorNamed("MyBehavior"));
REQUIRE(object.HasBehaviorNamed("BehaviorWithRequiredBehaviorProperty"));
gd::BehaviorContent unknownBehaviorContent(
"MyUnknownBehavior", "MyUnknownExtension::MyUnknownBehavior");
object.AddBehavior(unknownBehaviorContent);
// Find dependent behaviors, and ignore the unknown one.
{
const auto &behaviorNames =
gd::WholeProjectRefactorer::FindDependentBehaviorNames(
project, object, "MyBehavior");
REQUIRE(behaviorNames.size() == 1);
REQUIRE(std::find(behaviorNames.begin(),
behaviorNames.end(),
"BehaviorWithRequiredBehaviorProperty") !=
behaviorNames.end());
}
}
}
@@ -69,6 +69,7 @@ gd::String BehaviorCodeGenerator::GenerateRuntimeBehaviorCompleteCode(
runtimeBehaviorMethodsCode +=
EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
project,
eventsBasedBehavior,
*eventsFunction,
methodCodeNamespace,
methodFullyQualifiedName,
@@ -213,7 +214,10 @@ gd::String BehaviorCodeGenerator::GenerateUpdatePropertyFromBehaviorDataCode(
gd::String BehaviorCodeGenerator::GeneratePropertyValueCode(
const gd::PropertyDescriptor& property) {
if (property.GetType() == "String" || property.GetType() == "Choice" || property.GetType() == "Color") {
if (property.GetType() == "String" ||
property.GetType() == "Choice" ||
property.GetType() == "Color" ||
property.GetType() == "Behavior") {
return EventsCodeGenerator::ConvertToStringExplicit(property.GetValue());
} else if (property.GetType() == "Number") {
return "Number(" +
@@ -19,6 +19,7 @@
#include "GDCore/IDE/SceneNameMangler.h"
#include "GDCore/Project/Behavior.h"
#include "GDCore/Project/EventsFunction.h"
#include "GDCore/Project/EventsBasedBehavior.h"
#include "GDCore/Project/ExternalEvents.h"
#include "GDCore/Project/Layout.h"
#include "GDCore/Project/Object.h"
@@ -110,7 +111,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
bool compilationForRuntime) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::EventsFunctionToObjectsContainer(
gd::EventsFunctionTools::FreeEventsFunctionToObjectsContainer(
project, eventsFunction, globalObjectsAndGroups, objectsAndGroups);
EventsCodeGenerator codeGenerator(globalObjectsAndGroups, objectsAndGroups);
@@ -123,7 +124,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
codeGenerator.GetCodeNamespaceAccessor() + "func",
codeGenerator.GenerateEventsFunctionParameterDeclarationsList(
eventsFunction.GetParameters(), false),
codeGenerator.GenerateEventsFunctionContext(
codeGenerator.GenerateFreeEventsFunctionContext(
eventsFunction.GetParameters(), "runtimeScene.getOnceTriggers()"),
eventsFunction.GetEvents(),
codeGenerator.GenerateEventsFunctionReturn(eventsFunction));
@@ -135,6 +136,7 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionCode(
gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction,
const gd::String& codeNamespace,
const gd::String& fullyQualifiedFunctionName,
@@ -144,8 +146,12 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
bool compilationForRuntime) {
gd::ObjectsContainer globalObjectsAndGroups;
gd::ObjectsContainer objectsAndGroups;
gd::EventsFunctionTools::EventsFunctionToObjectsContainer(
project, eventsFunction, globalObjectsAndGroups, objectsAndGroups);
gd::EventsFunctionTools::BehaviorEventsFunctionToObjectsContainer(
project,
eventsBasedBehavior,
eventsFunction,
globalObjectsAndGroups,
objectsAndGroups);
EventsCodeGenerator codeGenerator(globalObjectsAndGroups, objectsAndGroups);
codeGenerator.SetCodeNamespace(codeNamespace);
@@ -166,7 +172,8 @@ gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionCode(
// By convention of Behavior Events Function, the behavior is accessible
// as a parameter called "Behavior".
"var Behavior = this.name;\n" +
codeGenerator.GenerateEventsFunctionContext(
codeGenerator.GenerateBehaviorEventsFunctionContext(
eventsBasedBehavior,
eventsFunction.GetParameters(),
onceTriggersVariable,
// Pass the names of the parameters considered as the current
@@ -211,9 +218,76 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionParameterDeclarationsList(
return declaration;
}
gd::String EventsCodeGenerator::GenerateFreeEventsFunctionContext(
const vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable) {
gd::String objectsGettersMap;
gd::String objectArraysMap;
gd::String behaviorNamesMap;
return GenerateEventsFunctionContext(parameters, onceTriggersVariable, objectsGettersMap, objectArraysMap, behaviorNamesMap);
}
gd::String EventsCodeGenerator::GenerateBehaviorEventsFunctionContext(
const gd::EventsBasedBehavior& eventsBasedBehavior,
const vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable,
const gd::String& thisObjectName,
const gd::String& thisBehaviorName) {
// See the comment at the start of the GenerateEventsFunctionContext function
gd::String objectsGettersMap;
gd::String objectArraysMap;
gd::String behaviorNamesMap;
// If we have an object considered as the current object ("this") (usually
// called Object in behavior events function), generate a slightly more
// optimized getter for it (bypassing "Object" hashmap, and directly return
// the array containing it).
if (!thisObjectName.empty()) {
objectsGettersMap +=
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n";
objectArraysMap +=
ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n";
}
if (!thisBehaviorName.empty()) {
// If we have a behavior considered as the current behavior ("this") (usually
// called Behavior in behavior events function), generate a slightly more
// optimized getter for it.
behaviorNamesMap += ConvertToStringExplicit(thisBehaviorName) + ": " +
thisBehaviorName + "\n";
// Add required behaviors from properties
for (size_t i = 0; i < eventsBasedBehavior.GetPropertyDescriptors().GetCount(); i++)
{
const gd::NamedPropertyDescriptor& propertyDescriptor = eventsBasedBehavior.GetPropertyDescriptors().Get(i);
const std::vector<gd::String>& extraInfo = propertyDescriptor.GetExtraInfo();
if (propertyDescriptor.GetType() == "Behavior") {
// Generate map that will be used to transform from behavior name used in
// function to the "real" behavior name from the caller.
gd::String comma = behaviorNamesMap.empty() ? "" : ", ";
behaviorNamesMap += comma + ConvertToStringExplicit(propertyDescriptor.GetName()) +
": this._get" + propertyDescriptor.GetName() + "()\n";
}
}
}
return GenerateEventsFunctionContext(
parameters,
onceTriggersVariable,
objectsGettersMap,
objectArraysMap,
behaviorNamesMap,
thisObjectName,
thisBehaviorName);
}
gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
const vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable,
gd::String& objectsGettersMap,
gd::String& objectArraysMap,
gd::String& behaviorNamesMap,
const gd::String& thisObjectName,
const gd::String& thisBehaviorName) {
// When running in the context of a function generated from events, we
@@ -229,29 +303,8 @@ gd::String EventsCodeGenerator::GenerateEventsFunctionContext(
// * For other parameters, allow to access to them without transformation.
// Conditions/expressions are available to deal with them in events.
gd::String objectsGettersMap;
gd::String objectArraysMap;
gd::String behaviorNamesMap;
gd::String argumentsGetters;
// If we have an object considered as the current object ("this") (usually
// called Object in behavior events function), generate a slightly more
// optimized getter for it (bypassing "Object" hashmap, and directly return
// the array containing it).
if (!thisObjectName.empty()) {
objectsGettersMap +=
ConvertToStringExplicit(thisObjectName) + ": " + thisObjectName + "\n";
objectArraysMap +=
ConvertToStringExplicit(thisObjectName) + ": thisObjectList\n";
}
// If we have a behavior considered as the current behavior ("this") (usually
// called Behavior in behavior events function), generate a slightly more
// optimized getter for it.
if (!thisBehaviorName.empty()) {
behaviorNamesMap += ConvertToStringExplicit(thisBehaviorName) + ": " +
thisBehaviorName + "\n";
}
for (const auto& parameter : parameters) {
if (parameter.GetName().empty()) continue;
@@ -15,6 +15,7 @@
namespace gd {
class ObjectsContainer;
class EventsFunction;
class EventsBasedBehavior;
class ObjectMetadata;
class BehaviorMetadata;
class InstructionMetadata;
@@ -85,6 +86,7 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
*/
static gd::String GenerateBehaviorEventsFunctionCode(
gd::Project& project,
const gd::EventsBasedBehavior& eventsBasedBehavior,
const gd::EventsFunction& eventsFunction,
const gd::String& codeNamespace,
const gd::String& fullyQualifiedFunctionName,
@@ -321,15 +323,25 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
bool isBehaviorEventsFunction);
/**
* \brief Generate the "eventsFunctionContext" object that allow a function
* \brief Generate the "eventsFunctionContext" object that allow a free function
* to provides access objects, object creation and access to arguments from
* the rest of the events.
*/
gd::String GenerateEventsFunctionContext(
gd::String GenerateFreeEventsFunctionContext(
const std::vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable);
/**
* \brief Generate the "eventsFunctionContext" object that allow a behavior function
* to provides access objects, object creation and access to arguments from
* the rest of the events.
*/
gd::String GenerateBehaviorEventsFunctionContext(
const gd::EventsBasedBehavior& eventsBasedBehavior,
const std::vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable,
const gd::String& thisObjectName = "",
const gd::String& thisBehaviorName = "");
const gd::String& thisObjectName,
const gd::String& thisBehaviorName);
gd::String GenerateEventsFunctionReturn(
const gd::EventsFunction& eventFunction);
@@ -348,6 +360,20 @@ class EventsCodeGenerator : public gd::EventsCodeGenerator {
gd::String codeNamespace; ///< Optional namespace for the generated code,
///< used when generating events function.
private:
/**
* \brief Generate the "eventsFunctionContext" object that allow a function
* to provides access objects, object creation and access to arguments from
* the rest of the events.
*/
gd::String GenerateEventsFunctionContext(
const std::vector<gd::ParameterMetadata>& parameters,
const gd::String& onceTriggersVariable,
gd::String& objectsGettersMap,
gd::String& objectArraysMap,
gd::String& behaviorNamesMap,
const gd::String& thisObjectName = "",
const gd::String& thisBehaviorName = "");
};
} // namespace gdjs
Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 489 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 566 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

+22 -3
View File
@@ -744,7 +744,7 @@ interface PropertyDescriptor {
[Const, Ref] DOMString GetDescription();
[Ref] PropertyDescriptor AddExtraInfo([Const] DOMString type);
[Ref] PropertyDescriptor SetExtraInfo([Const, Ref] VectorString info);
[Value] VectorString GetExtraInfo();
[Ref] VectorString GetExtraInfo();
[Ref] PropertyDescriptor SetHidden(boolean enable);
boolean IsHidden();
@@ -1247,7 +1247,8 @@ interface ParameterMetadataTools {
};
interface EventsFunctionTools {
void STATIC_EventsFunctionToObjectsContainer([Ref] Project project, [Const, Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer outputGlobalObjectsContainer, [Ref] ObjectsContainer outputObjectsContainer);
void STATIC_FreeEventsFunctionToObjectsContainer([Ref] Project project, [Const, Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer outputGlobalObjectsContainer, [Ref] ObjectsContainer outputObjectsContainer);
void STATIC_BehaviorEventsFunctionToObjectsContainer([Ref] Project project, [Const, Ref] EventsBasedBehavior eventsBasedBehavior, [Const, Ref] EventsFunction eventsFunction, [Ref] ObjectsContainer outputGlobalObjectsContainer, [Ref] ObjectsContainer outputObjectsContainer);
};
interface ObjectMetadata {
@@ -1873,6 +1874,19 @@ interface EventsRefactorer {
[Value] VectorEventsSearchResult STATIC_SearchInEvents([Ref] ObjectsContainer project, [Ref] ObjectsContainer layout, [Ref] EventsList events, [Const] DOMString search, boolean matchCase, boolean inConditions, boolean inActions, boolean inEventStrings);
};
interface UnfilledRequiredBehaviorPropertyProblem {
[Const, Ref] Project GetSourceProject();
[Ref] gdObject GetSourceObject();
[Ref] BehaviorContent GetSourceBehaviorContent();
[Const, Ref] DOMString GetSourcePropertyName();
[Const, Ref] DOMString GetExpectedBehaviorTypeName();
};
interface VectorUnfilledRequiredBehaviorPropertyProblem {
unsigned long size();
[Const, Ref] UnfilledRequiredBehaviorPropertyProblem at(unsigned long index);
};
interface WholeProjectRefactorer {
void STATIC_ExposeProjectEvents([Ref] Project project, [Ref] ArbitraryEventsWorker worker);
void STATIC_RenameEventsFunctionsExtension(
@@ -1904,7 +1918,7 @@ interface WholeProjectRefactorer {
[Const] DOMString functionName,
unsigned long oldIndex,
unsigned long newIndex);
void STATIC_RenameBehaviorProperty(
void STATIC_RenameEventsBasedBehaviorProperty(
[Ref] Project project,
[Const, Ref] EventsFunctionsExtension eventsFunctionsExtension,
[Const, Ref] EventsBasedBehavior eventsBasedBehavior,
@@ -1923,6 +1937,11 @@ interface WholeProjectRefactorer {
void STATIC_GlobalObjectOrGroupRemoved([Ref] Project project, [Const] DOMString objectName, boolean isObjectGroup, boolean removeEventsAndGroups);
[Value] SetString STATIC_GetAllObjectTypesUsingEventsBasedBehavior([Const, Ref] Project project, [Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior);
void STATIC_EnsureBehaviorEventsFunctionsProperParameters([Const, Ref] EventsFunctionsExtension eventsFunctionsExtension, [Const, Ref] EventsBasedBehavior eventsBasedBehavior);
void STATIC_AddBehaviorAndRequiredBehaviors([Ref] Project project, [Ref] gdObject obj, [Const] DOMString behaviorType, [Const] DOMString behaviorName);
[Value] VectorString STATIC_FindDependentBehaviorNames([Const, Ref] Project project, [Const, Ref] gdObject obj, [Const] DOMString behaviorName);
[Value] VectorUnfilledRequiredBehaviorPropertyProblem STATIC_FindInvalidRequiredBehaviorProperties([Const, Ref] Project project);
[Value] VectorString STATIC_GetBehaviorsWithType([Const, Ref] gdObject obj, [Const] DOMString type);
boolean STATIC_FixInvalidRequiredBehaviorProperties([Ref] Project project);
};
interface UsedExtensionsFinder {
+16 -2
View File
@@ -43,6 +43,7 @@
#include <GDCore/IDE/Project/ResourcesMergingHelper.h>
#include <GDCore/IDE/Project/ResourcesRenamer.h>
#include <GDCore/IDE/WholeProjectRefactorer.h>
#include <GDCore/IDE/UnfilledRequiredBehaviorPropertyProblem.h>
#include <GDCore/Project/Behavior.h>
#include <GDCore/Project/Effect.h>
#include <GDCore/Project/EventsBasedBehavior.h>
@@ -411,6 +412,7 @@ typedef std::map<gd::String, gd::SerializerValue> MapStringSerializerValue;
typedef std::vector<std::pair<gd::String, std::shared_ptr<SerializerElement>>>
VectorPairStringSharedPtrSerializerElement;
typedef std::shared_ptr<SerializerElement> SharedPtrSerializerElement;
typedef std::vector<UnfilledRequiredBehaviorPropertyProblem> VectorUnfilledRequiredBehaviorPropertyProblem;
typedef ExtensionAndMetadata<BehaviorMetadata> ExtensionAndBehaviorMetadata;
typedef ExtensionAndMetadata<ObjectMetadata> ExtensionAndObjectMetadata;
@@ -426,6 +428,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
// Customize some functions implementation thanks to WRAPPED_* macros
// The original names will be reconstructed in the js file (see postjs.js)
#define WRAPPED_set(a, b) at(a) = b
@@ -547,6 +550,16 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
GetAllObjectTypesUsingEventsBasedBehavior
#define STATIC_EnsureBehaviorEventsFunctionsProperParameters \
EnsureBehaviorEventsFunctionsProperParameters
#define STATIC_AddBehaviorAndRequiredBehaviors \
AddBehaviorAndRequiredBehaviors
#define STATIC_FindDependentBehaviorNames \
FindDependentBehaviorNames
#define STATIC_FindInvalidRequiredBehaviorProperties \
FindInvalidRequiredBehaviorProperties
#define STATIC_GetBehaviorsWithType \
GetBehaviorsWithType
#define STATIC_FixInvalidRequiredBehaviorProperties \
FixInvalidRequiredBehaviorProperties
#define STATIC_CreateRectangle CreateRectangle
#define STATIC_SanityCheckBehaviorProperty SanityCheckBehaviorProperty
#define STATIC_SanityCheckObjectProperty SanityCheckObjectProperty
@@ -560,7 +573,8 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
#define STATIC_SearchInEvents SearchInEvents
#define STATIC_UnfoldWhenContaining UnfoldWhenContaining
#define STATIC_EventsFunctionToObjectsContainer EventsFunctionToObjectsContainer
#define STATIC_FreeEventsFunctionToObjectsContainer FreeEventsFunctionToObjectsContainer
#define STATIC_BehaviorEventsFunctionToObjectsContainer BehaviorEventsFunctionToObjectsContainer
#define STATIC_ParametersToObjectsContainer ParametersToObjectsContainer
#define STATIC_GetObjectParameterIndexFor GetObjectParameterIndexFor
@@ -571,7 +585,7 @@ typedef ExtensionAndMetadata<ExpressionMetadata> ExtensionAndExpressionMetadata;
#define STATIC_MoveEventsFunctionParameter MoveEventsFunctionParameter
#define STATIC_MoveBehaviorEventsFunctionParameter \
MoveBehaviorEventsFunctionParameter
#define STATIC_RenameBehaviorProperty RenameBehaviorProperty
#define STATIC_RenameEventsBasedBehaviorProperty RenameEventsBasedBehaviorProperty
#define STATIC_RenameEventsBasedBehavior RenameEventsBasedBehavior
#define STATIC_GetBehaviorPropertyGetterName GetBehaviorPropertyGetterName
+2 -1
View File
@@ -1,6 +1,7 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdEventsFunctionTools {
static eventsFunctionToObjectsContainer(project: gdProject, eventsFunction: gdEventsFunction, outputGlobalObjectsContainer: gdObjectsContainer, outputObjectsContainer: gdObjectsContainer): void;
static freeEventsFunctionToObjectsContainer(project: gdProject, eventsFunction: gdEventsFunction, outputGlobalObjectsContainer: gdObjectsContainer, outputObjectsContainer: gdObjectsContainer): void;
static behaviorEventsFunctionToObjectsContainer(project: gdProject, eventsBasedBehavior: gdEventsBasedBehavior, eventsFunction: gdEventsFunction, outputGlobalObjectsContainer: gdObjectsContainer, outputObjectsContainer: gdObjectsContainer): void;
delete(): void;
ptr: number;
};
@@ -0,0 +1,10 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdUnfilledRequiredBehaviorPropertyProblem {
getSourceProject(): gdProject;
getSourceObject(): gdObject;
getSourceBehaviorContent(): gdBehaviorContent;
getSourcePropertyName(): string;
getExpectedBehaviorTypeName(): string;
delete(): void;
ptr: number;
};
@@ -0,0 +1,7 @@
// Automatically generated by GDevelop.js/scripts/generate-types.js
declare class gdVectorUnfilledRequiredBehaviorPropertyProblem {
size(): number;
at(index: number): gdUnfilledRequiredBehaviorPropertyProblem;
delete(): void;
ptr: number;
};
@@ -6,7 +6,7 @@ declare class gdWholeProjectRefactorer {
static renameBehaviorEventsFunction(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void;
static moveEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, functionName: string, oldIndex: number, newIndex: number): void;
static moveBehaviorEventsFunctionParameter(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, functionName: string, oldIndex: number, newIndex: number): void;
static renameBehaviorProperty(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void;
static renameEventsBasedBehaviorProperty(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior, oldName: string, newName: string): void;
static renameEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, oldName: string, newName: string): void;
static objectOrGroupRenamedInLayout(project: gdProject, layout: gdLayout, oldName: string, newName: string, isObjectGroup: boolean): void;
static objectOrGroupRemovedInLayout(project: gdProject, layout: gdLayout, objectName: string, isObjectGroup: boolean, removeEventsAndGroups: boolean): void;
@@ -16,6 +16,11 @@ declare class gdWholeProjectRefactorer {
static globalObjectOrGroupRemoved(project: gdProject, objectName: string, isObjectGroup: boolean, removeEventsAndGroups: boolean): void;
static getAllObjectTypesUsingEventsBasedBehavior(project: gdProject, eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior): gdSetString;
static ensureBehaviorEventsFunctionsProperParameters(eventsFunctionsExtension: gdEventsFunctionsExtension, eventsBasedBehavior: gdEventsBasedBehavior): void;
static addBehaviorAndRequiredBehaviors(project: gdProject, obj: gdObject, behaviorType: string, behaviorName: string): void;
static findDependentBehaviorNames(project: gdProject, obj: gdObject, behaviorName: string): gdVectorString;
static findInvalidRequiredBehaviorProperties(project: gdProject): gdVectorUnfilledRequiredBehaviorPropertyProblem;
static getBehaviorsWithType(obj: gdObject, type: string): gdVectorString;
static fixInvalidRequiredBehaviorProperties(project: gdProject): boolean;
delete(): void;
ptr: number;
};
+2
View File
@@ -135,6 +135,8 @@ declare class libGDevelop {
EventsSearchResult: Class<gdEventsSearchResult>;
VectorEventsSearchResult: Class<gdVectorEventsSearchResult>;
EventsRefactorer: Class<gdEventsRefactorer>;
UnfilledRequiredBehaviorPropertyProblem: Class<gdUnfilledRequiredBehaviorPropertyProblem>;
VectorUnfilledRequiredBehaviorPropertyProblem: Class<gdVectorUnfilledRequiredBehaviorPropertyProblem>;
WholeProjectRefactorer: Class<gdWholeProjectRefactorer>;
UsedExtensionsFinder: Class<gdUsedExtensionsFinder>;
ExtensionAndBehaviorMetadata: Class<gdExtensionAndBehaviorMetadata>;
@@ -207,6 +207,8 @@ export const addAssetToProject = async ({
}
const behavior = behaviorMetadata.get();
// TODO: When this feature is exposed to users, we might want to use
// gd.WholeProjectRefactorer.addBehaviorAndRequiredBehaviors instead.
const behaviorContent = object.addNewBehavior(
project,
behaviorType,
@@ -12,6 +12,7 @@ export type BehaviorEditorProps = {|
behavior: gdBehavior,
behaviorContent: gdBehaviorContent,
project: gdProject,
object: gdObject,
resourceSources: Array<ResourceSource>,
onChooseResource: ChooseResourceFunction,
resourceExternalEditors: Array<ResourceExternalEditor>,
@@ -12,7 +12,7 @@ type Props = BehaviorEditorProps;
export default class BehaviorPropertiesEditor extends React.Component<Props> {
render() {
const { behavior, behaviorContent } = this.props;
const { behavior, behaviorContent, object } = this.props;
const properties = behavior.getProperties(behaviorContent.getContent());
const propertiesSchema = propertiesMapToSchema(
@@ -20,7 +20,8 @@ export default class BehaviorPropertiesEditor extends React.Component<Props> {
behaviorContent => behavior.getProperties(behaviorContent.getContent()),
(behaviorContent, name, value) => {
behavior.updateProperty(behaviorContent.getContent(), name, value);
}
},
object
);
return (
+21 -4
View File
@@ -74,7 +74,12 @@ const BehaviorsEditor = (props: Props) => {
const name = newNameGenerator(defaultName, name =>
object.hasBehaviorNamed(name)
);
object.addNewBehavior(project, type, name);
gd.WholeProjectRefactorer.addBehaviorAndRequiredBehaviors(
project,
object,
type,
name
);
forceUpdate();
if (props.onSizeUpdated) props.onSizeUpdated();
@@ -97,12 +102,23 @@ const BehaviorsEditor = (props: Props) => {
};
const onRemoveBehavior = (behaviorName: string) => {
const answer = Window.showConfirmDialog(
"Are you sure you want to remove this behavior? This can't be undone."
);
let message =
"Are you sure you want to remove this behavior? This can't be undone.";
const dependentBehaviors = gd.WholeProjectRefactorer.findDependentBehaviorNames(
project,
object,
behaviorName
).toJSArray();
if (dependentBehaviors.length > 0) {
message +=
'\nDependent behaviors will be removed too: ' +
dependentBehaviors.join(', ');
}
const answer = Window.showConfirmDialog(message);
if (answer) {
object.removeBehavior(behaviorName);
dependentBehaviors.forEach(name => object.removeBehavior(name));
if (props.onSizeUpdated) props.onSizeUpdated();
}
};
@@ -234,6 +250,7 @@ const BehaviorsEditor = (props: Props) => {
behavior={behavior}
behaviorContent={behaviorContent}
project={project}
object={object}
resourceSources={props.resourceSources}
onChooseResource={props.onChooseResource}
resourceExternalEditors={props.resourceExternalEditors}
@@ -24,6 +24,7 @@ import Add from '@material-ui/icons/Add';
import { ResponsiveLineStackLayout, ColumnStackLayout } from '../UI/Layout';
import StringArrayEditor from '../StringArrayEditor';
import ColorField from '../UI/ColorField';
import BehaviorTypeSelector from '../BehaviorTypeSelector';
const gd: libGDevelop = global.gd;
@@ -247,6 +248,10 @@ export default class EventsBasedBehaviorPropertiesEditor extends React.Component
value="Color"
primaryText={t`Color (text)`}
/>
<SelectOption
value="Behavior"
primaryText={t`Required behavior`}
/>
</SelectField>
{(property.getType() === 'String' ||
property.getType() === 'Number') && (
@@ -292,6 +297,29 @@ export default class EventsBasedBehaviorPropertiesEditor extends React.Component
/>
</SelectField>
)}
{property.getType() === 'Behavior' && (
<BehaviorTypeSelector
project={this.props.project}
objectType={this.props.eventsBasedBehavior.getObjectType()}
value={
property.getExtraInfo().size() === 0
? ''
: property.getExtraInfo().at(0)
}
onChange={(newValue: string) => {
// Change the type of the required behavior.
const extraInfo = property.getExtraInfo();
if (extraInfo.size() === 0) {
extraInfo.push_back(newValue);
} else {
extraInfo.set(0, newValue);
}
this.forceUpdate();
this.props.onPropertiesUpdated();
}}
disabled={false}
/>
)}
</ResponsiveLineStackLayout>
{property.getType() === 'Choice' && (
<StringArrayEditor
@@ -59,7 +59,7 @@ type Props = {|
extensionName: string,
eventsFunction: gdEventsFunction
) => void,
onBehaviorEdited?: () => void,
onBehaviorEdited?: () => Promise<void>,
initiallyFocusedFunctionName: ?string,
initiallyFocusedBehaviorName: ?string,
unsavedChanges?: ?UnsavedChanges,
@@ -129,7 +129,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
_loadEventsFunctionFrom = (
project: gdProject,
eventsFunction: gdEventsFunction
eventsFunction: gdEventsFunction,
eventsBasedBehavior: ?gdEventsBasedBehavior
) => {
// Create an empty "context" of objects.
// Avoid recreating containers if they were already created, so that
@@ -145,12 +146,22 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
// Initialize this "context" of objects with the function
// (as done during code generation).
gd.EventsFunctionTools.eventsFunctionToObjectsContainer(
project,
eventsFunction,
this._globalObjectsContainer,
this._objectsContainer
);
if (eventsBasedBehavior) {
gd.EventsFunctionTools.behaviorEventsFunctionToObjectsContainer(
project,
eventsBasedBehavior,
eventsFunction,
this._globalObjectsContainer,
this._objectsContainer
);
} else {
gd.EventsFunctionTools.freeEventsFunctionToObjectsContainer(
project,
eventsFunction,
this._globalObjectsContainer,
this._objectsContainer
);
}
};
updateToolbar = () => {
@@ -206,7 +217,11 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
return;
}
this._loadEventsFunctionFrom(this.props.project, selectedEventsFunction);
this._loadEventsFunctionFrom(
this.props.project,
selectedEventsFunction,
selectedEventsBasedBehavior
);
this.setState(
{
selectedEventsFunction,
@@ -421,7 +436,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
if (this.state.selectedEventsFunction) {
this._loadEventsFunctionFrom(
this.props.project,
this.state.selectedEventsFunction
this.state.selectedEventsFunction,
this.state.selectedEventsBasedBehavior
);
}
};
@@ -509,7 +525,7 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
newName: string
) => {
const { project, eventsFunctionsExtension } = this.props;
gd.WholeProjectRefactorer.renameBehaviorProperty(
gd.WholeProjectRefactorer.renameEventsBasedBehaviorProperty(
project,
eventsFunctionsExtension,
eventsBasedBehavior,
@@ -541,12 +557,24 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
editedEventsBasedBehavior,
};
},
() => {
async () => {
// TODO: Is this logic the same as in _onEventsBasedBehaviorRenamed?
if (!editedEventsBasedBehavior) {
// If we're closing the properties of a behavior, notify parent
// that a behavior was edited (to trigger reload of extensions)
if (this.props.onBehaviorEdited) {
this.props.onBehaviorEdited();
await this.props.onBehaviorEdited();
// Once extensions are reloaded, ensure the project stays valid by
// filling any invalid required behavior property in the objects
// of the project.
//
// We need to do that as "required behavior" properties may have been
// added (or the type of the required behavior changed) in the dialog.
gd.WholeProjectRefactorer.fixInvalidRequiredBehaviorProperties(
this.props.project
);
}
// Reload the selected events function, if any, as the behavior was
@@ -555,7 +583,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
if (this.state.selectedEventsFunction) {
this._loadEventsFunctionFrom(
this.props.project,
this.state.selectedEventsFunction
this.state.selectedEventsFunction,
this.state.selectedEventsBasedBehavior
);
}
}
@@ -640,7 +669,8 @@ export default class EventsFunctionsExtensionEditor extends React.Component<
onParametersOrGroupsUpdated={() => {
this._loadEventsFunctionFrom(
project,
selectedEventsFunction
selectedEventsFunction,
selectedEventsBasedBehavior
);
this.forceUpdate();
}}
@@ -97,7 +97,8 @@ export const declareBehaviorMetadata = (
if (
propertyType === 'String' ||
propertyType === 'Choice' ||
propertyType === 'Color'
propertyType === 'Color' ||
propertyType === 'Behavior'
) {
element.setStringValue(newValue);
} else if (propertyType === 'Number') {
@@ -125,7 +126,8 @@ export const declareBehaviorMetadata = (
if (
propertyType === 'String' ||
propertyType === 'Choice' ||
propertyType === 'Color'
propertyType === 'Color' ||
propertyType === 'Behavior'
) {
newProperty.setValue(
behaviorContent.getChild(propertyName).getStringValue()
@@ -159,7 +161,8 @@ export const declareBehaviorMetadata = (
if (
propertyType === 'String' ||
propertyType === 'Choice' ||
propertyType === 'Color'
propertyType === 'Color' ||
propertyType === 'Behavior'
) {
element.setStringValue(enumeratedProperty.value);
} else if (propertyType === 'Number') {
+4 -3
View File
@@ -18,8 +18,8 @@ import CreateProfile from '../Profile/CreateProfile';
import LimitDisplayer from '../Profile/LimitDisplayer';
import {
displayProjectErrorsBox,
getErrors,
} from '../ProjectManager/ProjectErrorsChecker';
getProjectPropertiesErrors,
} from '../Utils/ProjectErrorsChecker';
import { type Limit } from '../Utils/GDevelopServices/Usage';
import BuildsWatcher from './Builds/BuildsWatcher';
import BuildStepsProgress, {
@@ -94,7 +94,8 @@ export default class ExportLauncher extends Component<Props, State> {
const { project, exportPipeline } = this.props;
sendExportLaunched(exportPipeline.name);
if (!displayProjectErrorsBox(t, getErrors(t, project))) return;
if (!displayProjectErrorsBox(t, getProjectPropertiesErrors(t, project)))
return;
const handleError = (message: string) => (err: Error) => {
if (!this.state.errored) {
@@ -43,7 +43,7 @@ export type RenderEditorContainerProps = {|
) => void,
// Events function management:
onLoadEventsFunctionsExtensions: () => void,
onLoadEventsFunctionsExtensions: () => Promise<void>,
onCreateEventsFunction: (
extensionName: string,
eventsFunction: gdEventsFunction
@@ -60,11 +60,18 @@ export class EventsFunctionsExtensionEditorContainer extends React.Component<Ren
}
}
_onBehaviorEdited = () => {
_onBehaviorEdited = async () => {
// Immediately trigger the reload/regeneration of extensions
// as a change in the properties of a behavior can create changes
// in actions/conditions/expressions to manipulate these properties.
this.props.onLoadEventsFunctionsExtensions();
try {
await this.props.onLoadEventsFunctionsExtensions();
} catch (error) {
console.warn(
'Error while loading events functions extensions - ignoring this in the context of the EventsFunctionsExtensionEditorContainer.',
error
);
}
};
previewWillStart = () => {
+8 -3
View File
@@ -117,6 +117,7 @@ import { useDiscordRichPresence } from '../Utils/UpdateDiscordRichPresence';
import { useResourceFetcher } from '../ProjectsStorage/ResourceFetcher';
import { delay } from '../Utils/Delay';
import { type ExtensionShortHeader } from '../Utils/GDevelopServices/Extension';
import { findAndLogProjectPreviewErrors } from '../Utils/ProjectErrorsChecker';
const GD_STARTUP_TIMES = global.GD_STARTUP_TIMES || [];
@@ -1208,6 +1209,11 @@ const MainFrame = (props: Props) => {
autosaveProjectIfNeeded();
// Note that in the future, this kind of checks could be done
// and stored in a "diagnostic report", rather than hiding errors
// from the user.
findAndLogProjectPreviewErrors(currentProject);
eventsFunctionsExtensionsState
.ensureLoadFinished()
.then(() =>
@@ -2164,11 +2170,10 @@ const MainFrame = (props: Props) => {
onOpenGamesShowcase: () => onOpenGamesShowcase(),
onOpenHelpFinder: () => openHelpFinderDialog(true),
onOpenLanguageDialog: () => openLanguageDialog(true),
onLoadEventsFunctionsExtensions: () => {
onLoadEventsFunctionsExtensions: () =>
eventsFunctionsExtensionsState.loadProjectEventsFunctionsExtensions(
currentProject
);
},
),
onDeleteResource: (
resource: gdResource,
cb: boolean => void
@@ -10,10 +10,10 @@ import SelectOption from '../UI/SelectOption';
import Dialog from '../UI/Dialog';
import SemiControlledTextField from '../UI/SemiControlledTextField';
import {
getErrors,
getProjectPropertiesErrors,
displayProjectErrorsBox,
validatePackageName,
} from './ProjectErrorsChecker';
} from '../Utils/ProjectErrorsChecker';
import DismissableAlertMessage from '../UI/DismissableAlertMessage';
import HelpButton from '../UI/HelpButton';
import { ResponsiveLineStackLayout, ColumnStackLayout } from '../UI/Layout';
@@ -127,7 +127,7 @@ function applyPropertiesToProject(
project.setFolderProject(isFolderProject);
project.setUseDeprecatedZeroAsDefaultZOrder(useDeprecatedZeroAsDefaultZOrder);
return displayProjectErrorsBox(t, getErrors(t, project));
return displayProjectErrorsBox(t, getProjectPropertiesErrors(t, project));
}
function ProjectPropertiesDialog(props: Props) {
@@ -17,7 +17,8 @@ export default (
instance: Instance,
propertyName: string,
newValue: string
) => void
) => void,
object: ?gdObject
): Schema => {
const propertyNames = properties.keys();
const propertyFields = mapFor(0, propertyNames.size(), i => {
@@ -114,6 +115,37 @@ export default (
getLabel,
getDescription,
};
} else if (valueType === 'behavior') {
const behaviorType =
property.getExtraInfo().size() > 0 ? property.getExtraInfo().at(0) : '';
return {
name,
valueType: 'string',
getChoices: () => {
return !object || behaviorType === ''
? []
: object
.getAllBehaviorNames()
.toJSArray()
.map(name =>
object.getBehavior(name).getTypeName() === behaviorType
? name
: null
)
.filter(Boolean)
.map(value => ({ value, label: value }));
},
getValue: (instance: Instance): string => {
return getProperties(instance)
.get(name)
.getValue();
},
setValue: (instance: Instance, newValue: string) => {
onUpdateProperty(instance, name, newValue);
},
getLabel,
getDescription,
};
} else if (valueType === 'resource') {
// Resource is a "string" (with a selector in the UI)
// $FlowFixMe - assume the passed resource kind is always valid.
@@ -1,6 +1,7 @@
// @flow
import { showErrorBox } from '../UI/Messages/MessageBox';
import values from 'lodash/values';
const gd: libGDevelop = global.gd;
export type ProjectError = {
type: 'error' | 'warning',
@@ -20,7 +21,13 @@ export const validatePackageName = (packageName: string) => {
type TFunction = string => string; //TODO
export const getErrors = (t: TFunction, project: gdProject): ProjectErrors => {
/**
* Check if there is any blocking error in the project properties.
*/
export const getProjectPropertiesErrors = (
t: TFunction,
project: gdProject
): ProjectErrors => {
const errors: ProjectErrors = {};
const addError = (
@@ -109,11 +116,37 @@ export const displayProjectErrorsBox = (
return false;
};
export const validateJSON = (jsonString: string): boolean => {
try {
JSON.parse(jsonString);
return true;
} catch {
return false;
/**
* Log errors found in the project and that should stop a preview (or an export)
* of the project to be done.
*
* For now, not all errors are reported (errors when generating events should ideally
* be stored and shown in the editor).
* See https://trello.com/c/IiLgNR16/462-add-a-diagnostic-report-to-warn-about-potential-issues-in-the-game-and-show-them-in-the-events-sheet
*/
export const findAndLogProjectPreviewErrors = (project: gdProject) => {
const problems = gd.WholeProjectRefactorer.findInvalidRequiredBehaviorProperties(
project
);
for (let index = 0; index < problems.size(); index++) {
const problem = problems.at(index);
const suggestedBehaviorNames = gd.WholeProjectRefactorer.getBehaviorsWithType(
problem.getSourceObject(),
problem.getExpectedBehaviorTypeName()
).toJSArray();
console.error(
`Invalid value for required behavior property "${problem.getSourcePropertyName()}" in object ${problem
.getSourceObject()
.getName()} for behavior ${problem
.getSourceBehaviorContent()
.getName()}.`
);
console.info(
`Expected behavior of type ${problem.getExpectedBehaviorTypeName()}. Possibles values are: ${suggestedBehaviorNames.join(
', '
) || '(none)'}.`
);
}
};