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.
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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(){};
|
||||
};
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
After Width: | Height: | Size: 551 B |
|
After Width: | Height: | Size: 459 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 502 B |
|
After Width: | Height: | Size: 449 B |
|
After Width: | Height: | Size: 491 B |
|
After Width: | Height: | Size: 554 B |
|
After Width: | Height: | Size: 540 B |
|
After Width: | Height: | Size: 532 B |
|
After Width: | Height: | Size: 558 B |
|
After Width: | Height: | Size: 495 B |
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 492 B |
|
After Width: | Height: | Size: 489 B |
|
After Width: | Height: | Size: 566 B |
|
After Width: | Height: | Size: 553 B |
|
After Width: | Height: | Size: 512 B |
|
After Width: | Height: | Size: 482 B |
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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 (
|
||||
|
||||
@@ -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') {
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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)'}.`
|
||||
);
|
||||
}
|
||||
};
|
||||