/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ #include #include #include #include #include #include #include #include #include #include #include #include // for pow #include #include #include // Components #include #include #include #include #include #include #include namespace UnitTest { using namespace AZ; enum GlobalEnum { GE_VALUE1 = 0, GE_VALUE2, }; enum class GlobalClassEnum { Value1, Value2, Value3, }; struct GlobalData { AZ_TYPE_INFO(GlobalData, "{4F35A5E6-568E-43C8-851A-4D2315E9BAD0}"); GlobalData() {} char data[512]; }; int g_globalValue = 501; int g_globalData = 0; int g_globalTestClassesConstructed = 0; int g_globalTestClassesDestructed = 0; GlobalClassEnum globalClassEnumValue = GlobalClassEnum::Value3; int globalPropertyGetter() { return g_globalValue; } void globalPropertySetter(int value) { g_globalValue = value; } int globalMethod(int a) { return a + 3; } void globalMethod1() { g_globalData = 1001; } int globalMethod2WithDefaultArgument(int value) { return value + 1; } int globalMethodContainers(const AZStd::vector& value) { return value[0]; } int globalMethodPair(const AZStd::pair& value) { return value.second ? value.first : -1; } int globalMethodToOverride(int a) { return a * 3; } void globalMethodOverride(ScriptDataContext& dc) { (void)dc; } GlobalData globalMethodLarge() { return GlobalData(); } GlobalClassEnum globalMethodGetClassEnum() { return GlobalClassEnum::Value1; } void globalMethodSetClassEnum(GlobalClassEnum value) { globalClassEnumValue = value; } // Global Enum class wrapper, till the "namespace" open/close functionallity is missing struct BehaviorGlobalClassEnumWrapper { AZ_TYPE_INFO(BehaviorGlobalClassEnumWrapper,"{de36dcf3-7c25-4053-b747-1148240cda68}"); }; class BehaviorTestClass { public: AZ_RTTI(BehaviorTestClass, "{26398112-E4F1-4912-80E6-D81152116D02}"); AZ_CLASS_ALLOCATOR(BehaviorTestClass, AZ::SystemAllocator, 0); BehaviorTestClass() : m_data(1) , m_data1(2) , m_dataReadOnly(3) { g_globalData = 1020; g_globalTestClassesConstructed++; } BehaviorTestClass(const BehaviorTestClass& other) : m_data(other.m_data) , m_data1(other.m_data1) , m_dataReadOnly(other.m_dataReadOnly) { g_globalData = 1025; g_globalTestClassesConstructed++; } BehaviorTestClass(int data) : m_data(data) , m_data1(3) , m_dataReadOnly(4) { g_globalData = 1030; g_globalTestClassesConstructed++; } virtual ~BehaviorTestClass() { g_globalData = 1040; g_globalTestClassesDestructed++; } enum MyEnum { ME_VALUE0, ME_VALUE1, }; enum class MyClassEnum { MCE_VALUE0, MCE_VALUE1, }; void Method1() { g_globalData = 1050; } int Method2(int* a) const { return *a + 1; } BehaviorTestClass* GetClass() { return this; } int GetData() const { return m_data; } void SetData(int data) { m_data = data; m_data1 = m_data + 10; m_dataReadOnly = m_data1 + 100; } static void StaticMethod(int newData) { g_globalData = newData; } void SpecialScriptMethod(ScriptDataContext& dc) { dc.PushResult(303); } void MethodToOverrideInScript() { g_globalData = 1060; } void Method1ScriptOverride(ScriptDataContext&) { g_globalData = 1070; } void MethodToOverrideInScriptWithANonMember() const { g_globalData = 1080; } BehaviorTestClass AddOperator(BehaviorTestClass& rhs) { BehaviorTestClass result = *this; result.m_data += rhs.m_data; result.m_data1 += rhs.m_data1; result.m_dataReadOnly += rhs.m_dataReadOnly; return result; } AZStd::string ToString() { return AZStd::string::format("%d\n", m_data); } bool BoundsCheckMethodWithDefaultValue(float value, float epsilon, float minBounds, float maxBounds) { (void)epsilon; return value >= minBounds && value < maxBounds; } int m_data; int m_data1; int m_dataReadOnly; }; class BehaviorDerivedTestClass : public BehaviorTestClass { public: AZ_RTTI(BehaviorDerivedTestClass, "{dba8a4e3-8fab-4223-94a6-874c6cff88e5}", BehaviorTestClass); int m_data; }; void TestMethodToScriptOverride(const BehaviorTestClass* thisPtr, ScriptDataContext&) { (void)thisPtr; g_globalData = 1010; } void TestScriptNonIntrusiveConstructor(BehaviorTestClass* thisPtr, ScriptDataContext& dc) { new(thisPtr) BehaviorTestClass(); if (dc.GetNumArguments() && dc.IsNumber(0)) { int data = 0; dc.ReadArg(0, data); thisPtr->m_data = data; thisPtr->m_data1 = data * 2; thisPtr->m_dataReadOnly = data * 3; } else { thisPtr->m_data = 1000; thisPtr->m_data1 = 1005; thisPtr->m_dataReadOnly = 1007; } } class BehaviorTestBusEvents : public AZ::EBusTraits { public: ////////////////////////////////////////////////////////////////////////// // Configure the EBus static const EBusAddressPolicy AddressPolicy = EBusAddressPolicy::ById; using BusIdType = int; static const bool EnableEventQueue = true; ////////////////////////////////////////////////////////////////////////// virtual void OnEvent(int data) = 0; virtual int OnEventWithResult(int data) { return data + 2; } virtual int OnEventWithResultContainer(const AZStd::vector values) = 0; virtual GlobalClassEnum OnEventWithClassEnumResult() = 0; virtual AZStd::string OnEventWithStringResult() { return ""; } virtual BehaviorTestClass OnEventWithClassResult() { return BehaviorTestClass(); } virtual AZStd::string OnEventWithDefaultValueAndStringResult(AZStd::string_view view1, AZStd::string_view) { return AZStd::string::format("Default Value: %s", view1.data()); } virtual void OnEventConst() const {}; virtual BehaviorTestClass OnEventResultWithBehaviorClassParameter(BehaviorTestClass data) { return BehaviorTestClass(); }; }; typedef AZ::EBus BehaviorTestBus; class BehaviorTestBusHandler : public BehaviorTestBus::Handler, public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER(BehaviorTestBusHandler, "{19F5C8C8-4260-46B1-B624-997CD3F10CBD}", AZ::SystemAllocator , OnEvent , OnEventWithResult , OnEventWithResultContainer , OnEventWithClassEnumResult , OnEventWithStringResult , OnEventWithClassResult , OnEventWithDefaultValueAndStringResult , OnEventResultWithBehaviorClassParameter ); // User code void OnEvent(int data) override { // you can get the index yourself or use the FN_xxx enum FN_OnEvent static int eventIndex = GetFunctionIndex("OnEvent"); AZ_Assert(eventIndex != -1, "We can't find event with name %s", "OnEvent"); Call(eventIndex, data); } int OnEventWithResult(int data) override { int result = 0; // default result as a function hook might not exists CallResult(result, FN_OnEventWithResult, data); return result; } int OnEventWithResultContainer(const AZStd::vector values) override { int result = 0; CallResult(result, FN_OnEventWithResultContainer, values); return result; } GlobalClassEnum OnEventWithClassEnumResult() override { GlobalClassEnum result = GlobalClassEnum::Value1; CallResult(result, FN_OnEventWithClassEnumResult); return result; } AZStd::string OnEventWithStringResult() override { AZStd::string result; CallResult(result, FN_OnEventWithStringResult); return result; } BehaviorTestClass OnEventWithClassResult() override { BehaviorTestClass result; CallResult(result, FN_OnEventWithClassResult); return result; } AZStd::string OnEventWithDefaultValueAndStringResult(AZStd::string_view view1, AZStd::string_view view2) override { return BehaviorTestBus::Handler::OnEventWithDefaultValueAndStringResult(view1, view2); } BehaviorTestClass OnEventResultWithBehaviorClassParameter(BehaviorTestClass data) override { BehaviorTestClass result = data; return result; } }; class BehaviorTestBusHandlerWithDoc : public BehaviorTestBus::Handler, public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER_WITH_DOC(BehaviorTestBusHandlerWithDoc, "{C0D4FE98-DBE1-439A-ACBA-B3767863C560}", AZ::SystemAllocator , OnEvent, ({"data", "Data to pass in"}) , OnEventWithResult, ({"data", "Data to pass in"}) , OnEventWithResultContainer, ({ "values", "Vector of integers to forward" }) , OnEventWithClassEnumResult, () , OnEventWithStringResult, () , OnEventWithClassResult, () , OnEventWithDefaultValueAndStringResult, ({ "defaultView", "string_view which contains literal to print by default" }, { "unusedView", "Unused test parameter" }) , OnEventResultWithBehaviorClassParameter, () ); // User code void OnEvent(int data) override { // you can get the index yourself or use the FN_xxx enum FN_OnEvent static int eventIndex = GetFunctionIndex("OnEvent"); AZ_Assert(eventIndex != -1, "We can't find event with name %s", "OnEvent"); Call(eventIndex, data); } int OnEventWithResult(int data) override { int result = 0; // default result as a function hook might not exists CallResult(result, FN_OnEventWithResult, data); return result; } int OnEventWithResultContainer(const AZStd::vector values) override { int result = 0; CallResult(result, FN_OnEventWithResultContainer, values); return result; } GlobalClassEnum OnEventWithClassEnumResult() override { GlobalClassEnum result = GlobalClassEnum::Value1; CallResult(result, FN_OnEventWithClassEnumResult); return result; } AZStd::string OnEventWithStringResult() override { AZStd::string result; CallResult(result, FN_OnEventWithStringResult); return result; } BehaviorTestClass OnEventWithClassResult() override { BehaviorTestClass result; CallResult(result, FN_OnEventWithClassResult); return result; } AZStd::string OnEventWithDefaultValueAndStringResult(AZStd::string_view view1, AZStd::string_view view2) override { return BehaviorTestBus::Handler::OnEventWithDefaultValueAndStringResult(view1, view2); } BehaviorTestClass OnEventResultWithBehaviorClassParameter(BehaviorTestClass data) override { BehaviorTestClass result = data; return result; } }; // traditional EBus Handler class NativeBehaviorTestBusHandler : public BehaviorTestBus::Handler { public: NativeBehaviorTestBusHandler() { m_enumResult = GlobalClassEnum::Value2; BehaviorTestBus::Handler::BusConnect(1); } void OnEvent(int data) override { g_globalData = 2000 + data; } int OnEventWithResult(int data) override { g_globalData = 2100 + data; return data + 3; } int OnEventWithResultContainer(const AZStd::vector values) override { g_globalData = 2100 + values[0]; return values[0]; } GlobalClassEnum OnEventWithClassEnumResult() override { return m_enumResult; } GlobalClassEnum m_enumResult; }; /// Hooks void OnEventHook(void* userData, int data) { (void)userData; g_globalData = 2200 + data; } int OnEventWithResultHook(void* userData, int data) { (void)userData; g_globalData = 2300 + data; return data + 30; } void OnEventGenericHook(void* userData, const char* eventName, int eventIndex, BehaviorValueParameter* result, int numParameters, BehaviorValueParameter* parameters) { (void)userData; (void)numParameters; (void)result; (void)eventName; (void)eventIndex; AZ_Assert(result == nullptr || strstr(eventName, "OnEventWithResult"), "We don't exepct result here"); AZ_Assert(numParameters == 1, "Invalid number of parameters!"); AZ_Assert(parameters[0].ConvertTo(), "The parameter should be int, and convertable to it"); int* intData = parameters[0].GetAsUnsafe(); g_globalData = 2400 + *intData; if (result) { result->StoreResult(*intData + 50); } } void globalGenericMethod(ScriptDataContext& dc) { if (dc.GetNumArguments()) { if (dc.IsNumber(0)) { int data; dc.ReadArg(0, data); dc.PushResult(data + 5); } } } class BehaviorTestSmartPtr { public: AZ_CLASS_ALLOCATOR(BehaviorTestSmartPtr, AZ::SystemAllocator, 0); AZ_TYPE_INFO(BehaviorTestSmartPtr, "{b59d3416-a77c-4e71-b638-4212705a07e6}"); BehaviorTestSmartPtr() : m_intrusiveRefCount(0) , m_data(303) { } ////////////////////////////////////////////////////////////////////////// // instrusive_ptr // TODO: Add intrusive pointers helpers (RefCounted and RefCountedThreadSave or as a policy RefCounter void add_ref() { ++m_intrusiveRefCount; } void release() { if (--m_intrusiveRefCount == 0) { delete this; } } int m_intrusiveRefCount; ////////////////////////////////////////////////////////////////////////// void TestFunction() { g_globalData = 1090; } int m_data; }; void IncrementBehaviorTestSmartPtrSharedPtr(AZStd::shared_ptr& sp) { if (sp) { ++sp->m_data; } }; void IncrementBehaviorTestSmartPtrIntrusivePtr(AZStd::intrusive_ptr& sp) { if (sp) { ++sp->m_data; } } void PointerIsNullptr(BehaviorTestSmartPtr* value) { AZ_TEST_ASSERT(value == nullptr); } void BehaviorTestTemplatedOnDemandReflection(const AZStd::vector& values) { g_globalData = 2500 + static_cast(values.size()); } int BehaviorGlobalMethodAfterBind() { return 3030; } int BehaviorGlobalPropertyGetAfterBind() { return g_globalData + 101; } void BehaviorGlobalPropertySetAfterBind(int data) { g_globalData = data; } struct BehaviorClassAfterBind { AZ_CLASS_ALLOCATOR(BehaviorClassAfterBind, AZ::SystemAllocator, 0); AZ_TYPE_INFO(BehaviorClassAfterBind, "{72de521d-5880-43b7-93e9-ab06a3770946}"); int m_data; }; class ClassRequestBusEvents : public AZ::EBusTraits { public: virtual int GetData() = 0; virtual void SetData(int data) = 0; }; using ClassRequestEBus = EBus; class ClassInteractingWithEBus : public ClassRequestEBus::Handler { public: AZ_TYPE_INFO(ClassInteractingWithEBus, "{28d9591f-d233-442f-af62-81096b64d703}"); ClassInteractingWithEBus() : m_data(0) { BusConnect(); } int GetData() override { return m_data; } void SetData(int data) override { m_data = data; } int m_data; }; static void ScriptErrorAssertCB(AZ::ScriptContext*, AZ::ScriptContext::ErrorType, const char*) { AZ_TEST_ASSERT(false); } class BehaviorContextTest : public BehaviorContextFixture { public: void SetUp() override { BehaviorContextFixture::SetUp(); ResetGlobalVars(); } void ResetGlobalVars() { g_globalValue = 501; g_globalData = 0; globalClassEnumValue = GlobalClassEnum::Value3; } void run() { // Constants/Enum m_behaviorContext->Constant("globalConstant", []() { return 3.14f; }); m_behaviorContext->Constant("globalConstantMacro", BehaviorConstant(3.14f)); m_behaviorContext->Enum<(int)GE_VALUE1>("GE_VALUE1"); m_behaviorContext->Enum<(int)GlobalClassEnum::Value1>("Value1"); m_behaviorContext->Enum<(int)GlobalClassEnum::Value2>("Value2"); // Property m_behaviorContext->Property("globalProperty", &globalPropertyGetter, &globalPropertySetter) ->Attribute("GlobalPropAttr", 1); m_behaviorContext->Property("globalPropertyLambda", []() { return g_globalValue; }, [](int v) { g_globalValue = v; }); m_behaviorContext->Property("globalPropertyMacro", BehaviorValueProperty(&g_globalValue)); m_behaviorContext->Property("globalPropertyReadOnly", BehaviorValueGetter(&g_globalValue), nullptr); // read only property // Property by address??? m_behaviorContext->Class(); // Method const int defaultIntValue = 20; m_behaviorContext->Method("globalMethod", &globalMethod, m_behaviorContext->MakeDefaultValues(555)) ->Attribute("GlobalMethodAttr", 5); m_behaviorContext->Method("globalMethod1", &globalMethod1); m_behaviorContext->Method("globalMethod2WithDefaultArgument", &globalMethod2WithDefaultArgument, { {{"Value", "An Integer argument", m_behaviorContext->MakeDefaultValue(defaultIntValue)}} }); m_behaviorContext->Method("globalMethodContainers", &globalMethodContainers); m_behaviorContext->Method("globalMethodPair", &globalMethodPair); m_behaviorContext->Method("globalMethodToOverride", &globalMethodToOverride) ->Attribute(Script::Attributes::MethodOverride, &globalMethodOverride); m_behaviorContext->Method("globalMethodLarge", &globalMethodLarge); m_behaviorContext->Method("TestTemplatedOnDemandReflection", &BehaviorTestTemplatedOnDemandReflection); m_behaviorContext->Method("IncrementTestSmartPtrSharedPtr", &IncrementBehaviorTestSmartPtrSharedPtr); m_behaviorContext->Method("IncrementTestSmartPtrIntrusivePtr", &IncrementBehaviorTestSmartPtrIntrusivePtr); m_behaviorContext->Method("globalMethodGetClassEnum", &globalMethodGetClassEnum); m_behaviorContext->Method("globalMethodSetClassEnum", &globalMethodSetClassEnum); m_behaviorContext->Method("PointerIsNullptr", &PointerIsNullptr); // Class m_behaviorContext->Class()-> Constructor()-> Attribute("ClassAttr",10)-> Attribute(AZ::Script::Attributes::Storage,AZ::Script::Attributes::StorageType::Value)-> Attribute(AZ::Script::Attributes::ConstructorOverride,&TestScriptNonIntrusiveConstructor)-> Constant("epsilon", BehaviorConstant(0.001f))-> Enum<(int)BehaviorTestClass::ME_VALUE0>("ME_VALUE0")-> Enum<(int)BehaviorTestClass::MyClassEnum::MCE_VALUE0>("MCE_VALUE0")-> Enum<(int)BehaviorTestClass::MyClassEnum::MCE_VALUE1>("MCE_VALUE1")-> Method("Method1", &BehaviorTestClass::Method1)-> Attribute("MethodAttr", 20)-> Method("Method2", &BehaviorTestClass::Method2)-> Method("SpecialScriptMethod", &BehaviorTestClass::SpecialScriptMethod)-> Method("OverrideMethod", &BehaviorTestClass::MethodToOverrideInScript)-> Attribute(Script::Attributes::MethodOverride, &BehaviorTestClass::Method1ScriptOverride)-> Method("OverrideMethod1", &BehaviorTestClass::MethodToOverrideInScriptWithANonMember)-> Attribute(Script::Attributes::MethodOverride, &TestMethodToScriptOverride)-> Method("GetClass", &BehaviorTestClass::GetClass)-> Method("AddOperator", &BehaviorTestClass::AddOperator)-> Attribute(AZ::Script::Attributes::Operator,AZ::Script::Attributes::OperatorType::Add)-> Method("ToString", &BehaviorTestClass::ToString)-> Method("StaticMethod", &BehaviorTestClass::StaticMethod)-> Method("MemberWithDefaultValues", &BehaviorTestClass::BoundsCheckMethodWithDefaultValue, { {{"value", "Value which will be checked to be within the two bounds arguments"}, {"delta", "The epsilon value", m_behaviorContext->MakeDefaultValue(0.1f)}, {"minBound", "The minimum bounds value,", m_behaviorContext->MakeDefaultValue(0.0f)}, {"maxBound", "The maximum bounds value", m_behaviorContext->MakeDefaultValue(1.0f)}} })-> Property("data", &BehaviorTestClass::GetData, &BehaviorTestClass::SetData)-> Attribute("PropAttr", 30)-> Property("data1", BehaviorValueProperty(&BehaviorTestClass::m_data1))-> Property("dataReadOnly", BehaviorValueGetter(&BehaviorTestClass::m_dataReadOnly), nullptr); m_behaviorContext->Class()-> Property("derivedData", BehaviorValueProperty(&BehaviorDerivedTestClass::m_data)); m_behaviorContext->Class()-> Method("TestFunction", &BehaviorTestSmartPtr::TestFunction)-> Property("data", BehaviorValueProperty(&BehaviorTestSmartPtr::m_data)); // Class Global Class enum wrapper m_behaviorContext->Class() ->Constant("VALUE1", BehaviorConstant(GlobalClassEnum::Value1)) ->Constant("VALUE2", BehaviorConstant(GlobalClassEnum::Value2)) ; // EBus const AZStd::string_view defaultStringViewValue = "DEFAULT!!!!"; AZStd::string expectedDefaultValueAndStringResult = AZStd::string::format("Default Value: %s", defaultStringViewValue.data()); BehaviorDefaultValuePtr defaultStringViewBehaviorValue = aznew BehaviorDefaultValue(defaultStringViewValue); BehaviorDefaultValuePtr superDefaultStringViewBehaviorValue = aznew BehaviorDefaultValue(AZStd::string_view("SUPER DEFAULT!!!!")); m_behaviorContext->EBus("TestBus") ->Attribute("EBusAttr", 40) ->Handler() ->Attribute("HandlerAttr", 50) ->Event("OnEvent", &BehaviorTestBus::Events::OnEvent) ->Attribute("EventAttr", 60) ->Event("OnEventWithResult", &BehaviorTestBus::Events::OnEventWithResult) ->Event("OnEventWithResultContainer", &BehaviorTestBus::Events::OnEventWithResultContainer) ->Event("OnEventWithClassEnumResult", &BehaviorTestBus::Events::OnEventWithClassEnumResult) ->Event("OnEventWithStringResult", &BehaviorTestBus::Events::OnEventWithStringResult) ->Event("OnEventResultWithBehaviorClassParameter", &BehaviorTestBus::Events::OnEventResultWithBehaviorClassParameter) ->Event("OnEventWithDefaultValueAndStringResult", &BehaviorTestBus::Events::OnEventWithDefaultValueAndStringResult, { {{"view1", "string_view without string trait", defaultStringViewBehaviorValue, AZ::BehaviorParameter::TR_NONE, AZ::BehaviorParameter::TR_STRING}, {"view2", "string_view with string trait", superDefaultStringViewBehaviorValue}} }) // Remove string trait from parameter ->Event("OnEventConst", &BehaviorTestBus::Events::OnEventConst) ; // Calls BehaviorMethod* method = m_behaviorContext->m_methods.find("globalMethod")->second; int result = 0; method->InvokeResult(result,3); AZ_TEST_ASSERT(result == 6); // check with default values method->InvokeResult(result); AZ_TEST_ASSERT(result == 558); // Check attr AZ_Assert(method->m_attributes[0].first == Crc32("GlobalMethodAttr"), "Expected attribute"); AttributeData* attrData = azrtti_cast*>(method->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); int attrValue = attrData->Get(nullptr); (void)attrValue; AZ_Assert(attrValue == 5, "Data should be 5"); method = m_behaviorContext->m_methods.find("globalMethod1")->second; method->Invoke(); method = m_behaviorContext->m_methods.find("globalMethod2WithDefaultArgument")->second; int defaultValueMethodResult = 0; method->InvokeResult(defaultValueMethodResult); EXPECT_EQ(defaultIntValue + 1, defaultValueMethodResult); AZStd::vector values = { 10, 11, 12 }; method = m_behaviorContext->m_methods.find("globalMethodContainers")->second; method->InvokeResult(result, values); // Global property BehaviorProperty* prop = m_behaviorContext->m_properties.find("globalProperty")->second; prop->m_setter->Invoke(3); prop->m_getter->InvokeResult(result); // Check attr AZ_Assert(prop->m_attributes[0].first == Crc32("GlobalPropAttr"), "Expected attribute"); attrData = azrtti_cast*>(prop->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); attrValue = attrData->Get(nullptr); AZ_Assert(attrValue == 1, "Data should be 1"); // Global read only property prop = m_behaviorContext->m_properties.find("globalPropertyReadOnly")->second; AZ_Assert(prop->m_setter == nullptr, "This property should be read only!"); prop->m_getter->InvokeResult(result); // Constant prop = m_behaviorContext->m_properties.find("globalConstant")->second; float fResult = 0.0f; prop->m_getter->InvokeResult(fResult); // Enum GlobalClassEnum enumResult = GlobalClassEnum::Value2; method = m_behaviorContext->m_methods.find("globalMethodGetClassEnum")->second; method->InvokeResult(enumResult); AZ_TEST_ASSERT(enumResult == GlobalClassEnum::Value1); method = m_behaviorContext->m_methods.find("globalMethodSetClassEnum")->second; method->Invoke(GlobalClassEnum::Value2); // Classes BehaviorClass* behaviorClass = m_behaviorContext->m_classes.find("BehaviorTestClass")->second; BehaviorObject classInstance = behaviorClass->Create(); { // Check attr AZ_Assert(behaviorClass->m_attributes[0].first == Crc32("ClassAttr"), "Expected attribute"); attrData = azrtti_cast*>(behaviorClass->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); attrValue = attrData->Get(classInstance.m_address); AZ_Assert(attrValue == 10, "Data should be 10"); } method = behaviorClass->m_methods.find("Method1")->second; method->Invoke(classInstance); { // Check attr AZ_Assert(method->m_attributes[0].first == Crc32("MethodAttr"), "Expected attribute"); attrData = azrtti_cast*>(method->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); attrValue = attrData->Get(classInstance.m_address); AZ_Assert(attrValue == 20, "Data should be 20"); } method = behaviorClass->m_methods.find("Method2")->second; EXPECT_TRUE(method->m_isConst); // Generic class instance int v = 5; method->InvokeResult(result, classInstance, &v); // Generic result BehaviorObject tc; method = behaviorClass->m_methods.find("GetClass")->second; method->InvokeResult(tc, classInstance); EXPECT_TRUE(method->IsMember()); method = behaviorClass->m_methods.find("StaticMethod")->second; EXPECT_FALSE(method->IsMember()); method = behaviorClass->m_methods.find("MemberWithDefaultValues")->second; EXPECT_TRUE(method->IsMember()); bool withinBounds = false; method->InvokeResult(withinBounds, classInstance, 0.4f); EXPECT_TRUE(withinBounds); method->InvokeResult(withinBounds, classInstance, -1.1f); EXPECT_FALSE(withinBounds); // Class property prop = behaviorClass->m_properties.find("data")->second; EXPECT_TRUE(prop->m_getter->m_isConst); EXPECT_FALSE(prop->m_setter->m_isConst); prop->m_setter->Invoke(classInstance,12); prop->m_getter->InvokeResult(result,classInstance); { // Check attr AZ_Assert(prop->m_attributes[0].first == Crc32("PropAttr"), "Expected attribute"); attrData = azrtti_cast*>(prop->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); attrValue = attrData->Get(classInstance.m_address); AZ_Assert(attrValue == 30, "Data should be 30"); } // Class value property prop = behaviorClass->m_properties.find("data1")->second; prop->m_setter->Invoke(classInstance, 30); prop->m_getter->InvokeResult(result, classInstance); // Class value read only property prop = behaviorClass->m_properties.find("dataReadOnly")->second; AZ_Assert(prop->m_setter == nullptr, "Setter should be null!"); prop->m_getter->InvokeResult(result, classInstance); // Test function conversion of parameters using RTTI (it this case base classes) method = behaviorClass->m_methods.find("Method1")->second; BehaviorDerivedTestClass tt; method->Invoke(&tt); ////////////////////////////////////////////////////////////////////////// // EBus ////////////////////////////////////////////////////////////////////////// // EBus - send events BehaviorTestBus::BusIdType testId = 1; BehaviorEBus* behaviorEBus = m_behaviorContext->m_ebuses.find("TestBus")->second; { // Check attr AZ_Assert(behaviorEBus->m_attributes[0].first == Crc32("EBusAttr"), "Expected attribute"); attrData = azrtti_cast*>(behaviorEBus->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); attrValue = attrData->Get(nullptr); AZ_Assert(attrValue == 40, "Data should be 40"); } // create handler for events BehaviorEBusHandler* testBusHandler = nullptr; behaviorEBus->m_createHandler->InvokeResult(testBusHandler); testBusHandler->InstallHook("OnEvent", &OnEventHook); testBusHandler->InstallHook("OnEventWithResult", &OnEventWithResultHook); // generic event handler BehaviorEBusHandler* genericTestBusHandler = nullptr; behaviorEBus->m_createHandler->InvokeResult(genericTestBusHandler); genericTestBusHandler->InstallGenericHook("OnEvent", &OnEventGenericHook); genericTestBusHandler->InstallGenericHook("OnEventWithResult", &OnEventGenericHook); { // Check attr AZ_Assert(behaviorEBus->m_createHandler->m_attributes[0].first == Crc32("HandlerAttr"), "Expected attribute"); attrData = azrtti_cast*>(behaviorEBus->m_createHandler->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); attrValue = attrData->Get(nullptr); AZ_Assert(attrValue == 50, "Data should be 50"); } // connect handlers to receive events testBusHandler->Connect(testId); genericTestBusHandler->Connect(testId); // fire some events BehaviorEBusEventSender* ebusSender = &behaviorEBus->m_events.find("OnEvent")->second; method = ebusSender->m_broadcast; method->Invoke(10); { // Check attr AZ_Assert(ebusSender->m_attributes[0].first == Crc32("EventAttr"), "Expected attribute"); attrData = azrtti_cast*>(ebusSender->m_attributes[0].second); AZ_Assert(attrData != nullptr, "This must be a valid attribute!"); attrValue = attrData->Get(nullptr); AZ_Assert(attrValue == 60, "Data should be 60"); } method = behaviorEBus->m_events.find("OnEventWithResult")->second.m_broadcast; method->InvokeResult(result, 11); method = behaviorEBus->m_events.find("OnEventWithResult")->second.m_event; method->InvokeResult(result, testId, 15); method = behaviorEBus->m_events.find("OnEvent")->second.m_queueBroadcast; method->Invoke(1); method = behaviorEBus->m_events.find("OnEvent")->second.m_queueEvent; method->Invoke(testId, 2); method = behaviorEBus->m_events.find("OnEventWithDefaultValueAndStringResult")->second.m_broadcast; AZStd::string defaultStringResultValue; method->InvokeResult(defaultStringResultValue); EXPECT_EQ(expectedDefaultValueAndStringResult, defaultStringResultValue); method = behaviorEBus->m_events.find("OnEventWithDefaultValueAndStringResult")->second.m_event; defaultStringResultValue.clear(); method->InvokeResult(defaultStringResultValue, testId); EXPECT_EQ(expectedDefaultValueAndStringResult, defaultStringResultValue); EXPECT_EQ(3u, method->GetNumArguments()); EXPECT_EQ(1u, method->GetMinNumberOfArguments()); EXPECT_TRUE(method->HasResult()); const AZ::BehaviorParameter* stringResultParam = method->GetResult(); EXPECT_NE(nullptr, stringResultParam); EXPECT_TRUE(AZ::BehaviorContextHelper::IsStringParameter(*stringResultParam)); // The first string_view param has removed the TR_STRING trait const AZ::BehaviorParameter* stringView1Param = method->GetArgument(1); EXPECT_NE(nullptr, stringView1Param); EXPECT_FALSE(AZ::BehaviorContextHelper::IsStringParameter(*stringView1Param)); // The second string_view param has the TR_STRING trait const AZ::BehaviorParameter* stringView2Param = method->GetArgument(2); EXPECT_NE(nullptr, stringView2Param); EXPECT_TRUE(AZ::BehaviorContextHelper::IsStringParameter(*stringView2Param)); method = behaviorEBus->m_events.find("OnEventConst")->second.m_event; EXPECT_TRUE(method->m_isConst); method = behaviorEBus->m_events.find("OnEventConst")->second.m_broadcast; EXPECT_TRUE(method->m_isConst); method = behaviorEBus->m_events.find("OnEventConst")->second.m_queueEvent; EXPECT_TRUE(method->m_isConst); method = behaviorEBus->m_events.find("OnEventConst")->second.m_queueBroadcast; EXPECT_TRUE(method->m_isConst); BehaviorTestBus::ExecuteQueuedEvents(); delete testBusHandler; delete genericTestBusHandler; // class enums { NativeBehaviorTestBusHandler nativeTestBusHandler; ebusSender = &behaviorEBus->m_events.find("OnEventWithClassEnumResult")->second; method = ebusSender->m_broadcast; GlobalClassEnum classEnumResult = GlobalClassEnum::Value1; method->InvokeResult(classEnumResult); AZ_TEST_ASSERT(classEnumResult == GlobalClassEnum::Value2); } behaviorClass->Destroy(classInstance); m_behaviorContext->Method("globalGenericMethod", &globalGenericMethod); { ScriptContext sc; sc.BindTo(m_behaviorContext); // global methods sc.Execute("value = globalMethod(12)"); sc.Execute("value = globalGenericMethod(value)"); sc.Execute("value = globalMethodToOverride(20)"); sc.Execute("value = globalMethodLarge()"); // global method with a vector (OnDemandReflection) sc.Execute("intVector = vector_int()"); sc.Execute("intVector:push_back(74)"); AZ_TEST_START_TRACE_SUPPRESSION; ScriptContext::ErrorHook oldHook = sc.GetErrorHook(); sc.SetErrorHook(ScriptErrorAssertCB); sc.Execute("intVector.push_back(74)"); // missing self pointer sc.SetErrorHook(oldHook); AZ_TEST_STOP_TRACE_SUPPRESSION(1); // test index operators sc.Execute("globalProperty = intVector[1]"); // read a value using custom operator AZ_TEST_ASSERT(g_globalValue == 74); sc.Execute("intVector[6] = 128"); // write a value (set and resize) using custom operator sc.Execute("globalProperty = intVector[2]"); AZ_TEST_ASSERT(g_globalValue == 0); // on resize we initialize with default value sc.Execute("globalProperty = intVector[6]"); AZ_TEST_ASSERT(g_globalValue == 128); sc.Execute("pair = pair_int_bool()"); sc.Execute("pair.first = 7; pair.second = true;"); sc.Execute("globalProperty = globalMethodPair(pair)"); EXPECT_EQ(7, g_globalValue); sc.Execute("TestTemplatedOnDemandReflection(intVector)"); // global properties sc.Execute("value = globalProperty"); sc.Execute("globalProperty = value + 10"); // classes sc.Execute("testClass = BehaviorTestClass(11)"); // class methods sc.Execute("testClass:Method1()"); sc.Execute("testClass:Method2(12)"); sc.Execute("value = testClass:SpecialScriptMethod(11)"); sc.Execute("value = testClass:OverrideMethod(12)"); sc.Execute("value = testClass:OverrideMethod1(13)"); sc.Execute("testClass1 = testClass:GetClass()"); // Test static methods sc.Execute("BehaviorTestClass.StaticMethod(1090)"); EXPECT_EQ(1090, g_globalData); AZ_TEST_START_TRACE_SUPPRESSION; sc.Execute("testClass:StaticMethod(1090)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; sc.Execute("BehaviorTestClass.Method1(nil)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); // class properties sc.Execute("value = testClass.data"); sc.Execute("testClass.data = value + 11"); // class operators sc.Execute("testClass1 = BehaviorTestClass() testClass1.data = 203"); sc.Execute("testClass2 = testClass1 + testClass"); sc.Execute("testClass1.data = testClass2.data + 23"); // derived classes sc.Execute("derivedTestClass = BehaviorDerivedTestClass()"); sc.Execute("derivedTestClass.derivedData = 101"); sc.Execute("derivedTestClass.data = 13"); // smart ptr sc.Execute("smartIntrusivePtr = intrusive_ptr_BehaviorTestSmartPtr(BehaviorTestSmartPtr())"); sc.Execute("IncrementTestSmartPtrIntrusivePtr(smartIntrusivePtr)"); // pass the smartPointer to a function as a smart pointer sc.Execute("rawPointer = smartIntrusivePtr:get() rawPointer:TestFunction()"); // get the raw pointer and call a function sc.Execute("smartIntrusivePtr:TestFunction()"); // call the wrapped function directly from the smart pointer sc.Execute("PointerIsNullptr(nil)"); // ebus NativeBehaviorTestBusHandler myTestBusHandler; // broadcast sc.Execute("TestBus.Broadcast.OnEvent(10)"); // class enum sc.Execute("eventClassResult = TestBus.Broadcast.OnEventWithClassEnumResult()"); sc.Execute("globalMethodSetClassEnum(eventClassResult)"); // events sc.Execute("TestBus.Event.OnEvent(0,20)"); // send event to ID 0 (not listener) sc.Execute("TestBus.Event.OnEvent(1,30)"); // send event to ID 1 // queue broadcast sc.Execute("TestBus.QueueBroadcast.OnEvent(50)"); // queue event sc.Execute("TestBus.QueueEvent.OnEvent(1,60)"); // queue function sc.Execute("function QueuedFunctionOnTestBus(a,b,c)\ globalProperty = a + b + c\ end\ TestBus.QueueFunction(QueuedFunctionOnTestBus,1,2,3)"); BehaviorTestBus::ExecuteQueuedEvents(); myTestBusHandler.BusDisconnect(); ////////////////////////////////////////////////////////////////////////// // handling ebus multiple return values NativeBehaviorTestBusHandler myTestBusHandler1; NativeBehaviorTestBusHandler myTestBusHandler2; myTestBusHandler1.m_enumResult = GlobalClassEnum::Value1; myTestBusHandler2.m_enumResult = GlobalClassEnum::Value2; // use a table as result container sc.Execute("eventClassResult = { TestBus.Broadcast.OnEventWithClassEnumResult() }"); globalClassEnumValue = GlobalClassEnum::Value3; sc.Execute("globalMethodSetClassEnum(eventClassResult[1])"); AZ_TEST_ASSERT(globalClassEnumValue == GlobalClassEnum::Value2); sc.Execute("globalMethodSetClassEnum(eventClassResult[2])"); AZ_TEST_ASSERT(globalClassEnumValue == GlobalClassEnum::Value1); // use multiple variables as result container sc.Execute("result1, result2 = TestBus.Broadcast.OnEventWithClassEnumResult()"); globalClassEnumValue = GlobalClassEnum::Value3; sc.Execute("globalMethodSetClassEnum(result1)"); AZ_TEST_ASSERT(globalClassEnumValue == GlobalClassEnum::Value2); sc.Execute("globalMethodSetClassEnum(result2)"); AZ_TEST_ASSERT(globalClassEnumValue == GlobalClassEnum::Value1); // collect garbage before running next test so any destructors get called sc.Execute(R"LUA( collectgarbage() )LUA"); g_globalTestClassesConstructed = 0; g_globalTestClassesDestructed = 0; // test whether the behavior parameters that are passed by value have their // constructors/destructors called equally sc.Execute(R"LUA( local behaviorParameter = BehaviorTestClass(); local result = TestBus.Broadcast.OnEventResultWithBehaviorClassParameter(behaviorParameter); behaviorParameter = nil; result = nil; collectgarbage(); )LUA"); AZ_TEST_ASSERT(g_globalTestClassesConstructed > 0); AZ_TEST_ASSERT(g_globalTestClassesDestructed > 0); AZ_TEST_ASSERT(g_globalTestClassesConstructed == g_globalTestClassesDestructed); myTestBusHandler1.BusDisconnect(); myTestBusHandler2.BusDisconnect(); ////////////////////////////////////////////////////////////////////////// // create handler sc.Execute(R"LUA( testBusHandler = {} function testBusHandler:OnEvent(data) globalProperty = data globalProperty = TestBus.GetCurrentBusId() end function testBusHandler:OnEventWithClassEnumResult() return BehaviorGlobalClassEnumWrapper.VALUE2 end function testBusHandler:OnEventWithStringResult() return 'success'; end function testBusHandler:OnEventWithClassResult() local result = BehaviorTestClass(100); result.data = 100; return result; end testBusHandler = TestBus.Connect(testBusHandler,1) )LUA"); // check if we can handle event BehaviorTestBus::Broadcast(&BehaviorTestBus::Events::OnEvent, 101); // broadcast a class enum GlobalClassEnum globalClassEnumResult = GlobalClassEnum::Value1; BehaviorTestBus::BroadcastResult(globalClassEnumResult, &BehaviorTestBus::Events::OnEventWithClassEnumResult); AZ_TEST_ASSERT(globalClassEnumResult == GlobalClassEnum::Value2); AZStd::string stringResult; BehaviorTestBus::BroadcastResult(stringResult, &BehaviorTestBus::Events::OnEventWithStringResult); EXPECT_STREQ("success", stringResult.c_str()); BehaviorTestClass classResult; BehaviorTestBus::BroadcastResult(classResult, &BehaviorTestBus::Events::OnEventWithClassResult); EXPECT_EQ(100, classResult.m_data); // disconnect sc.Execute("testBusHandler:Disconnect()"); // test events BehaviorTestBus::Broadcast(&BehaviorTestBus::Events::OnEvent, 201); // it should have no effect ////////////////////////////////////////////////////////////////////////// // Test reflecting after the bind m_behaviorContext->Method("BehaviorGlobalMethodAfterBind", &BehaviorGlobalMethodAfterBind); sc.Execute("afterBindValue = BehaviorGlobalMethodAfterBind()"); m_behaviorContext->Property("globalPropertyAfterBind", &BehaviorGlobalPropertyGetAfterBind, &BehaviorGlobalPropertySetAfterBind); sc.Execute("globalPropertyAfterBind = afterBindValue"); AZ_TEST_ASSERT(g_globalData == 3030); m_behaviorContext->Class()-> Property("data", BehaviorValueProperty(&BehaviorClassAfterBind::m_data)); sc.Execute("classAfterBind = BehaviorClassAfterBind() classAfterBind.data = 1012 globalPropertyAfterBind = classAfterBind.data"); AZ_TEST_ASSERT(g_globalData == 1012); ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// // Classes using EBuses for communication and virtual ebus properties m_behaviorContext->Class()-> RequestBus("ClassRequestEBus"); m_behaviorContext->EBus("ClassRequestEBus")-> Attribute(AZ::Script::Attributes::DisallowBroadcast, true)-> Event("GetData", &ClassRequestEBus::Events::GetData)-> Event("SetData", &ClassRequestEBus::Events::SetData)-> VirtualProperty("data", "GetData", "SetData"); BehaviorClass* classInteractingWithEBus = m_behaviorContext->m_classes.find("ClassInteractingWithEBus")->second; AZ_TEST_ASSERT(classInteractingWithEBus->m_requestBuses.size() == 1); AZ_TEST_ASSERT(classInteractingWithEBus->m_requestBuses.find("ClassRequestEBus") != classInteractingWithEBus->m_requestBuses.end()); BehaviorEBus* classRequestBus = m_behaviorContext->m_ebuses.find("ClassRequestEBus")->second; AZ_TEST_ASSERT(classRequestBus->m_virtualProperties.size() == 1); const BehaviorEBus::VirtualProperty& virtualEBusProperty = classRequestBus->m_virtualProperties.find("data")->second; AZ_TEST_START_TRACE_SUPPRESSION; sc.Execute("ClassRequestEBus.Broadcast.GetData()"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); { ClassInteractingWithEBus listenerClassInstance; // setup a listener, otherwise property value can't be stored listenerClassInstance.m_data = 1010; // get the value of the ebus property. In practice those request buses use ID to identify a single handler, then we just use m_event instead of m_broadcast int data = 0; virtualEBusProperty.m_getter->m_broadcast->InvokeResult(data); AZ_TEST_ASSERT(data == 1010); // set the value of the ebus property. virtualEBusProperty.m_setter->m_broadcast->Invoke(3030); AZ_TEST_ASSERT(listenerClassInstance.m_data == 3030); } ////////////////////////////////////////////////////////////////////////// } } }; TEST_F(BehaviorContextTest, Test) { run(); } TEST_F(BehaviorContextTest, BehaviorEBusHandlerWithDocMacroCompilesSuccessfully) { AZ::BehaviorContext behaviorContext; behaviorContext.EBus("TestBusWithEbusHandlerThatSupportsNameAndTooltip") ->Handler() ; auto BehaviorEBusFoundIt = behaviorContext.m_ebuses.find("TestBusWithEbusHandlerThatSupportsNameAndTooltip"); ASSERT_NE(behaviorContext.m_ebuses.end(), BehaviorEBusFoundIt); AZ::BehaviorEBus* behaviorEBus = BehaviorEBusFoundIt->second; ASSERT_NE(nullptr, behaviorEBus->m_createHandler); AZ::BehaviorEBusHandler* handlerResult{}; EXPECT_TRUE(behaviorEBus->m_createHandler->InvokeResult(handlerResult)); ASSERT_NE(nullptr, handlerResult); auto handlerDeleter = [behaviorEBus](AZ::BehaviorEBusHandler* handler) { behaviorEBus->m_destroyHandler->Invoke(handler); }; AZStd::unique_ptr ebusHandler(handlerResult, AZStd::move(handlerDeleter)); const AZ::BehaviorEBusHandler::EventArray handlerEvents = ebusHandler->GetEvents(); auto stringViewEventIt = AZStd::find_if(handlerEvents.begin(), handlerEvents.end(), [](const AZ::BehaviorEBusHandler::BusForwarderEvent& handlerEvent) { return strcmp(handlerEvent.m_name, "OnEventWithDefaultValueAndStringResult") == 0; }); ASSERT_NE(handlerEvents.end(), stringViewEventIt); ASSERT_EQ(AZ::eBehaviorBusForwarderEventIndices::ParameterFirst + 2, stringViewEventIt->m_metadataParameters.size()); EXPECT_EQ("defaultView", stringViewEventIt->m_metadataParameters[AZ::eBehaviorBusForwarderEventIndices::ParameterFirst].m_name); EXPECT_EQ("string_view which contains literal to print by default", stringViewEventIt->m_metadataParameters[AZ::eBehaviorBusForwarderEventIndices::ParameterFirst].m_toolTip); EXPECT_EQ("unusedView", stringViewEventIt->m_metadataParameters[AZ::eBehaviorBusForwarderEventIndices::ParameterFirst + 1].m_name); EXPECT_EQ("Unused test parameter", stringViewEventIt->m_metadataParameters[AZ::eBehaviorBusForwarderEventIndices::ParameterFirst + 1].m_toolTip); } } // namespace Unittest #if !defined(AZCORE_EXCLUDE_LUA) namespace UnitTest { class IncompleteType; } namespace AZ { AZ_TYPE_INFO_SPECIALIZE(UnitTest::IncompleteType, "{53CC592A-E4B8-4F89-A293-87C7838A6108}"); AZ_TYPE_INFO_SPECIALIZE(UnitTest::GlobalEnum, "{8A34A34C-B547-4724-8D3B-E12B8774E338}"); } using namespace AZ; namespace UnitTest { int s_globalVar = 0; bool s_globalVarBool = false; float s_globalVar1 = 0.0f; float s_globalVar2ReadOnly = 10.0f; float s_globalVar3WriteOnly = 0.0f; AZStd::string s_globalVarString; int s_globalField = 0; int s_globalField1 = 12; int s_globalField2 = 0; static int s_errorCount = 0; IncompleteType* s_globalIncompletePtr = static_cast(AZ_INVALID_POINTER); IncompleteType* s_globalIncompletePtr1 = 0; void GlobalVarSet(int v) { s_globalVar = v; } int GlobalVarGet() { return s_globalVar; } void GlobalVarStringSet(const char* v) { s_globalVarString = v; } const char* GlobalVarStringGet() { return s_globalVarString.c_str(); } void GlobalCheckString(AZStd::string globalVarString) { AZ_TEST_ASSERT(s_globalVarString == globalVarString); } void GlobalCheckStringRef(const AZStd::string& globalVarString) { AZ_TEST_ASSERT(s_globalVarString == globalVarString); } int GlobalFunc0() { return 0; } int GlobalFunc1(int) { return 1; } int GlobalFunc2(int, float) { return 2; } int GlobalFunc3(int, float, bool) { return 3; } int GlobalFunc4(int, float, bool, int) { return 4; } int GlobalFunc5(int, float, bool, int, float) { return 5; } int GlobalFunc0Override() { return 10; } void GlobalVoidFunc0() { s_globalVar = 0; } void GlobalVoidFunc1(int v) { s_globalVar = v; } void GlobalVoidFunc2(int v, float) { s_globalVar = v; } void GlobalVoidFunc3(int v, float, bool) { s_globalVar = v; } void GlobalVoidFunc4(int v, float, bool, int) { s_globalVar = v; } void GlobalVoidFunc5(int v, float, bool, int, float) { s_globalVar = v; } void GlobalEnumFunc(GlobalEnum value) { s_globalVar = static_cast(value); } void VariadicFunc(ScriptDataContext& context) { s_globalVar = context.GetNumArguments(); context.PushResult(s_globalVar * 2); } class ScriptClass2; class ScriptClass4; class ScriptClass5; class ScriptBindTest : public BehaviorContextFixture { static ScriptBindTest* s_scriptBindInstance; public: AZStd::fixed_vector scriptClass2Instances; AZStd::fixed_vector< AZStd::shared_ptr, 10 > scriptClass4lockedInstances; int scriptClass4numInstances = 0; AZStd::fixed_vector< AZStd::intrusive_ptr, 10 > scriptClass5lockedInstances; int scriptClass5numInstances = 0; void SetUp() override; void TearDown() override; void ResetGlobalVars(); static ScriptBindTest* GetScriptBindInstance() { return s_scriptBindInstance; } static bool EnumClass(const char* name, const AZ::Uuid& /*typeid*/, void* /*userData*/); static bool EnumMethod(const AZ::Uuid* /*type id*/, const char* name, const char* dbgParamInfo, void* /*userData*/); static bool EnumProperty(const AZ::Uuid* /*type id*/, const char* name, bool isRead, bool isWrite, void* /*userData*/); static void ScriptErrorCB(AZ::ScriptContext*, AZ::ScriptContext::ErrorType, const char* details); void run(); }; ScriptBindTest* ScriptBindTest::s_scriptBindInstance = nullptr; class ScriptClass* s_scriptClassInstance = nullptr; class ScriptClass { public: AZ_TYPE_INFO(ScriptClass, "{7b91cb47-a271-4031-8114-56d38efabc4f}"); AZ_CLASS_ALLOCATOR(ScriptClass, SystemAllocator, 0); enum LocalEnum { SC_ET_VALUE2 = 2, SC_ET_VALUE3, }; ScriptClass() : m_data(0) , m_data1(0.0f) , m_data2ReadOnly(11.0f) , m_data3WriteOnly(0.0f) { if (s_scriptClassInstance == nullptr) { s_scriptClassInstance = this; } } ScriptClass(float dataReadOnly) : m_data(0) , m_data1(0.0f) , m_data2ReadOnly(dataReadOnly) , m_data3WriteOnly(0.0f) { if (s_scriptClassInstance == nullptr) { s_scriptClassInstance = this; } } ~ScriptClass() { if (s_scriptClassInstance == this) { s_scriptClassInstance = nullptr; } } int MemberFunc0() { return 0; } int MemberFunc1(int) { return 1; } int MemberFunc2(int, float) { return 2; } int MemberFunc3(int, float, bool) { return 3; } int MemberFunc4(int, float, bool, int) { return 4; } int MemberFunc5(int, float, bool, int, float) { return 5; } void MemberVoidFunc0() { m_data = 0; } void MemberVoidFunc1(int v) { m_data = v; } void MemberVoidFunc2(int v, float) { m_data = v; } void MemberVoidFunc3(int v, float, bool) { m_data = v; } void MemberVoidFunc4(int v, float, bool, int) { m_data = v; } void MemberVoidFunc5(int v, float, bool, int, float) { m_data = v; } int GetData() const { return m_data; } void SetData(int data) { m_data = data; } void VariadicFunc(ScriptDataContext& context) { s_globalVar = context.GetNumArguments(); } int m_data; float m_data1; float m_data2ReadOnly; float m_data3WriteOnly; struct ScriptClassNested { ScriptClassNested() : m_data(100) {} int m_data; }; }; void* ScriptClassAllocate(void* userData) { (void)userData; return azmalloc(sizeof(ScriptClass),AZStd::alignment_of::value,AZ::SystemAllocator,"ScriptClass"); } void ScriptClassFree(void* obj, void* userData) { (void)userData; azfree(obj, AZ::SystemAllocator, sizeof(ScriptClass), AZStd::alignment_of::value); } class ScriptClass1 { public: AZ_CLASS_ALLOCATOR(ScriptClass1, SystemAllocator, 0); ScriptClass1() : m_data(0.0f) {} float m_data; }; class ScriptClass2 { public: AZ_TYPE_INFO(ScriptClass2, "{b4482151-b246-4c95-9ab3-db4589b3ffa0}"); AZ_CLASS_ALLOCATOR(ScriptClass2, SystemAllocator, 0); ScriptClass2(int data) : m_data(data) { ScriptBindTest::GetScriptBindInstance()->scriptClass2Instances.push_back(this); } ScriptClass2(const ScriptClass2& rhs) { m_data = rhs.m_data; ScriptBindTest::GetScriptBindInstance()->scriptClass2Instances.push_back(this); } ~ScriptClass2() { auto& scriptClass2Instances = ScriptBindTest::GetScriptBindInstance()->scriptClass2Instances; auto instanceIt = AZStd::find(scriptClass2Instances.begin(), scriptClass2Instances.end(), this); if (instanceIt != scriptClass2Instances.end()) { scriptClass2Instances.erase(instanceIt); } } void VariadicFunc(ScriptDataContext& context) { (void)context; } bool operator<(const ScriptClass2& rhs) const { return m_data < rhs.m_data; } bool operator<=(const ScriptClass2& rhs) const { return m_data < rhs.m_data; } bool operator==(const ScriptClass2& rhs) const { return m_data == rhs.m_data; } ScriptClass2 ScriptAdd(const ScriptClass2& rhs) const { return ScriptClass2(m_data + rhs.m_data); } ScriptClass2 ScriptSub(const ScriptClass2& rhs) const { return ScriptClass2(m_data - rhs.m_data); } ScriptClass2 ScriptMul(const ScriptClass2& rhs) const { return ScriptClass2(m_data * rhs.m_data); } ScriptClass2 ScriptDiv(const ScriptClass2& rhs) const { return ScriptClass2(m_data / rhs.m_data); } ScriptClass2 ScriptMod(const ScriptClass2& rhs) const { return ScriptClass2(m_data % rhs.m_data); } ScriptClass2 ScriptPow(const ScriptClass2& rhs) const { return ScriptClass2((int)pow((float)m_data, rhs.m_data)); } ScriptClass2 ScriptUnary() const { return ScriptClass2(-m_data); } ScriptClass2 ScriptConcat(const ScriptClass2& rhs) const { return ScriptClass2(m_data + rhs.m_data); } int ScriptLength() const { return m_data; } const char* ToString() const { return "This is ScriptClass2"; } ScriptClass2* Forward() { return this; } int m_data; }; class ScriptClass3* s_scriptClass3Instance = nullptr; class ScriptClass3 { public: AZ_TYPE_INFO(ScriptClass3, "{058b255b-1abf-4831-9ea9-8b8cb6a7a634}"); AZ_CLASS_ALLOCATOR(ScriptClass3, SystemAllocator, 0); ScriptClass3() { m_bool = false; m_intData = 0; m_floatData = 0.0f; m_incompletePtr = nullptr; } ScriptClass3(ScriptDataContext& ctx) { m_bool = false; m_intData = 0; m_floatData = 0.0f; m_incompletePtr = nullptr; // unpack variadic parameters AZ_TEST_ASSERT(ctx.GetNumArguments() == 4); AZ_TEST_ASSERT(ctx.IsBoolean(0)); AZ_TEST_ASSERT(ctx.IsNumber(1)); AZ_TEST_ASSERT(ctx.IsNumber(2)); AZ_TEST_ASSERT(ctx.IsRegisteredClass(3) == false); ctx.ReadArg(0, m_bool); ctx.ReadArg(1, m_intData); ctx.ReadArg(2, m_floatData); ctx.ReadArg(3, m_incompletePtr); m_scriptClass2 = nullptr; if (s_scriptClass3Instance == nullptr) { s_scriptClass3Instance = this; } } ~ScriptClass3() { if (s_scriptClass3Instance == this) { s_scriptClass3Instance = nullptr; } } const char* ToString() const { return "This is ScriptClass3"; } bool m_bool; int m_intData; float m_floatData; IncompleteType* m_incompletePtr; ScriptClass2* m_scriptClass2; private: // TODO: Simulate private copy constructor when AZStd::is_copy_constructible works //ScriptClass3(const ScriptClass3&) = delete; }; class ScriptClass4 { public: AZ_TYPE_INFO(ScriptClass4, "{4ecb714f-c73d-4a80-84d4-b55b145c3033}"); AZ_CLASS_ALLOCATOR(ScriptClass4, SystemAllocator, 0); ScriptClass4(int data) : m_data(data) { int& numClass4Instances = ScriptBindTest::GetScriptBindInstance()->scriptClass4numInstances; numClass4Instances++; } ~ScriptClass4() { int& numClass4Instances = ScriptBindTest::GetScriptBindInstance()->scriptClass4numInstances; numClass4Instances--; } static void HoldInstance(const AZStd::shared_ptr& ptr) { auto& class4lockedInstances = ScriptBindTest::GetScriptBindInstance()->scriptClass4lockedInstances; class4lockedInstances.push_back(ptr); } void CopyData(AZStd::shared_ptr rhs) { m_data = rhs->m_data; } int m_data; }; class ScriptClass5 { public: AZ_TYPE_INFO(ScriptClass5, "{99a1e378-d457-45d0-bd49-72b21d3368c5}"); AZ_CLASS_ALLOCATOR(ScriptClass5, SystemAllocator, 0); ScriptClass5(int data) : m_data(data) , m_refCount(0) { int& numClass5Instances = ScriptBindTest::GetScriptBindInstance()->scriptClass5numInstances; numClass5Instances++; } ~ScriptClass5() { int& numClass5Instances = ScriptBindTest::GetScriptBindInstance()->scriptClass5numInstances; numClass5Instances--; } void add_ref() { m_refCount++; } void release() { m_refCount--; if (m_refCount == 0) { delete this; } } static void HoldInstance(const AZStd::intrusive_ptr& ptr) { auto& class5lockedInstances = ScriptBindTest::GetScriptBindInstance()->scriptClass5lockedInstances; class5lockedInstances.push_back(ptr); } void CopyData(const AZStd::intrusive_ptr& rhs) { m_data = rhs->m_data; } int m_data; int m_refCount; }; struct ScriptValueClass { AZ_TYPE_INFO(ScriptValueClass, "{78b1baa9-483b-42c7-8c1e-4eba903bd529}"); AZ_CLASS_ALLOCATOR(ScriptValueClass, SystemAllocator, 0); ScriptValueClass() : m_data(10) { } ScriptValueClass(const ScriptValueClass& rhs) { (void)rhs; AZ_TEST_ASSERT(false); // we should not make copies } int m_data; }; struct ScriptValueHolder { AZ_TYPE_INFO(ScriptValueHolder, "{86887e28-dfc6-4619-87d8-3658da0996ec}"); AZ_CLASS_ALLOCATOR(ScriptValueHolder, SystemAllocator, 0); ScriptValueHolder() { } ScriptValueHolder(const ScriptValueHolder& rhs) { (void)rhs; AZ_TEST_ASSERT(false); // we should not make copies } ScriptValueClass m_value; }; class ScriptUnregisteredBaseClass { public: AZ_RTTI(ScriptUnregisteredBaseClass, "{0ebe10b0-617b-4086-bff1-d51523db8cd6}"); ScriptUnregisteredBaseClass() : m_baseData(1) {} virtual ~ScriptUnregisteredBaseClass() {} virtual int GetData() const { return m_baseData; } int m_baseData; }; class ScriptUnregisteredDerivedClass : public ScriptUnregisteredBaseClass { public: AZ_RTTI(ScriptUnregisteredDerivedClass, "{5aba38c7-ae10-46d1-bc8e-3a0a00a3a97c}", ScriptUnregisteredBaseClass); ScriptUnregisteredDerivedClass() : m_derivedData(10) {} int GetData() const override { return m_derivedData; } int m_derivedData; }; int GetUnregisteredScriptBaseData(ScriptUnregisteredBaseClass* base) { return base->GetData(); } int GetUnregisteredScriptDerivedData(ScriptUnregisteredDerivedClass* derived) { return derived->m_derivedData; } ScriptUnregisteredBaseClass* s_globalUnregBaseClass = nullptr; ScriptUnregisteredDerivedClass* s_globalUnregDerivedClass = nullptr; class ScriptRegisteredBaseClass { public: AZ_RTTI(ScriptRegisteredBaseClass, "{73c2f822-d1ef-4abc-accb-cb36599f6b66}"); AZ_CLASS_ALLOCATOR(ScriptRegisteredBaseClass, SystemAllocator, 0) ScriptRegisteredBaseClass() : m_baseData(2) {} virtual ~ScriptRegisteredBaseClass() {} virtual int VirtualFunction() { return 0; } int m_baseData; }; ScriptRegisteredBaseClass* s_globalRegisteredBaseClass = nullptr; class ScriptRegisteredDerivedClass : public ScriptRegisteredBaseClass { public: AZ_RTTI(ScriptRegisteredDerivedClass, "{9f42df41-fdcc-44aa-909f-1f675804653c}", ScriptRegisteredBaseClass); AZ_CLASS_ALLOCATOR(ScriptRegisteredDerivedClass, SystemAllocator, 0) ScriptRegisteredDerivedClass() : m_derivedData(11) {} ~ScriptRegisteredDerivedClass() override {} int VirtualFunction() override { return 1; } int m_derivedData; }; class AbstractClass { public: AZ_RTTI(AbstractClass, "{d7082d66-108f-4177-8fb3-5cf25b510aa7}"); virtual ~AbstractClass() {} virtual int PureCall() = 0; }; class AbstractImplementation : public AbstractClass { public: AZ_RTTI(AbstractImplementation, "{62e1514d-57be-49c5-b829-d937f07276cd}", AbstractClass); AZ_CLASS_ALLOCATOR(AbstractImplementation, SystemAllocator, 0) int PureCall() override { return 5; } }; AZStd::shared_ptr s_scriptMemberShared; AZStd::intrusive_ptr s_scriptMemberIntrusive; void ScriptBindTest::SetUp() { BehaviorContextFixture::SetUp(); ResetGlobalVars(); s_scriptBindInstance = this; s_scriptMemberShared = AZStd::shared_ptr(aznew ScriptClass4(10)); s_scriptMemberIntrusive = AZStd::intrusive_ptr(aznew ScriptClass5(10)); } void ScriptBindTest::TearDown() { s_scriptMemberShared.reset(); s_scriptMemberIntrusive.reset(); s_scriptBindInstance = nullptr; BehaviorContextFixture::TearDown(); } void ScriptBindTest::ResetGlobalVars() { s_globalVar = 0; s_globalVarBool = false; s_globalVar1 = 0.0f; s_globalVar2ReadOnly = 10.0f; s_globalVar3WriteOnly = 0.0f; s_globalVarString = AZStd::string(); s_globalField = 0; s_globalField1 = 12; s_globalField2 = 0; s_errorCount = 0; s_globalIncompletePtr = static_cast(AZ_INVALID_POINTER); s_globalIncompletePtr1 = nullptr; } bool ScriptBindTest::EnumClass(const char* name, const AZ::Uuid& /*typeid*/, void* /*userData*/) { AZ_TEST_ASSERT(name != nullptr); return true; } bool ScriptBindTest::EnumMethod(const AZ::Uuid* /*type id*/, const char* name, const char* dbgParamInfo, void* /*userData*/) { // make sure the debug info works if (strcmp(name, "GlobalFunc5") == 0) { AZ_TEST_ASSERT(strcmp(dbgParamInfo, "int NumberArguments (int intValue,float floatValue,bool Value,int intValue2,float floatValue2") == 0); } return true; } bool ScriptBindTest::EnumProperty(const AZ::Uuid* /*type id*/, const char* name, bool isRead, bool isWrite, void* /*userData*/) { if (strcmp(name, "globalVar") == 0) { AZ_TEST_ASSERT(isWrite && isRead); } if (strcmp(name, "globalVar2") == 0) { AZ_TEST_ASSERT(!isWrite && isRead); } if (strcmp(name, "globalVar3") == 0) { AZ_TEST_ASSERT(isWrite && !isRead); } return true; } void ScriptBindTest::ScriptErrorCB(AZ::ScriptContext*, AZ::ScriptContext::ErrorType, const char* details) { (void)details; s_errorCount++; AZ_Warning("Script", false, "%s", details); } void ScriptBindTest::run() { AZ::Uuid ch = AzTypeInfo::Uuid(); AZ::Uuid ch1 = AzTypeInfo::Uuid(); (void)ch; (void)ch1; // enum m_behaviorContext->Enum("GE_VALUE1")-> Enum("GE_VALUE2"); // constant m_behaviorContext->Constant("PI", BehaviorConstant(3.14f)); // property m_behaviorContext->Property("globalVar", &GlobalVarGet, &GlobalVarSet); m_behaviorContext->Property("globalVar1", BehaviorValueProperty(&s_globalVar1)); m_behaviorContext->Property("globalVarBool", BehaviorValueProperty(&s_globalVarBool)); m_behaviorContext->Property("globalVar2", BehaviorValueGetter(&s_globalVar2ReadOnly), nullptr); m_behaviorContext->Property("globalVar3", nullptr, BehaviorValueSetter(&s_globalVar3WriteOnly)); m_behaviorContext->Property("globalVarString", &GlobalVarStringGet, &GlobalVarStringSet); // field m_behaviorContext->Property("globalField", BehaviorValueProperty(&s_globalField)); m_behaviorContext->Property("globalField1", BehaviorValueGetter(&s_globalField1), nullptr); m_behaviorContext->Property("globalField2", nullptr, BehaviorValueSetter(&s_globalField2)); m_behaviorContext->Property("globalIncomplete", BehaviorValueProperty(&s_globalIncompletePtr)); // unregistered type (passed as light userdata) m_behaviorContext->Property("globalIncomplete1", BehaviorValueProperty(&s_globalIncompletePtr1)); // unregistered type (passed as light userdata) // method m_behaviorContext->Method("GlobalFunc0", &GlobalFunc0); m_behaviorContext->Method("GlobalFunc1", &GlobalFunc1); m_behaviorContext->Method("GlobalFunc2", &GlobalFunc2); m_behaviorContext->Method("GlobalFunc3", &GlobalFunc3); m_behaviorContext->Method("GlobalFunc4", &GlobalFunc4); m_behaviorContext->Method("GlobalFunc5", &GlobalFunc5, nullptr, "int NumberArguments (int intValue,float floatValue,bool Value,int intValue2,float floatValue2"); m_behaviorContext->Method("GlobalVoidFunc0", &GlobalVoidFunc0); m_behaviorContext->Method("GlobalVoidFunc1", &GlobalVoidFunc1); m_behaviorContext->Method("GlobalVoidFunc2", &GlobalVoidFunc2); m_behaviorContext->Method("GlobalVoidFunc3", &GlobalVoidFunc3); m_behaviorContext->Method("GlobalVoidFunc4", &GlobalVoidFunc4); m_behaviorContext->Method("GlobalVoidFunc5", &GlobalVoidFunc5); m_behaviorContext->Method("GlobalEnumFunc", &GlobalEnumFunc); m_behaviorContext->Method("GlobalCheckString", &GlobalCheckString); m_behaviorContext->Method("GlobalCheckStringRef", &GlobalCheckStringRef); // clases m_behaviorContext->Class()-> Allocator(&ScriptClassAllocate, &ScriptClassFree)-> Enum("SC_ET_VALUE2")-> Enum("SC_ET_VALUE3")-> Constant("EPSILON", BehaviorConstant(0.001f))-> Method("MemberFunc0", &ScriptClass::MemberFunc0)-> Method("MemberFunc1", &ScriptClass::MemberFunc1)-> Method("MemberFunc2", &ScriptClass::MemberFunc2)-> Method("MemberFunc3", &ScriptClass::MemberFunc3)-> Method("MemberFunc4", &ScriptClass::MemberFunc4)-> Method("MemberFunc5", &ScriptClass::MemberFunc5)-> Method("MemberVoidFunc0", &ScriptClass::MemberVoidFunc0)-> Method("MemberVoidFunc1", &ScriptClass::MemberVoidFunc1)-> Method("MemberVoidFunc2", &ScriptClass::MemberVoidFunc2)-> Method("MemberVoidFunc3", &ScriptClass::MemberVoidFunc3)-> Method("MemberVoidFunc4", &ScriptClass::MemberVoidFunc4)-> Method("MemberVoidFunc5", &ScriptClass::MemberVoidFunc5)-> Method("VariadicFunc", &ScriptClass::VariadicFunc)-> Property("data", &ScriptClass::GetData, &ScriptClass::SetData)-> Property("data1", BehaviorValueProperty(&ScriptClass::m_data1))-> Property("data2", BehaviorValueGetter(&ScriptClass::m_data2ReadOnly), nullptr)-> Property("data3", nullptr, BehaviorValueSetter(&ScriptClass::m_data3WriteOnly));//-> //Class()-> // Field("data",&ScriptClass::ScriptClassNested::m_data)-> //ClassEnd(); m_behaviorContext->Class()-> Constructor()-> Method("Add", &ScriptClass2::ScriptAdd)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Add)-> Method("Sub", &ScriptClass2::ScriptSub)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Sub)-> Method("Mul", &ScriptClass2::ScriptMul)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Mul)-> Method("Div", &ScriptClass2::ScriptDiv)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Div)-> Method("Mod", &ScriptClass2::ScriptMod)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Mod)-> Method("Pow", &ScriptClass2::ScriptPow)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Pow)-> Method("Unary", &ScriptClass2::ScriptUnary)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Unary)-> Method("Concat", &ScriptClass2::ScriptConcat)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Concat)-> Method("Length", &ScriptClass2::ScriptLength)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Length)-> Method("Equal", &ScriptClass2::operator==)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)-> Method("LessEqual", &ScriptClass2::operator<=)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::LessEqualThan)-> Method("LessThan", &ScriptClass2::operator<)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::LessThan)-> Method("ToString", &ScriptClass2::ToString)-> Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)-> Method("Forward", &ScriptClass2::Forward)-> Property("data", BehaviorValueProperty(&ScriptClass2::m_data)); m_behaviorContext->Class()-> Constructor()-> Property("boolData", BehaviorValueProperty(&ScriptClass3::m_bool))-> Property("intData", BehaviorValueProperty(&ScriptClass3::m_intData))-> Property("floatData", BehaviorValueProperty(&ScriptClass3::m_floatData))-> Property("scriptClass2", BehaviorValueProperty(&ScriptClass3::m_scriptClass2)); m_behaviorContext->Class()-> Constructor()-> Method("CopyData", &ScriptClass4::CopyData)-> Property("data", BehaviorValueProperty(&ScriptClass4::m_data)); m_behaviorContext->Property("scriptClass4Member", BehaviorValueProperty(&s_scriptMemberShared)); m_behaviorContext->Method("HoldScriptClass4", &ScriptClass4::HoldInstance); m_behaviorContext->Class()-> Constructor()-> Method("CopyData", &ScriptClass5::CopyData)-> Property("data", BehaviorValueProperty(&ScriptClass5::m_data)); m_behaviorContext->Property("scriptClass5Member", BehaviorValueProperty(&s_scriptMemberIntrusive)); m_behaviorContext->Method("HoldScriptClass5", &ScriptClass5::HoldInstance); // Variadic method m_behaviorContext->Method("VariadicFunc", &VariadicFunc); // Check nested classes m_behaviorContext->Class()-> Property("data", BehaviorValueProperty(&ScriptValueClass::m_data)); m_behaviorContext->Class()-> Property("value", BehaviorValueProperty(&ScriptValueHolder::m_value)); // test class pointer casting for unregistered classes m_behaviorContext->Property("globalUnregBaseClass", BehaviorValueProperty(&s_globalUnregBaseClass)); m_behaviorContext->Property("globalUnregDerivedClass", BehaviorValueProperty(&s_globalUnregDerivedClass)); m_behaviorContext->Method("GetUnregisteredScriptBaseData", &GetUnregisteredScriptBaseData); m_behaviorContext->Method("GetUnregisteredScriptDerivedData", &GetUnregisteredScriptDerivedData); // register a base and derived class test m_behaviorContext->Class()-> Property("baseData", BehaviorValueProperty(&ScriptRegisteredBaseClass::m_baseData))-> Method("VirtualFunction", &ScriptRegisteredBaseClass::VirtualFunction); // when using AZ_RTTI the bind will discover the 'ScriptRegisteredBaseClass' and import it's bindings // into 'ScriptRegisteredDerivedClass' class so we access it (like in C++) m_behaviorContext->Class()-> Property("derivedData", BehaviorValueProperty(&ScriptRegisteredDerivedClass::m_derivedData)); // add a function that makes a derived class (can change at runtime) and return pointer to a base class. m_behaviorContext->Property("globalRegisteredBaseClass", BehaviorValueProperty(&s_globalRegisteredBaseClass)); // abstract classes m_behaviorContext->Class()-> Method("PureCall", &AbstractClass::PureCall); m_behaviorContext->Class(); // create the lua script context and Bind it the behavior ScriptContext script; script.BindTo(m_behaviorContext); // test globalVar with functions get and set AZ_TEST_ASSERT(s_globalVar == 0); script.Execute("globalVar = 5"); AZ_TEST_ASSERT(s_globalVar == 5); script.Execute("globalVar = globalVar + 1"); AZ_TEST_ASSERT(s_globalVar == 6); // test globalVar1 with variable address only AZ_TEST_ASSERT(s_globalVar1 == 0.0f); script.Execute("globalVar1 = 5"); AZ_TEST_ASSERT(s_globalVar1 == 5.0f); script.Execute("globalVar1 = globalVar1 + 1.0"); AZ_TEST_ASSERT(s_globalVar1 == 6.0f); // test globalVar2ReadOnly s_globalVar1 = 0.0f; AZ_TEST_ASSERT(s_globalVar2ReadOnly == 10.0f); script.Execute("globalVar1 = globalVar2"); AZ_TEST_ASSERT(s_globalVar1 == 10.0f); //script.Execute("globalVar2 = 5.0"); //- this will cause script error (we can improve the message by providing a fake set function); // test globalVar2WriteOnly AZ_TEST_ASSERT(s_globalVar3WriteOnly == 0.0f); script.Execute("globalVar3 = 100"); AZ_TEST_ASSERT(s_globalVar3WriteOnly == 100.0f); //script.Execute("print(\"globalVar3 is \",globalVar3)"); //- this will cause script error (we can improve the message by providing a fake set function); // globalField with functions get and set AZ_TEST_ASSERT(s_globalField == 0); script.Execute("globalField = 5"); AZ_TEST_ASSERT(s_globalField == 5); script.Execute("globalField = globalField +1"); AZ_TEST_ASSERT(s_globalField == 6); // globalField1 read only s_globalVar1 = 0.0f; AZ_TEST_ASSERT(s_globalField1 == 12.0f); script.Execute("globalVar1 = globalField1"); AZ_TEST_ASSERT(s_globalVar1 == 12.0f); //script.Execute("globalField1 = 10.0"); //- this will cause script error (we can improve the message by providing a fake set function); // globalField2 write only AZ_TEST_ASSERT(s_globalField2 == 0.0f); script.Execute("globalField2 = 100"); AZ_TEST_ASSERT(s_globalField2 == 100.0f); //script.Execute("print(\"globalField2 is \",globalField2)"); //- this will cause script error (we can improve the message by providing a fake set function); // s_globalVarString test AZ_TEST_ASSERT(s_globalVarString.empty()); script.Execute("globalVarString = \"Hello\""); AZ_TEST_ASSERT(s_globalVarString.compare("Hello") == 0); // incomplete types passed by a light-user data (pointer reference) AZ_TEST_ASSERT(s_globalIncompletePtr == reinterpret_cast(AZ_INVALID_POINTER)); AZ_TEST_ASSERT(s_globalIncompletePtr1 == 0); script.Execute("globalIncomplete1 = globalIncomplete"); AZ_TEST_ASSERT(s_globalIncompletePtr1 == s_globalIncompletePtr); // enum s_globalVar = 100; script.Execute("globalVar = 0"); AZ_TEST_ASSERT(s_globalVar == 0); s_globalVar = 100; script.Execute("globalVar = GE_VALUE1"); AZ_TEST_ASSERT(s_globalVar == GE_VALUE1); s_globalVar = 100; script.Execute("globalVar = GE_VALUE2"); AZ_TEST_ASSERT(s_globalVar == GE_VALUE2); s_globalVar = 100; script.Execute("GlobalEnumFunc(GE_VALUE2)"); AZ_TEST_ASSERT(s_globalVar == GE_VALUE2); // constant s_globalVar1 = 0.0f; script.Execute("globalVar1 = PI"); AZ_TEST_ASSERT_FLOAT_CLOSE(s_globalVar1, 3.14f); // methods s_globalVar = 5; script.Execute("globalVar = GlobalFunc0()"); AZ_TEST_ASSERT(s_globalVar == 0); s_globalVar = 0; script.Execute("globalVar = GlobalFunc1(2)"); AZ_TEST_ASSERT(s_globalVar == 1); s_globalVar = 0; script.Execute("globalVar = GlobalFunc2(2,3)"); AZ_TEST_ASSERT(s_globalVar == 2); s_globalVar = 0; script.Execute("globalVar = GlobalFunc3(2,3,true)"); AZ_TEST_ASSERT(s_globalVar == 3); s_globalVar = 0; script.Execute("globalVar = GlobalFunc4(2,3,false,4)"); AZ_TEST_ASSERT(s_globalVar == 4); s_globalVar = 0; script.Execute("globalVar = GlobalFunc5(2,3,true,5,2)"); AZ_TEST_ASSERT(s_globalVar == 5); s_globalVar = 5; script.Execute("GlobalVoidFunc0()"); AZ_TEST_ASSERT(s_globalVar == 0); s_globalVar = 0; script.Execute("GlobalVoidFunc1(2)"); AZ_TEST_ASSERT(s_globalVar == 2); s_globalVar = 0; script.Execute("GlobalVoidFunc2(10,1)"); AZ_TEST_ASSERT(s_globalVar == 10); s_globalVar = 0; script.Execute("GlobalVoidFunc3(15,3,true)"); AZ_TEST_ASSERT(s_globalVar == 15); s_globalVar = 0; script.Execute("GlobalVoidFunc4(21,4,false,2)"); AZ_TEST_ASSERT(s_globalVar == 21); s_globalVar = 0; script.Execute("GlobalVoidFunc5(101,102,true,2,1)"); AZ_TEST_ASSERT(s_globalVar == 101); script.Execute("GlobalCheckString(globalVarString)"); script.Execute("GlobalCheckStringRef(globalVarString)"); // test a variadic function s_globalVar = 5; script.Execute("globalVar1 = VariadicFunc()"); AZ_TEST_ASSERT(s_globalVar == 0); AZ_TEST_ASSERT_FLOAT_CLOSE(s_globalVar1, 0.0f); s_globalVar = 0; script.Execute("globalVar1 = VariadicFunc(101,102,true,2,1)"); AZ_TEST_ASSERT(s_globalVar == 5); AZ_TEST_ASSERT_FLOAT_CLOSE(s_globalVar1, 10.0f); s_globalVar = 0; script.Execute("globalVar1 = VariadicFunc(101,102)"); AZ_TEST_ASSERT(s_globalVar == 2); AZ_TEST_ASSERT_FLOAT_CLOSE(s_globalVar1, 4.0f); ////////////////////////////////////////////////////////////////////////// // Classes s_globalVar = 0; script.Execute("globalVar = scriptClass4Member.data"); AZ_TEST_ASSERT(s_globalVar == 10); script.Execute("scriptClass4Member = nil"); AZ_TEST_ASSERT(s_scriptMemberShared == nullptr); script.Execute("scriptClass4Member = shared_ptr_ScriptClass4(ScriptClass4(12))"); AZ_TEST_ASSERT(s_scriptMemberShared->m_data == 12); // test invalid assignment { s_errorCount = 0; auto originalErrorCB = script.GetErrorHook(); script.SetErrorHook(&ScriptErrorCB); script.Execute("scriptClass4Member = ScriptClass5(12)"); script.SetErrorHook(originalErrorCB); AZ_TEST_ASSERT(s_errorCount == 1); } script.Execute("scriptClass4Member = nil"); script.GarbageCollect(); AZ_TEST_ASSERT(scriptClass4numInstances == 0) // test assignments of nil to intrusive pointer s_globalVar = 0; script.Execute("globalVar = scriptClass5Member.data"); AZ_TEST_ASSERT(s_globalVar == 10); script.Execute("scriptClass5Member = nil"); AZ_TEST_ASSERT(s_scriptMemberShared == nullptr); script.Execute("scriptClass5Member = intrusive_ptr_ScriptClass5(ScriptClass5(12))"); AZ_TEST_ASSERT(s_scriptMemberIntrusive->m_data == 12); // test invalid assignment { s_errorCount = 0; auto originalErrorCB = script.GetErrorHook(); script.SetErrorHook(&ScriptErrorCB); script.Execute("scriptClass5Member = ScriptClass4(12)"); script.SetErrorHook(originalErrorCB); AZ_TEST_ASSERT(s_errorCount == 1); } AZ_TEST_ASSERT(s_scriptMemberIntrusive->m_data == 12); script.Execute("scriptClass5Member = nil"); script.GarbageCollect(); AZ_TEST_ASSERT(scriptClass5numInstances == 0) // debug print AZ_TEST_ASSERT(script.GetDebugContext() == nullptr); script.EnableDebug(); if (script.GetDebugContext() != nullptr) // debug is NOT supported in release builds { script.GetDebugContext()->EnumRegisteredGlobals(&EnumMethod, &EnumProperty); script.GetDebugContext()->EnumRegisteredClasses(&EnumClass, &EnumMethod, &EnumProperty); script.DisableDebug(); AZ_TEST_ASSERT(script.GetDebugContext() == nullptr); } // create a ScriptClass instance from the script AZ_TEST_ASSERT(s_scriptClassInstance == nullptr); script.Execute("sc = ScriptClass()"); AZ_TEST_ASSERT(s_scriptClassInstance != nullptr); // enum s_globalVar = 0; script.Execute("globalVar = sc.SC_ET_VALUE2"); AZ_TEST_ASSERT(s_globalVar == 2); s_globalVar = 0; script.Execute("globalVar = sc.SC_ET_VALUE3"); AZ_TEST_ASSERT(s_globalVar == 3); // constant s_globalVar1 = 0.0f; script.Execute("globalVar1 = sc.EPSILON"); AZ_TEST_ASSERT_FLOAT_CLOSE(s_globalVar1, 0.001f); // methods s_globalVar = 1; script.Execute("globalVar = sc:MemberFunc0()"); AZ_TEST_ASSERT(s_globalVar == 0); s_globalVar = 0; script.Execute("globalVar = sc:MemberFunc1(1)"); AZ_TEST_ASSERT(s_globalVar == 1); s_globalVar = 0; script.Execute("globalVar = sc:MemberFunc2(2,2.0)"); AZ_TEST_ASSERT(s_globalVar == 2); s_globalVar = 0; script.Execute("globalVar = sc:MemberFunc3(3,3.0,false)"); AZ_TEST_ASSERT(s_globalVar == 3); s_globalVar = 0; script.Execute("globalVar = sc:MemberFunc4(4,4.0,true,40)"); AZ_TEST_ASSERT(s_globalVar == 4); s_globalVar = 0; script.Execute("globalVar = sc:MemberFunc5(5,5.0,false,50,50.0)"); AZ_TEST_ASSERT(s_globalVar == 5); s_scriptClassInstance->m_data = 10; script.Execute("sc:MemberVoidFunc0()"); AZ_TEST_ASSERT(s_scriptClassInstance->m_data == 0); s_scriptClassInstance->m_data = 0; script.Execute("sc:MemberVoidFunc1(11)"); AZ_TEST_ASSERT(s_scriptClassInstance->m_data == 11); s_scriptClassInstance->m_data = 0; script.Execute("sc:MemberVoidFunc2(7,11)"); AZ_TEST_ASSERT(s_scriptClassInstance->m_data == 7); s_scriptClassInstance->m_data = 0; script.Execute("sc:MemberVoidFunc3(13,11,false)"); AZ_TEST_ASSERT(s_scriptClassInstance->m_data == 13); s_scriptClassInstance->m_data = 0; script.Execute("sc:MemberVoidFunc4(21,11,false,40)"); AZ_TEST_ASSERT(s_scriptClassInstance->m_data == 21); s_scriptClassInstance->m_data = 0; script.Execute("sc:MemberVoidFunc5(30,11,false,50,50.0)"); AZ_TEST_ASSERT(s_scriptClassInstance->m_data == 30); // test number of arguments in a variadic function s_globalVar = 0; script.Execute("sc:VariadicFunc(30,11,false)"); AZ_TEST_ASSERT(s_globalVar == 3); s_scriptClassInstance->m_data = 0; s_globalVar = 1; script.Execute("globalVar = sc.data"); AZ_TEST_ASSERT(s_globalVar == 0); script.Execute("sc.data = 10"); script.Execute("globalVar = sc.data"); AZ_TEST_ASSERT(s_globalVar == 10); s_globalVar1 = 3.0f; script.Execute("globalVar1 = sc.data1"); AZ_TEST_ASSERT(s_globalVar1 == 0.0f); script.Execute("sc.data1 = 11 "); script.Execute("globalVar1 = sc.data1"); AZ_TEST_ASSERT(s_globalVar1 == 11.0f); s_globalVar1 = 0.0f; script.Execute("globalVar1 = sc.data2"); AZ_TEST_ASSERT(s_globalVar1 == 11.0f); AZ_TEST_ASSERT(s_scriptClassInstance->m_data3WriteOnly == 0.0f); script.Execute("sc.data3 = 101"); AZ_TEST_ASSERT(s_scriptClassInstance->m_data3WriteOnly == 101.0f); s_globalVarString.clear(); script.Execute("globalVarString = tostring(sc)"); AZ_TEST_ASSERT(s_globalVarString.compare("ScriptClass") == 0); // release test script.Execute("sc = nil"); script.GarbageCollect(); AZ_TEST_ASSERT(s_scriptClassInstance == nullptr); ////////////////////////////////////////////////////////////////////////// AZ_TEST_ASSERT(scriptClass2Instances.size() == 0); script.Execute("sc2 = ScriptClass2(10)"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 1); AZ_TEST_ASSERT(scriptClass2Instances[0]->m_data == 10); script.Execute("sc21 = ScriptClass2(20)"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); AZ_TEST_ASSERT(scriptClass2Instances[1]->m_data == 20); // addition script.Execute("sc22 = sc2 + sc21"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == 30); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // sub script.Execute("sc22 = sc2 - sc21"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == -10); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // mul script.Execute("sc22 = sc2 * sc21"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == 200); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // div script.Execute("sc22 = sc21 / sc2"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == 2); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // mod script.Execute("sc21.data = 3 sc22 = sc2 % sc21 sc21.data = 20"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == 1); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // pow script.Execute("sc21.data = 2 sc22 = sc2 ^ sc21 sc21.data = 20"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == 100); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // unary script.Execute("sc22 = -sc2"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == -10); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // concat script.Execute("sc22 = sc2 .. sc21"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 3); AZ_TEST_ASSERT(scriptClass2Instances[2]->m_data == 30); script.Execute("sc22 = nil collectgarbage(\"collect\")"); AZ_TEST_ASSERT(scriptClass2Instances.size() == 2); // compare s_globalVarBool = true; script.Execute("globalVarBool = (sc2 == sc21)"); AZ_TEST_ASSERT(!s_globalVarBool); script.Execute("sc21.data = 10 globalVarBool = (sc2 == sc21) sc21.data = 20"); AZ_TEST_ASSERT(s_globalVarBool); // compare less than s_globalVarBool = false; script.Execute("globalVarBool = (sc2 < sc21)"); AZ_TEST_ASSERT(s_globalVarBool); script.Execute("globalVarBool = (sc21 < sc2)"); AZ_TEST_ASSERT(!s_globalVarBool); // compare less than s_globalVarBool = false; script.Execute("globalVarBool = (sc2 <= sc21)"); AZ_TEST_ASSERT(s_globalVarBool); script.Execute("globalVarBool = (sc21 <= sc2)"); AZ_TEST_ASSERT(!s_globalVarBool); // len s_globalVar = 0; script.Execute("globalVar = #sc2"); AZ_TEST_ASSERT(s_globalVar == 10); // len s_globalVarString.clear(); script.Execute("globalVarString = tostring(sc2)"); AZ_TEST_ASSERT(s_globalVarString.compare("This is ScriptClass2") == 0); // test pass object by pointer (when it's script owned) back to script // we should make sure that we find the original instance script.Execute("sc2F = sc2:Forward()"); // release test script.Execute("sc2 = nil sc21 = nil"); script.GarbageCollect(); AZ_TEST_ASSERT(scriptClass2Instances.size() == 1); script.Execute("sc2F = nil"); script.GarbageCollect(); AZ_TEST_ASSERT(scriptClass2Instances.size() == 0); // test class with variadic constructor AZ_TEST_ASSERT(s_scriptClass3Instance == nullptr); script.Execute("sc3 = ScriptClass3(true,10,20.0,globalIncomplete)"); AZ_TEST_ASSERT(s_scriptClass3Instance != nullptr); AZ_TEST_ASSERT(s_scriptClass3Instance->m_bool == true) AZ_TEST_ASSERT(s_scriptClass3Instance->m_intData == 10) AZ_TEST_ASSERT(s_scriptClass3Instance->m_floatData == 20.0f) AZ_TEST_ASSERT(s_scriptClass3Instance->m_incompletePtr == s_globalIncompletePtr); AZ_TEST_ASSERT(s_scriptClass3Instance->m_scriptClass2 == nullptr); // we need to transfer the ownership of the object script.Execute("sc3.scriptClass2 = ScriptClass2(333):ReleaseOwnership()"); AZ_TEST_ASSERT(s_scriptClass3Instance->m_scriptClass2 != nullptr); AZ_TEST_ASSERT(s_scriptClass3Instance->m_scriptClass2->m_data == 333); script.Execute("sc3.scriptClass2.data = 444"); AZ_TEST_ASSERT(s_scriptClass3Instance->m_scriptClass2->m_data == 444); ScriptClass2* scriptClass2ToDelete = s_scriptClass3Instance->m_scriptClass2; // we Lua released the ownership script.Execute("sc3.scriptClass2 = nil"); AZ_TEST_ASSERT(s_scriptClass3Instance->m_scriptClass2 == nullptr); script.Execute("sc3 = nil collectgarbage(\"collect\")"); delete scriptClass2ToDelete; AZ_TEST_ASSERT(s_scriptClass3Instance == nullptr); // test a class with shared_ptr owner policy AZ_TEST_ASSERT(scriptClass4numInstances == 0); script.Execute("sc4 = shared_ptr_ScriptClass4(ScriptClass4(101))"); // create an instance and DON'T HOLD A SHARED PTR AZ_TEST_ASSERT(scriptClass4numInstances == 1); AZ_TEST_ASSERT(scriptClass4lockedInstances.empty()); script.Execute("sc41 = shared_ptr_ScriptClass4(ScriptClass4(201)) HoldScriptClass4(sc41)"); // create an instance and HOLD A SHARED PTR AZ_TEST_ASSERT(scriptClass4numInstances == 2); AZ_TEST_ASSERT(scriptClass4lockedInstances.size() == 1); AZ_TEST_ASSERT(scriptClass4lockedInstances[0]->m_data == 201); script.Execute("sc41:CopyData(sc4)"); AZ_TEST_ASSERT(scriptClass4lockedInstances[0]->m_data == 101); script.Execute("sc4 = nil sc41 = nil collectgarbage(\"collect\")"); // this should release and delete sc4 but NOT sc41 AZ_TEST_ASSERT(scriptClass4numInstances == 1); AZ_TEST_ASSERT(scriptClass4lockedInstances.size() == 1); scriptClass4lockedInstances.clear(); AZ_TEST_ASSERT(scriptClass4numInstances == 0); // test a class with intrusive_ptr owner policy AZ_TEST_ASSERT(scriptClass5numInstances == 0); script.Execute("sc5 = intrusive_ptr_ScriptClass5(ScriptClass5(101))"); // create an instance and DON'T HOLD A INTRUSIVE PTR (check constructor) AZ_TEST_ASSERT(scriptClass5numInstances == 1); AZ_TEST_ASSERT(scriptClass5lockedInstances.empty()); script.Execute("sc51 = intrusive_ptr_ScriptClass5(ScriptClass5(201)) HoldScriptClass5(sc51)"); // create an instance and HOLD A INTRUSIVE PTR (check constructor) AZ_TEST_ASSERT(scriptClass5numInstances == 2); AZ_TEST_ASSERT(scriptClass5lockedInstances.size() == 1); AZ_TEST_ASSERT(scriptClass5lockedInstances[0]->m_data == 201); script.Execute("sc51:CopyData(sc5)"); AZ_TEST_ASSERT(scriptClass5lockedInstances[0]->m_data == 101); script.Execute("sc5 = nil sc51 = nil collectgarbage(\"collect\")"); // this should release and delete sc4 but NOT sc41 AZ_TEST_ASSERT(scriptClass5numInstances == 1); AZ_TEST_ASSERT(scriptClass5lockedInstances.size() == 1); scriptClass5lockedInstances.clear(); AZ_TEST_ASSERT(scriptClass5numInstances == 0); // Check nested classes // create valueHolder script.Execute("valueHolder = ScriptValueHolder()"); // test reading user value data type script.Execute("globalVar1 = valueHolder.value.data"); AZ_TEST_ASSERT(s_globalVar1 == 10.0f); // test writing user value data type script.Execute("valueHolder.value.data = 5"); script.Execute("globalVar1 = valueHolder.value.data"); AZ_TEST_ASSERT(s_globalVar1 == 5.0f); // create user value type script.Execute("userValueType = ScriptValueClass()"); // test reading user value data type s_globalVar1 = 0.0f; script.Execute("globalVar1 = userValueType.data"); AZ_TEST_ASSERT(s_globalVar1 == 10.0f); // test assignment of value types s_globalVar1 = 0.0f; script.Execute("valueHolder.value = userValueType"); script.Execute("globalVar1 = valueHolder.value.data"); AZ_TEST_ASSERT(s_globalVar1 == 10.0f); // make sure we copy the userValueType, not store a reference to it! script.Execute("userValueType.data = 100"); script.Execute("globalVar1 = valueHolder.value.data"); AZ_TEST_ASSERT(s_globalVar1 == 10.0f); // now store a reference and make sure it works script.Execute("userValueTypeRef = valueHolder.value"); script.Execute("userValueTypeRef.data = 100"); script.Execute("globalVar1 = valueHolder.value.data"); AZ_TEST_ASSERT(s_globalVar1 == 100.0f); script.Execute("valueHolder = nil userValueType = nil"); // test class pointer casting for unregistered classes ScriptUnregisteredBaseClass ubc; ScriptUnregisteredDerivedClass udc; s_globalUnregBaseClass = &udc; s_globalUnregDerivedClass = &udc; s_globalVar = 0; script.Execute("globalVar = GetUnregisteredScriptBaseData(globalUnregDerivedClass)"); // using downcast AZ_TEST_ASSERT(s_globalVar == 10); s_globalVar = 0; script.Execute("globalVar = GetUnregisteredScriptBaseData(globalUnregBaseClass)"); AZ_TEST_ASSERT(s_globalVar == 10); s_globalVar = 0; script.Execute("globalVar = GetUnregisteredScriptDerivedData(globalUnregBaseClass)"); // using RTTI upcast AZ_TEST_ASSERT(s_globalVar == 10); // register a base and derived class test // test that derived classes copy the base class properties s_globalVar = 0; script.Execute("derivedWithBase = ScriptRegisteredDerivedClass()"); script.Execute("globalVar = derivedWithBase.baseData"); // access base class data (RTTI is needed to be discovered and imported into top class metatable) AZ_TEST_ASSERT(s_globalVar == 2); // check that virtual functions work properly. script.Execute("globalRegisteredBaseClass = derivedWithBase"); // this will cast ScriptRegisteredDerivedClass to ScriptRegisteredBaseClass script.Execute("registeredBase = globalRegisteredBaseClass"); // this should cast back to the top known/registered class using AZRTTI. script.Execute("globalVar = registeredBase:VirtualFunction()"); AZ_TEST_ASSERT(s_globalVar == 1); script.Execute("globalVar = derivedWithBase:VirtualFunction()"); AZ_TEST_ASSERT(s_globalVar == 1); script.Execute("globalRegisteredBaseClass = nil registeredBase = nil derivedWithBase = nil"); // abstract classes s_globalVar = 0; script.Execute("abstract = AbstractImplementation()"); script.Execute("globalVar = abstract:PureCall()"); AZ_TEST_ASSERT(s_globalVar == 5); script.Execute("abstract = nil"); s_globalVarString.set_capacity(0); // free all memory } TEST_F(ScriptBindTest, Test) { run(); } class ScriptContextTest : public AllocatorsFixture { public: void run() { ScriptContext script; const char code[] = "\ GlobalValueA = 6\ GlobalValueB = 12.4\ GlobalValueC = \"GlobalTest\"\ GlobalTable = {\ valueA = 5,\ valueB = 11.3,\ valueC = \"TableTest\",\ SubTable = {\ valueA = 6,\ valueB = 12.4,\ SubTable = {\ valueA = 7,\ },\ },\ }\ function GlobalTable.TableFunction (a)\ return a\ end\ function GlobalFunction ()\ return 25\ end\ function GlobalSquare (a)\ return a,a*a\ end\ function GlobalFast (a)\ return a*2\ end\ GlobalTableWithMeta = {}\ setmetatable(GlobalTableWithMeta,GlobalTable)\ "; script.Execute(code); ScriptDataContext dc; bool status; int intResult = 0; float floatResult = 0.f; const char* stringResult = nullptr; // inspect test // global int status = script.FindGlobal("GlobalValueA", dc); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(dc.IsNumber(0)); status = dc.ReadValue(0, intResult); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(intResult == 6); // global float status = script.FindGlobal("GlobalValueB", dc); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(dc.IsNumber(0)); status = dc.ReadValue(0, floatResult); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT_FLOAT_CLOSE(floatResult, 12.4f); // string value status = script.FindGlobal("GlobalValueC", dc); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(dc.IsString(0)); status = dc.ReadValue(0, stringResult); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(strcmp(stringResult, "GlobalTest") == 0); // table value status = script.FindGlobal("GlobalTable", dc); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(dc.IsTable(0)); AZ_TEST_ASSERT(dc.CheckTableElement(0, "valueA")); status = dc.ReadTableElement(0, "valueA", intResult); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(intResult == 5); AZ_TEST_ASSERT(dc.CheckTableElement(0, "valueB")); status = dc.ReadTableElement(0, "valueB", floatResult); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT_FLOAT_CLOSE(floatResult, 11.3f); AZ_TEST_ASSERT(dc.CheckTableElement(0, "valueC")); status = dc.ReadTableElement(0, "valueC", stringResult); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(strcmp(stringResult, "TableTest") == 0); // inspect table { ScriptDataContext dcTable; dc.InspectTable(0, dcTable); const char* fieldName; int fieldIndex; int valueIndex; while (dcTable.InspectNextElement(valueIndex, fieldName, fieldIndex)) { AZ_TEST_ASSERT(fieldIndex == -1); // we don't have indexed elements if (strcmp(fieldName, "valueA") == 0) { AZ_TEST_ASSERT(dcTable.IsNumber(valueIndex)); int intValue = 0; dcTable.ReadValue(valueIndex, intValue); AZ_TEST_ASSERT(intValue == 5); } else if (strcmp(fieldName, "valueB") == 0) { AZ_TEST_ASSERT(dcTable.IsNumber(valueIndex)); float floatValue = 0.f; dcTable.ReadValue(valueIndex, floatValue); AZ_TEST_ASSERT(fabsf(floatValue - 11.3f) < 0.001f); } else if (strcmp(fieldName, "valueC") == 0) { AZ_TEST_ASSERT(dcTable.IsString(valueIndex)); const char* stringValue; dcTable.ReadValue(valueIndex, stringValue); AZ_TEST_ASSERT(strcmp(stringResult, "TableTest") == 0); } else if (strcmp(fieldName, "SubTable") == 0) { AZ_TEST_ASSERT(dcTable.IsTable(valueIndex)); ScriptDataContext subTable; dcTable.InspectTable(valueIndex, subTable); while (subTable.InspectNextElement(valueIndex, fieldName, fieldIndex)) { AZ_TEST_ASSERT(fieldIndex == -1); // we don't have indexed elements if (strcmp(fieldName, "valueA") == 0) { AZ_TEST_ASSERT(subTable.IsNumber(valueIndex)); int intValue = 0; subTable.ReadValue(valueIndex, intValue); AZ_TEST_ASSERT(intValue == 6); } else if (strcmp(fieldName, "valueB") == 0) { AZ_TEST_ASSERT(subTable.IsNumber(valueIndex)); float floatValue = 0.f; subTable.ReadValue(valueIndex, floatValue); AZ_TEST_ASSERT(fabsf(floatValue - 12.4f) < 0.001f); } else if (strcmp(fieldName, "SubTable") == 0) { ScriptDataContext subTable1; subTable.InspectTable(valueIndex, subTable1); while (subTable1.InspectNextElement(valueIndex, fieldName, fieldIndex)) { AZ_TEST_ASSERT(fieldIndex == -1); // we don't have indexed elements if (strcmp(fieldName, "valueA") == 0) { AZ_TEST_ASSERT(subTable1.IsNumber(valueIndex)); int intValue = 0; subTable1.ReadValue(valueIndex, intValue); AZ_TEST_ASSERT(intValue == 7); } } } } } else if (strcmp(fieldName, "TableFunction") == 0) { AZ_TEST_ASSERT(dcTable.IsFunction(valueIndex)); ScriptDataContext callContext; dcTable.Call(valueIndex, callContext); callContext.PushArg(11); callContext.CallExecute(); AZ_TEST_ASSERT(callContext.GetNumResults() == 1); int intValue = 0; callContext.ReadResult(0, intValue); AZ_TEST_ASSERT(intValue == 11); } else { AZ_TEST_ASSERT(false); } } } // table replace dc.AddReplaceTableElement(0, "valueA", 4); status = dc.ReadTableElement(0, "valueA", intResult); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(intResult == 4); // table add AZ_TEST_ASSERT(dc.CheckTableElement(0, "valueD") == false); dc.AddReplaceTableElement(0, "valueD", 11); AZ_TEST_ASSERT(dc.CheckTableElement(0, "valueD")); dc.ReadTableElement(0, "valueD", intResult); AZ_TEST_ASSERT(intResult == 11); // global function call (no arguments 1 result) status = script.FindGlobal("GlobalFunction", dc); AZ_TEST_ASSERT(status); { ScriptDataContext callContext; status = dc.Call(0, callContext); AZ_TEST_ASSERT(status); status = callContext.CallExecute(); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(callContext.GetNumResults() == 1); AZ_TEST_ASSERT(callContext.IsNumber(0)); callContext.ReadResult(0, intResult); AZ_TEST_ASSERT(intResult == 25); } // global function call (1 argument 2 results) { ScriptDataContext callContext; status = script.FindGlobal("GlobalSquare", dc); AZ_TEST_ASSERT(status); status = dc.Call(0, callContext); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(callContext.GetNumArguments() == 0); callContext.PushArg(3); AZ_TEST_ASSERT(callContext.GetNumArguments() == 1); status = callContext.CallExecute(); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(callContext.GetNumResults() == 2); AZ_TEST_ASSERT(callContext.IsNumber(0)); AZ_TEST_ASSERT(callContext.IsNumber(1)); int intResult2 = 0; callContext.ReadResult(0, intResult); callContext.ReadResult(1, intResult2); AZ_TEST_ASSERT(intResult == 3); AZ_TEST_ASSERT(intResult2 == 9); } // global cached function call int cachedIndex = script.CacheGlobal("GlobalFast"); AZ_TEST_ASSERT(cachedIndex != -1); int value = 2; for (int i = 0; i < 6; ++i) { ScriptDataContext callContext; status = script.FindGlobal(cachedIndex, dc); AZ_TEST_ASSERT(status); status = dc.Call(0, callContext); AZ_TEST_ASSERT(status); callContext.PushArg(value); status = callContext.CallExecute(); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(callContext.GetNumResults() == 1); AZ_TEST_ASSERT(callContext.IsNumber(0)); callContext.ReadResult(0, value); } AZ_TEST_ASSERT(value == 128); script.ReleaseCached(cachedIndex); // metatable inspect status = script.FindGlobal("GlobalTableWithMeta", dc); AZ_TEST_ASSERT(status); AZ_TEST_ASSERT(dc.IsTable(0)); { ScriptDataContext dcTable; dc.InspectTable(0, dcTable); const char* fieldName; int fieldIndex; int valueIndex; while (dcTable.InspectNextElement(valueIndex, fieldName, fieldIndex)) { AZ_TEST_ASSERT(false); // this table doesn't have any elements } status = dc.InspectMetaTable(0, dcTable); AZ_TEST_ASSERT(status); while (dcTable.InspectNextElement(valueIndex, fieldName, fieldIndex)) { if (strcmp(fieldName, "valueA") == 0) { AZ_TEST_ASSERT(dcTable.IsNumber(valueIndex)); int intValue = 0; dcTable.ReadValue(valueIndex, intValue); AZ_TEST_ASSERT(intValue == 4); } else if (strcmp(fieldName, "valueB") == 0) { AZ_TEST_ASSERT(dcTable.IsNumber(valueIndex)); float floatValue = 0.f; dcTable.ReadValue(valueIndex, floatValue); AZ_TEST_ASSERT(fabsf(floatValue - 11.3f) < 0.001f); } else if (strcmp(fieldName, "valueC") == 0) { AZ_TEST_ASSERT(dcTable.IsString(valueIndex)); const char* stringValue; dcTable.ReadValue(valueIndex, stringValue); AZ_TEST_ASSERT(strcmp(stringResult, "TableTest") == 0); } else if (strcmp(fieldName, "valueD") == 0) { AZ_TEST_ASSERT(dcTable.IsNumber(valueIndex)); int intValue = 0; dcTable.ReadValue(valueIndex, intValue); AZ_TEST_ASSERT(intValue == 11); } else if (strcmp(fieldName, "SubTable") == 0) { AZ_TEST_ASSERT(dcTable.IsTable(valueIndex)); ScriptDataContext subTable; dcTable.InspectTable(valueIndex, subTable); while (subTable.InspectNextElement(valueIndex, fieldName, fieldIndex)) { AZ_TEST_ASSERT(fieldIndex == -1); // we don't have indexed elements if (strcmp(fieldName, "valueA") == 0) { AZ_TEST_ASSERT(subTable.IsNumber(valueIndex)); int intValue = 0; subTable.ReadValue(valueIndex, intValue); AZ_TEST_ASSERT(intValue == 6); } else if (strcmp(fieldName, "valueB") == 0) { AZ_TEST_ASSERT(subTable.IsNumber(valueIndex)); float floatValue = 0.f; subTable.ReadValue(valueIndex, floatValue); AZ_TEST_ASSERT(fabsf(floatValue - 12.4f) < 0.001f); } else if (strcmp(fieldName, "SubTable") == 0) { ScriptDataContext subTable1; subTable.InspectTable(valueIndex, subTable1); while (subTable1.InspectNextElement(valueIndex, fieldName, fieldIndex)) { AZ_TEST_ASSERT(fieldIndex == -1); // we don't have indexed elements if (strcmp(fieldName, "valueA") == 0) { AZ_TEST_ASSERT(subTable1.IsNumber(valueIndex)); int intValue = 0; subTable1.ReadValue(valueIndex, intValue); AZ_TEST_ASSERT(intValue == 7); } } } } } else if (strcmp(fieldName, "TableFunction") == 0) { AZ_TEST_ASSERT(dcTable.IsFunction(valueIndex)); ScriptDataContext callContext; dcTable.Call(valueIndex, callContext); callContext.PushArg(11); callContext.CallExecute(); AZ_TEST_ASSERT(callContext.GetNumResults() == 1); int intValue = 0; callContext.ReadResult(0, intValue); AZ_TEST_ASSERT(intValue == 11); } else { AZ_TEST_ASSERT(false); } } } // script loaders // from load function // from data buffer // from string } }; TEST_F(ScriptContextTest, Test) { run(); } class ScriptDebugTest : public AllocatorsFixture { int m_numBreakpointHits; static ScriptContext* s_currentSC; public: ScriptDebugTest() : m_numBreakpointHits(0) {} bool EnumLocal(const char* name, ScriptDataContext& dataContext) { AZ_TEST_ASSERT(strcmp(name, "a") == 0 || strcmp(name, "result") == 0); //AZ_Printf("Script","Local variable %s = ",name); if (dataContext.IsNumber(0)) { double number = 0.; dataContext.ReadValue(0, number); //AZ_Printf("Script","%f [number]\n",number); AZ_TEST_ASSERT(number == 10.0); } return true; } void BreakpointCallback(ScriptContextDebug* debugContext, const ScriptContextDebug::Breakpoint* breakpoint) { //AZ_Printf("Script","\nBreakpoint at '%s(%d)' triggered!",breakpoint->m_sourceName,breakpoint->m_lineNumber); AZ_TEST_ASSERT(breakpoint->m_sourceName == "DebugCode"); if (m_numBreakpointHits == 0) { AZ_TEST_ASSERT(breakpoint->m_lineNumber == 23); ScriptContextDebug::EnumLocalCallback enumCB = AZStd::bind(&ScriptDebugTest::EnumLocal, this, AZStd::placeholders::_1, AZStd::placeholders::_2); debugContext->EnumLocals(enumCB); debugContext->StepInto(); // go inside the script to line 20 } else if (m_numBreakpointHits == 1) { char stackOutput[2048]; debugContext->StackTrace(stackOutput, AZ_ARRAY_SIZE(stackOutput)); AZ_Printf("Script", "%s", stackOutput); AZ_TEST_ASSERT(strstr(stackOutput, "GlobalFunction") != 0); AZ_TEST_ASSERT(breakpoint->m_lineNumber == 20); } else if (m_numBreakpointHits == 2) { AZ_TEST_ASSERT(breakpoint->m_lineNumber == 23); // check A a local variable ScriptContextDebug::DebugValue value; value.m_name = "a"; bool valueResult = debugContext->GetValue(value); AZ_TEST_ASSERT(valueResult); AZ_TEST_ASSERT(value.m_type == LUA_TNUMBER); AZ_TEST_ASSERT(value.m_value.find("11.0") != OSString::npos); value.m_value = "3.0"; // modify the value debugContext->SetValue(value); debugContext->StepOver(); // same place as before, but now step over to line 24 } else if (m_numBreakpointHits == 3) { AZ_TEST_ASSERT(breakpoint->m_lineNumber == 24); } else if (m_numBreakpointHits == 4) { AZ_TEST_ASSERT(breakpoint->m_lineNumber == 23); debugContext->StepOut(); // same place as before, but now step out to line 28 } else if (m_numBreakpointHits == 5) { AZ_TEST_ASSERT(breakpoint->m_lineNumber == 28); } else if (m_numBreakpointHits == 6) { // called from C->script->C->script scenario char stackOutput[2048]; debugContext->StackTrace(stackOutput, AZ_ARRAY_SIZE(stackOutput)); AZ_Printf("Script", "%s", stackOutput); AZ_TEST_ASSERT(strstr(stackOutput, "GlobalMult") != 0); AZ_TEST_ASSERT(breakpoint->m_lineNumber == 23); } ++m_numBreakpointHits; } static int CallCFuncOperation(int value) { int result = 0; // call a script function if (s_currentSC) { ScriptDataContext call; if (s_currentSC->Call("GlobalFast", call)) { call.PushArg(value); if (call.CallExecute()) { if (call.GetNumResults()) { call.ReadResult(0, result); } } } } return result; } void run() { BehaviorContext behaviorContext; behaviorContext.Class()-> Allocator(&ScriptClassAllocate, &ScriptClassFree)-> Enum("SC_ET_VALUE2")-> Enum("SC_ET_VALUE3")-> Constant("EPSILON", BehaviorConstant(0.001f))-> Method("MemberFunc0", &ScriptClass::MemberFunc0)-> Method("MemberFunc1", &ScriptClass::MemberFunc1)-> Method("MemberFunc2", &ScriptClass::MemberFunc2)-> Method("MemberFunc3", &ScriptClass::MemberFunc3)-> Method("MemberFunc4", &ScriptClass::MemberFunc4)-> Method("MemberFunc5", &ScriptClass::MemberFunc5)-> Method("MemberVoidFunc0", &ScriptClass::MemberVoidFunc0)-> Method("MemberVoidFunc1", &ScriptClass::MemberVoidFunc1)-> Method("MemberVoidFunc2", &ScriptClass::MemberVoidFunc2)-> Method("MemberVoidFunc3", &ScriptClass::MemberVoidFunc3)-> Method("MemberVoidFunc4", &ScriptClass::MemberVoidFunc4)-> Method("MemberVoidFunc5", &ScriptClass::MemberVoidFunc5)-> Method("VariadicFunc", &ScriptClass::VariadicFunc)-> Property("data", &ScriptClass::GetData, &ScriptClass::SetData)-> Property("data1", BehaviorValueProperty(&ScriptClass::m_data1))-> Property("data2", BehaviorValueGetter(&ScriptClass::m_data2ReadOnly), nullptr)-> Property("data3", nullptr, BehaviorValueSetter(&ScriptClass::m_data3WriteOnly)); behaviorContext.Property("globalField", BehaviorValueProperty(&s_globalField)); behaviorContext.Method("CallCFuncOperation", &CallCFuncOperation); behaviorContext.Class()-> Property("data", BehaviorValueProperty(&ScriptValueClass::m_data)); behaviorContext.Class()-> Property("value", BehaviorValueProperty(&ScriptValueHolder::m_value)); ScriptContext script; script.BindTo(&behaviorContext); const char code[] = "GlobalValueA = 6\n\ GlobalValueB = 12.4\n\ GlobalValueC = \"GlobalTest\"\n\ GlobalTable = {\n\ valueA = 5,\n\ valueB = 11.3,\n\ valueC = \"TableTest\",\n\ SubTable = {\n\ valueA = 6,\n\ valueB = 12.4,\n\ SubTable = {\n\ valueA = 7,\n\ },\n\ },\n\ }\n\ function GlobalTable.TableFunction (a)\n\ return a\n\ end\n\ function GlobalFunction ()\n\ return 25\n\ end\n\ function GlobalMult (a)\n\ local result = a*GlobalFunction()\n\ return result\n\ end\n\ function GlobalFast (a)\n\ local result = GlobalMult(a)*2\n\ return result\n\ end\n\ Result=GlobalFast(10)\n\ Result=GlobalFast(11)\n\ Result=GlobalFast(12)\n\ GlobalValueB=Result*2\n\ GlobalTableDerived={}\n\ setmetatable(GlobalTableDerived,GlobalTable)"; const char code2[] = "function CallACFunction(a)\n\ return CallCFuncOperation(a)\n\ end\n\ Result = CallACFunction(10)"; // enable script debug script.EnableDebug(); // get debug context ScriptContextDebug* debugContext = script.GetDebugContext(); if (debugContext == nullptr) { return; // don't test when not supported. } // add a breakpoint code line 23 (return a*a) ScriptContextDebug::Breakpoint bp; bp.m_sourceName = "DebugCode"; bp.m_lineNumber = 23; debugContext->AddBreakpoint(bp); // enable breakpoints ScriptContextDebug::BreakpointCallback breakpointCallback = AZStd::bind(&ScriptDebugTest::BreakpointCallback, this, AZStd::placeholders::_1, AZStd::placeholders::_2); debugContext->EnableBreakpoints(breakpointCallback); // enabled 'advanced' stack tracing debugContext->EnableStackRecord(); // execute the code (that will trigger a breakpoint) script.Execute(code, "DebugCode"); // now check debug break with script1->C Code->script2 s_currentSC = &script; script.Execute(code2, "DebugCode2"); s_currentSC = nullptr; // disable breakpoints (not this doesn't delete them, just disables them) debugContext->DisableBreakpoints(); // test get value from globals, tables, function, and user class ScriptContextDebug::DebugValue values[9]; bool getValueResult; // make sure we detect non existing variables values[0].m_name = "InvalidVariable"; getValueResult = debugContext->GetValue(values[0]); AZ_TEST_ASSERT(getValueResult == false); // get global number values[0].m_name = "GlobalValueA"; getValueResult = debugContext->GetValue(values[0]); AZ_TEST_ASSERT(getValueResult); AZ_TEST_ASSERT(values[0].m_type == LUA_TNUMBER); AZ_TEST_ASSERT(values[0].m_value.find("6.0") != OSString::npos); // get global string values[1].m_name = "GlobalValueC"; getValueResult = debugContext->GetValue(values[1]); AZ_TEST_ASSERT(getValueResult); AZ_TEST_ASSERT(values[1].m_type == LUA_TSTRING); AZ_TEST_ASSERT(values[1].m_value == "GlobalTest"); // get global table values[2].m_name = "GlobalTable"; getValueResult = debugContext->GetValue(values[2]); AZ_TEST_ASSERT(getValueResult); AZ_TEST_ASSERT(values[2].m_type == LUA_TTABLE); AZ_TEST_ASSERT(values[2].m_elements.size() == 5); // valueA,valueB,valueC,SubTable and TableFunction // get global table with a metatable values[3].m_name = "GlobalTableDerived"; getValueResult = debugContext->GetValue(values[3]); AZ_TEST_ASSERT(getValueResult); AZ_TEST_ASSERT(values[3].m_type == LUA_TTABLE); AZ_TEST_ASSERT(values[3].m_elements.size() == 1); // valueA,valueB,valueC,SubTable and TableFunction AZ_TEST_ASSERT(values[3].m_elements[0].m_elements.size() == 5); // valueA,valueB,valueC,SubTable and TableFunction // get global function values[4].m_name = "GlobalFunction"; getValueResult = debugContext->GetValue(values[4]); AZ_TEST_ASSERT(getValueResult); AZ_TEST_ASSERT(values[4].m_type == LUA_TFUNCTION); // get global user class script.Execute("sc = ScriptClass()"); // create a C++ class values[5].m_name = "sc"; getValueResult = debugContext->GetValue(values[5]); AZ_TEST_ASSERT(getValueResult); AZ_TEST_ASSERT(values[5].m_type == LUA_TUSERDATA); // check nested user classes script.Execute("sv = ScriptValueHolder()"); values[6].m_name = "sv"; getValueResult = debugContext->GetValue(values[6]); AZ_TEST_ASSERT(getValueResult); // get global property/field s_globalField = 23; values[7].m_name = "globalField"; getValueResult = debugContext->GetValue(values[7]); AZ_TEST_ASSERT(getValueResult); AZ_TEST_ASSERT(values[7].m_value.find("23") != OSString::npos); // make sure we can get the global table safely (if we want to support cyclic tables) values[8].m_name = "_G"; getValueResult = debugContext->GetValue(values[8]); AZ_TEST_ASSERT(getValueResult == true); ScriptContextDebug::DebugValue valueCheck; // set a global number and change it's type to a string values[0].m_value = "BlaBla"; values[0].m_type = LUA_TSTRING; debugContext->SetValue(values[0]); // check the SetValue valueCheck.m_name = values[0].m_name; debugContext->GetValue(valueCheck); AZ_TEST_ASSERT(valueCheck.m_type == LUA_TSTRING); AZ_TEST_ASSERT(valueCheck.m_value == "BlaBla"); // set a global string values[1].m_value = "BlaBla1"; debugContext->SetValue(values[1]); // check the set value valueCheck.m_name = values[1].m_name; debugContext->GetValue(valueCheck); AZ_TEST_ASSERT(valueCheck.m_value == "BlaBla1"); // set a table value values[2].m_elements[0].m_value = "Aha"; values[2].m_elements[0].m_type = LUA_TSTRING; debugContext->SetValue(values[2]); // check valueCheck.m_name = values[2].m_name; debugContext->GetValue(valueCheck); AZ_TEST_ASSERT(values[2].m_elements.size() == valueCheck.m_elements.size()); // set a table value with metatable values[3].m_elements.push_back(); ScriptContextDebug::DebugValue& subValue = values[3].m_elements.back(); subValue.m_name = "valueA"; subValue.m_type = LUA_TNUMBER; subValue.m_value = "16"; debugContext->SetValue(values[3]); // check valueCheck.m_name = values[3].m_name; debugContext->GetValue(valueCheck); AZ_TEST_ASSERT(values[3].m_elements.size() == valueCheck.m_elements.size()); int data1Element = -1; for (size_t i = 0; i < values[5].m_elements.size(); ++i) { if (values[5].m_elements[i].m_name == "data1") { data1Element = static_cast(i); } } AZ_TEST_ASSERT(data1Element != -1); // make sure we have data 1 element // user class AZ_TEST_ASSERT(values[5].m_elements[data1Element].m_name == "data1"); // make sure we are modifying the correct element values[5].m_elements[data1Element].m_value = "6.0"; debugContext->SetValue(values[5]); // check valueCheck.m_name = values[5].m_name; debugContext->GetValue(valueCheck); AZ_TEST_ASSERT(values[5].m_elements.size() == valueCheck.m_elements.size()); AZ_TEST_ASSERT(valueCheck.m_elements[data1Element].m_value.find("6.0") != OSString::npos); // user nested class values[6].m_elements[0].m_elements[0].m_value = "3.0"; debugContext->SetValue(values[6]); // check valueCheck.m_name = values[6].m_name; debugContext->GetValue(valueCheck); AZ_TEST_ASSERT(valueCheck.m_elements[0].m_elements[0].m_value.find("3.0") != OSString::npos); // global table // modify todo debugContext->SetValue(values[8]); // check (TODO) // disable debug support (this will delete all breakpoints and everything debug related!) script.DisableDebug(); } }; ScriptContext* ScriptDebugTest::s_currentSC = nullptr; TEST_F(ScriptDebugTest, Test) { run(); } //----------------------------------------------------------------------------- // Ebus script test //----------------------------------------------------------------------------- void TestAssert(bool check) { (void)check; AZ_Assert(!check, "Script Test assert"); } class TestBusMessages : public AZ::EBusTraits { public: virtual ~TestBusMessages() {} virtual void Set() = 0; virtual void SetNotImplemented() = 0; virtual void SetSum1(int) = 0; virtual void SetSum2(int, int) = 0; virtual void SetSum3(int, int, int) = 0; virtual void SetSum4(int, int, int, int) = 0; virtual void SetSum5(int, int, int, int, int) = 0; virtual int Get() = 0; virtual int GetNotImplemented() = 0; virtual int GetSum1(int) = 0; virtual int GetSum2(int, int) = 0; virtual int GetSum3(int, int, int) = 0; virtual int GetSum4(int, int, int, int) = 0; virtual int GetSum5(int, int, int, int, int) = 0; }; typedef AZ::EBus TestBus; class TestBusHandler : public TestBus::Handler , public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER(TestBusHandler, "{CD26E702-6F40-4FF9-816D-4DCB652D97DF}", AZ::SystemAllocator, Set, SetNotImplemented, SetSum1, SetSum2, SetSum3, SetSum4, SetSum5, Get, GetNotImplemented, GetSum1, GetSum2, GetSum3, GetSum4, GetSum5); void Set() override { Call(FN_Set); } void SetNotImplemented() override { Call(FN_SetNotImplemented); } void SetSum1(int d1) override { Call(FN_SetSum1, d1); } void SetSum2(int d1, int d2) override { Call(FN_SetSum2, d1, d2); } void SetSum3(int d1, int d2, int d3) override { Call(FN_SetSum3, d1, d2, d3); } void SetSum4(int d1, int d2, int d3, int d4) override { Call(FN_SetSum4, d1, d2, d3, d4); } void SetSum5(int d1, int d2, int d3, int d4, int d5) override { Call(FN_SetSum5, d1, d2, d3, d4, d5); } int Get() override { int result = 0; CallResult(result, FN_Get); return result; } int GetNotImplemented() override { int result = -1; CallResult(result, FN_GetNotImplemented); return result; } int GetSum1(int d1) override { int result = 0; CallResult(result, FN_GetSum1, d1); return result; } int GetSum2(int d1, int d2) override { int result = 0; CallResult(result, FN_GetSum2, d1, d2); return result; } int GetSum3(int d1, int d2, int d3) override { int result = 0; CallResult(result, FN_GetSum3, d1, d2, d3); return result; } int GetSum4(int d1, int d2, int d3, int d4) override { int result = 0; CallResult(result, FN_GetSum4, d1, d2, d3, d4); return result; } int GetSum5(int d1, int d2, int d3, int d4, int d5) override { int result = 0; CallResult(result, FN_GetSum5, d1, d2, d3, d4, d5); return result; } }; //class TestBusSenderCheck : public TestBus::Handler //{ //public: // virtual void Set() { TestAssert(true); } // virtual void SetNotImplemented() { /*Not used*/ } // virtual void SetSum1(int _1) { TestAssert(_1 == 1); } // virtual void SetSum2(int _1, int _2) { TestAssert(_1 + _2 == 3); } // virtual void SetSum3(int _1, int _2, int _3) { TestAssert(_1 + _2 + _3 == 6); } // virtual void SetSum4(int _1, int _2, int _3, int _4) { TestAssert(_1 + _2 + _3 + _4 == 10); } // virtual void SetSum5(int _1, int _2, int _3, int _4, int _5) { TestAssert(_1 + _2 + _3 + _4 + _5 == 15); } // virtual int Get() { return -1; } // virtual int GetNotImplemented() { return -2; /*Not used*/ } // virtual int GetSum1(int _1) { return _1; } // virtual int GetSum2(int _1, int _2) { return _1 + _2; } // virtual int GetSum3(int _1, int _2, int _3) { return _1 + _2 + _3; } // virtual int GetSum4(int _1, int _2, int _3, int _4) { return _1 + _2 + _3 + _4; } // virtual int GetSum5(int _1, int _2, int _3, int _4, int _5) { return _1 + _2 + _3 + _4 + _5; } //}; //class MyInterface //{ //public: // // AZ_TYPE_INFO(MyInterface,"{96a83332-8cd1-4299-9972-e670be92ab99}"); // virtual int GetInt() const = 0; //}; /* class MyImpl : public MyInterface { public: int GetInt() const override { return 5; } };*/ //class MyRequests : public AZ::EBusTraits //{ //public: // virtual const MyInterface* GetIFace() = 0; //}; //using MyRequestBus = AZ::EBus; //AZ_SCRIPTABLE_EBUS( // MyRequestBus, // MyRequestBus, // "{DD4261AD-416E-4824-97A8-EFEF0F54AD91}", // "{9312EB0C-0717-4F91-97F8-30A946096E9B}", // AZ_SCRIPTABLE_EBUS_EVENT_RESULT(const MyInterface*, nullptr, GetIFace) // ); class ScriptedBusTest : public AllocatorsFixture { public: void run() { const char luaCode[] = "\ MyBusHandlerMetaTable1 = {\ Set = function(self)\ TestAssert(true)\ end,\ SetSum1 = function(self, _1)\ TestAssert(_1 == 1)\ end,\ SetSum2 = function(self, _1, _2)\ TestAssert(_1 + _2 == 3)\ end,\ SetSum3 = function(self, _1, _2, _3)\ TestAssert(_1 + _2 + _3 == 6)\ end,\ SetSum4 = function(self, _1, _2, _3, _4)\ TestAssert(_1 + _2 + _3 + _4 == 10)\ end,\ SetSum5 = function(self, _1, _2, _3, _4, _5)\ TestAssert(_1 + _2 + _3 + _4 + _5 == 15)\ end,\ Get = function(self)\ return -1\ end,\ GetSum1 = function(self, _1)\ return _1\ end,\ GetSum2 = function(self, _1, _2)\ return _1 + _2\ end,\ GetSum3 = function(self, _1, _2, _3)\ return _1 + _2 + _3\ end,\ GetSum4 = function(self, _1, _2, _3, _4)\ return _1 + _2 + _3 + _4\ end,\ GetSum5 = function(self, _1, _2, _3, _4, _5)\ return _1 + _2 + _3 + _4 + _5\ end\ }\ MyBusHandlerMetaTable2 = {\ Get = function(self)\ return -2\ end\ }\ setmetatable(MyBusHandlerMetaTable2, MyBusHandlerMetaTable1)\ "; BehaviorContext behaviorContext; behaviorContext.Method("TestAssert", &TestAssert); behaviorContext.EBus("TestBus")-> Handler()-> Event("Set", &TestBus::Events::Set)-> Event("SetNotImplemented", &TestBus::Events::SetNotImplemented)-> Event("SetSum1", &TestBus::Events::SetSum1)-> Event("SetSum2", &TestBus::Events::SetSum2)-> Event("SetSum3", &TestBus::Events::SetSum3)-> Event("SetSum4", &TestBus::Events::SetSum4)-> Event("SetSum5", &TestBus::Events::SetSum5)-> Event("Get", &TestBus::Events::Get)-> Event("GetNotImplemented", &TestBus::Events::GetNotImplemented)-> Event("GetSum1", &TestBus::Events::GetSum1)-> Event("GetSum2", &TestBus::Events::GetSum2)-> Event("GetSum3", &TestBus::Events::GetSum3)-> Event("GetSum4", &TestBus::Events::GetSum4)-> Event("GetSum5", &TestBus::Events::GetSum5) ; ScriptContext script; script.BindTo(&behaviorContext); script.Execute(luaCode); // Test sending messages from C++ to LUA handler script.Execute("handler = TestBus.Connect(MyBusHandlerMetaTable1)"); AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT(TestBus, Set); AZ_TEST_STOP_TRACE_SUPPRESSION(1); EBUS_EVENT(TestBus, SetNotImplemented); // nothing should happen here AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT(TestBus, SetSum1, 1); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT(TestBus, SetSum2, 1, 2); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT(TestBus, SetSum3, 1, 2, 3); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT(TestBus, SetSum4, 1, 2, 3, 4); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT(TestBus, SetSum5, 1, 2, 3, 4, 5); AZ_TEST_STOP_TRACE_SUPPRESSION(1); script.Execute("handler:Disconnect()"); script.Execute("handler = TestBus.Connect(MyBusHandlerMetaTable2)"); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestBus.Broadcast.Set()"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestBus.Broadcast.SetNotImplemented()"); AZ_TEST_STOP_TRACE_SUPPRESSION(0); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestBus.Broadcast.SetSum1(1)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestBus.Broadcast.SetSum2(1, 2)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestBus.Broadcast.SetSum3(1, 2, 3)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestBus.Broadcast.SetSum4(1, 2, 3, 4)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestBus.Broadcast.SetSum5(1, 2, 3, 4, 5)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestBus.Broadcast.Get() == -2)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestBus.Broadcast.GetNotImplemented() == -1)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestBus.Broadcast.GetSum1(1) == 1)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestBus.Broadcast.GetSum2(1, 2) == 3)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestBus.Broadcast.GetSum3(1, 2, 3) == 6)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestBus.Broadcast.GetSum4(1, 2, 3, 4) == 10)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestBus.Broadcast.GetSum5(1, 2, 3, 4, 5) == 15)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); script.Execute("handler:Disconnect()\ handler = nil"); } }; TEST_F(ScriptedBusTest, Test) { run(); } // RamenRequests class RamenRequests : public AZ::EBusTraits { public: typedef unsigned int BusIdType; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; virtual void AddPepper() = 0; }; using RamenRequestBus = AZ::EBus; class RamenRequestHandler : public RamenRequestBus::Handler { public: void AddPepper() override { }; }; class RamenRequestBehaviorHandler : public RamenRequestBus::Handler , public BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER(RamenRequestBehaviorHandler, "{EB4E043B-AD1A-4745-A725-91100E191517}", AZ::SystemAllocator, AddPepper); void AddPepper() override { Call(FN_AddPepper); } }; // RamenRequests // RamenShopNotifications class RamenShopNotifications : public AZ::EBusTraits { public: typedef unsigned int BusIdType; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; virtual void OnOrderCancelled(int id) = 0; }; using RamenShopNotificationBus = AZ::EBus; class RamenShopNotificationHandler : public RamenShopNotificationBus::MultiHandler { public: void OnOrderCancelled(int /*id*/) override {}; }; class RamenShopNotificationBehaviorHandler : public RamenShopNotificationBus::MultiHandler , public BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER(RamenShopNotificationBehaviorHandler, "{98A0C1B1-0B81-4563-886A-03204DDBE146}", AZ::SystemAllocator, OnOrderCancelled); void OnOrderCancelled(int id) override { Call(FN_OnOrderCancelled, id); } }; // RamenShopNotifications class ScriptBehaviorHandlerIsConnectedTest : public AllocatorsFixture { public: static void TestAssert(bool check) { AZ_Assert(check, "Script Test assert"); } void SetUp() override { AllocatorsFixture::SetUp(); m_behaviorContext = aznew BehaviorContext(); m_behaviorContext->Method("TestAssert", &ScriptBehaviorHandlerIsConnectedTest::TestAssert); m_behaviorContext->EBus("RamenRequestBus")-> Handler()-> Event("AddPepper", &RamenRequestBus::Events::AddPepper); m_behaviorContext->EBus("RamenShopNotificationBus")-> Handler()-> Event("OnOrderCancelled", &RamenShopNotificationBus::Events::OnOrderCancelled); m_scriptContext = aznew ScriptContext(); m_scriptContext->BindTo(m_behaviorContext); } void TearDown() override { delete m_scriptContext; delete m_behaviorContext; AllocatorsFixture::TearDown(); } ScriptContext* m_scriptContext; BehaviorContext* m_behaviorContext; }; TEST_F(ScriptBehaviorHandlerIsConnectedTest, IsConnected_UberTest) { const char luaCode[] = R"( ramen = { handler1 = nil, handler2 = nil } ramen.handler1 = RamenRequestBus.Connect(ramen, 1) ramen.handler2 = RamenRequestBus.Connect(ramen, 2) ramenShop = { handler = nil } ramenShop.handler = RamenShopNotificationBus.CreateHandler(ramenShop) ramenShop.handler:Connect(3); ramenShop.handler:Connect(4); )"; m_scriptContext->Execute(luaCode); m_scriptContext->Execute("TestAssert(ramen.handler1:IsConnected())"); m_scriptContext->Execute("TestAssert(ramen.handler1:IsConnectedId(1))"); m_scriptContext->Execute("TestAssert(ramen.handler2:IsConnectedId(2))"); m_scriptContext->Execute("TestAssert(ramenShop.handler:IsConnected())"); m_scriptContext->Execute("TestAssert(ramenShop.handler:IsConnectedId(3))"); m_scriptContext->Execute("TestAssert(ramenShop.handler:IsConnectedId(4))"); } //----------------------------------------------------------------------------- // Ebus script with bus id test //----------------------------------------------------------------------------- class TestIdBusMessages : public AZ::EBusTraits { public: typedef unsigned int BusIdType; static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById; virtual ~TestIdBusMessages() {} virtual void VoidFunc0() = 0; virtual float Pick(float, float, float) = 0; }; typedef AZ::EBus TestIdBus; class TestIdBusCPPHandler : public TestIdBus::Handler { public: void VoidFunc0() override { TestAssert(true); } float Pick(float /*_1*/, float /*_2*/, float _3) override { return _3; } }; //AZ_SCRIPTABLE_EBUS( // TestIdBus, // TestIdBus, // "{2DACEBCC-CB6B-403A-9E91-4E7390F567C9}", // Handler type UUID // "{EB13A5D0-D642-49E2-9D3C-DC15F96FD97A}", // Sender type UUID // AZ_SCRIPTABLE_EBUS_EVENT(VoidFunc0) // AZ_SCRIPTABLE_EBUS_EVENT_RESULT(float, 0.f, Pick, float, float, float) // ) class TestIdBusHandler : public TestIdBus::Handler , public AZ::BehaviorEBusHandler { public: AZ_EBUS_BEHAVIOR_BINDER(TestIdBusHandler, "{2DACEBCC-CB6B-403A-9E91-4E7390F567C9}", AZ::SystemAllocator, VoidFunc0, Pick); void VoidFunc0() override { Call(FN_VoidFunc0); } float Pick(float a, float b, float c) override { float result = 0.0f; CallResult(result, FN_Pick, a, b, c); return result; } }; class ScriptedIdBusTest : public AllocatorsFixture { public: void run() { const char luaCode[] = "\ MyBusHandlerMetaTable1 = {\ VoidFunc0 = function(self)\ TestAssert(true)\ end,\ Pick = function(self, _1, _2, _3)\ return _1\ end\ }\ MyBusHandlerMetaTable2 = {\ Pick = function(self, _1, _2, _3)\ return _2\ end\ }\ setmetatable(MyBusHandlerMetaTable2, MyBusHandlerMetaTable1)\ handler1 = TestIdBus.Connect(MyBusHandlerMetaTable1, 1)\ handler2 = TestIdBus.CreateHandler(MyBusHandlerMetaTable2)\ handler2:Connect(2)\ "; BehaviorContext behaviorContext; behaviorContext.Method("TestAssert", &TestAssert); behaviorContext.EBus("TestIdBus")-> Handler()-> Event("VoidFunc0", &TestIdBus::Events::VoidFunc0)-> Event("Pick", &TestIdBus::Events::Pick); ScriptContext script; script.BindTo(&behaviorContext); script.Execute(luaCode); // Test sending messages from C++ to two LUA handlers on different bus ids AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT_ID(1, TestIdBus, VoidFunc0); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; EBUS_EVENT_ID(2, TestIdBus, VoidFunc0); AZ_TEST_STOP_TRACE_SUPPRESSION(1); float ret = 0.f; EBUS_EVENT_ID_RESULT(ret, 1, TestIdBus, Pick, 1.f, 2.f, 3.f); AZ_TEST_ASSERT(ret == 1.f); ret = 0; EBUS_EVENT_ID_RESULT(ret, 2, TestIdBus, Pick, 1.f, 2.f, 3.f); AZ_TEST_ASSERT(ret == 2.f); script.Execute("handler1:Disconnect()"); script.Execute("handler2:Disconnect()"); // Test sending messages from LUA to a LUA handler on id 1 and a C++ handler on id 3. script.Execute("handler = TestIdBus.Connect(MyBusHandlerMetaTable1, 1)"); TestIdBusCPPHandler cppHandler; cppHandler.BusConnect(3); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestIdBus.Event.VoidFunc0(1)"); // send even on id=1 AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestIdBus.Event.Pick(1, 1.0, 2.0, 3.0) == 1.0)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestIdBus.Event.VoidFunc0(3)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("TestAssert(TestIdBus.Event.Pick(3, 1.0, 2.0, 3.0) == 3.0)"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); script.Execute("handler:Disconnect()"); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("handler:Connect('my fake bus id')"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); AZ_TEST_START_TRACE_SUPPRESSION; script.Execute("handler = TestIdBus.Connect({ }, 'my fake bus id')"); AZ_TEST_STOP_TRACE_SUPPRESSION(1); script.Execute("TestAssert(handler == nil)"); } }; TEST_F(ScriptedIdBusTest, Test) { run(); } void AZTestAssert(bool check) { (void)check; AZ_Assert(check, "Script Test assert"); } class AnyScriptBindTest : public AllocatorsFixture { public: struct DataContainer { public: AZ_TYPE_INFO(DataContainer, "{FC0B12F1-165F-4D0C-ABAE-E9F657370352}"); DataContainer(int initData) : m_data(initData) { } int m_data; }; static bool s_wasCalled; static void TestThatDataIs10(AZStd::any container) { ASSERT_TRUE(container.is()); ASSERT_EQ(10, AZStd::any_cast(container).m_data); s_wasCalled = true; } void SetUp() override { AllocatorsFixture::SetUp(); s_wasCalled = false; m_behavior = aznew BehaviorContext(); m_behavior->Class("DataContainer") ->Constructor() ->Property("data", BehaviorValueProperty(&DataContainer::m_data)) ; m_behavior->Method("TestThatDataIs10", &TestThatDataIs10); m_behavior->Property("wasCalled", BehaviorValueGetter(&s_wasCalled), nullptr); m_script = aznew ScriptContext(); m_script->BindTo(m_behavior); } void TearDown() override { delete m_script; delete m_behavior; AllocatorsFixture::TearDown(); } BehaviorContext* m_behavior; ScriptContext* m_script; }; bool AnyScriptBindTest::s_wasCalled = false; TEST_F(AnyScriptBindTest, ScriptContextAny_AnyFromLua) { m_script->Execute("TestThatDataIs10(DataContainer(10))"); EXPECT_TRUE(s_wasCalled); } class BaseScriptTest : public AllocatorsFixture { public: void SetUp() override { AllocatorsFixture::SetUp(); m_behavior = aznew BehaviorContext(); m_behavior->Method("AZTestAssert", &AZTestAssert); SetupBehaviorContext(*m_behavior); m_script = aznew ScriptContext(); m_script->BindTo(m_behavior); } void TearDown() override { delete m_script; delete m_behavior; AllocatorsFixture::TearDown(); } BehaviorContext* m_behavior; ScriptContext* m_script; virtual void SetupBehaviorContext(BehaviorContext& bc) { (void)bc; } }; TEST_F(BaseScriptTest, Unreflect) { m_behavior->Class(); m_script->Execute("AZTestAssert(ScriptClass ~= nil)"); m_behavior->EnableRemoveReflection(); m_behavior->Class(); m_behavior->DisableRemoveReflection(); m_script->Execute("AZTestAssert(ScriptClass == nil)"); } class MathScriptTest : public BaseScriptTest { static void ScriptAssertEqual(double lhs, double rhs) { EXPECT_TRUE(AZ::IsClose(lhs, rhs, static_cast(g_fltEps))); } void SetupBehaviorContext(BehaviorContext& bc) override { MathReflect(&bc); bc.Method("ScriptAssertEqual", &ScriptAssertEqual); } }; TEST_F(MathScriptTest, ScriptMath_BasicRandomTests) { // tests pseudo random number generator m_script->Execute(R"LUA( local r = Random(); r = Random(4321); r:SetSeed(1212); ScriptAssertEqual(r:GetRandom(), 2783974736); ScriptAssertEqual(r:GetRandomFloat(), 0.61152875423431396); )LUA"); } class ScriptTypeidTest : public AllocatorsFixture { public: void SetUp() override { AllocatorsFixture::SetUp(); m_behavior = aznew BehaviorContext(); AZ::MathReflect(m_behavior); m_behavior->Class(); m_behavior->Method("AZTestAssert", &AZTestAssert); m_behavior->Method("CheckScriptClassTypeid", &CheckScriptTypeid); m_behavior->Method("CheckScriptBoolTypeid", &CheckScriptTypeid); m_behavior->Method("CheckScriptStringTypeid", &CheckScriptTypeid); m_behavior->Method("CheckScriptNumberTypeid", &CheckScriptTypeid); m_behavior->Method("CheckScriptTypeidNullptr", &CheckScriptTypeidNullptr); m_script = aznew ScriptContext(); m_script->BindTo(m_behavior); } void TearDown() override { delete m_script; delete m_behavior; AllocatorsFixture::TearDown(); } template static void CheckScriptTypeid(const AZ::Uuid& typeId) { EXPECT_EQ(azrtti_typeid(), typeId); } static void CheckScriptTypeidNullptr(const AZ::Uuid& typeId) { EXPECT_TRUE(typeId.IsNull()); } BehaviorContext* m_behavior = nullptr; ScriptContext* m_script = nullptr; }; TEST_F(ScriptTypeidTest, TypeId_Class) { m_script->Execute("CheckScriptClassTypeid(typeid(ScriptClass))"); m_script->Execute("local v = ScriptClass(); CheckScriptClassTypeid(typeid(v))"); } TEST_F(ScriptTypeidTest, TypeId_Bool) { m_script->Execute("CheckScriptBoolTypeid(typeid(true))"); } TEST_F(ScriptTypeidTest, TypeId_String) { m_script->Execute("CheckScriptStringTypeid(typeid(''))"); } TEST_F(ScriptTypeidTest, TypeId_Number) { m_script->Execute("CheckScriptNumberTypeid(typeid(1))"); } TEST_F(ScriptTypeidTest, TypeId_ArgMiscount) { m_script->Execute("CheckScriptTypeidNullptr(typeid())"); m_script->Execute("CheckScriptTypeidNullptr(typeid(1, 2, 3))"); } TEST_F(ScriptTypeidTest, TypeId_UnhandledType) { m_script->Execute("CheckScriptTypeidNullptr(typeid(function() end))"); m_script->Execute("CheckScriptTypeidNullptr(typeid({ }))"); } class ScriptCacheTableTest : public AllocatorsFixture { public: void SetUp() override { AllocatorsFixture::SetUp(); m_script = aznew ScriptContext(); m_lua = m_script->NativeContext(); } void TearDown() override { m_lua = nullptr; delete m_script; AllocatorsFixture::TearDown(); } ScriptContext* m_script = nullptr; lua_State* m_lua = nullptr; }; TEST_F(ScriptCacheTableTest, NilRef) { lua_pushnil(m_lua); EXPECT_EQ(LUA_REFNIL, Internal::LuaCacheValue(m_lua, -1)); lua_pop(m_lua, 1); Internal::LuaLoadCached(m_lua, LUA_REFNIL); EXPECT_TRUE(lua_isnil(m_lua, -1)); lua_pop(m_lua, 1); } TEST_F(ScriptCacheTableTest, InsertElement_RetreiveElement_IsSame) { lua_pushliteral(m_lua, "TEST_STRING"); int ref = Internal::LuaCacheValue(m_lua, -1); lua_pop(m_lua, 1); ASSERT_GT(ref, 0); Internal::LuaLoadCached(m_lua, ref); ASSERT_EQ(LUA_TSTRING, lua_type(m_lua, -1)); EXPECT_STREQ("TEST_STRING", lua_tostring(m_lua, -1)); lua_pop(m_lua, 1); Internal::LuaReleaseCached(m_lua, ref); } TEST_F(ScriptCacheTableTest, InsertElements_RemoveFromMiddle_RemoveFromEnd) { int refs[6] = { }; int removedRefs[] = { 2, 4, 5 }; // Create a bunch of references lua_pushinteger(m_lua, 42); for (int& ref : refs) { ref = Internal::LuaCacheValue(m_lua, -1); ASSERT_GT(ref, 0); } // All refs should be valid at the beginning for (const int& ref : refs) { Internal::LuaLoadCached(m_lua, ref); ASSERT_FALSE(lua_isnil(m_lua, -1)); EXPECT_EQ(42, lua_tointeger(m_lua, -1)); lua_pop(m_lua, 1); } // Remove some refs for (const int& refIdx : removedRefs) { Internal::LuaReleaseCached(m_lua, refs[refIdx]); refs[refIdx] = LUA_NOREF; } // All non-removed refs here should be valid for (const int& ref : refs) { // Skip removed refs if (ref == LUA_NOREF) { continue; } Internal::LuaLoadCached(m_lua, ref); ASSERT_FALSE(lua_isnil(m_lua, -1)); EXPECT_EQ(42, lua_tointeger(m_lua, -1)); lua_pop(m_lua, 1); } // Reinsert the refs for (const int& refIdx : removedRefs) { refs[refIdx] = Internal::LuaCacheValue(m_lua, -1); } // All refs should be valid again for (const int& ref : refs) { Internal::LuaLoadCached(m_lua, ref); ASSERT_FALSE(lua_isnil(m_lua, -1)); EXPECT_EQ(42, lua_tointeger(m_lua, -1)); lua_pop(m_lua, 1); } for (int& ref : refs) { Internal::LuaReleaseCached(m_lua, ref); ref = LUA_NOREF; } // Get cache table to ensure it's reset lua_rawgeti(m_lua, LUA_REGISTRYINDEX, AZ_LUA_WEAK_CACHE_TABLE_REF); lua_rawgeti(m_lua, -1, 2); // Get first free list index EXPECT_EQ(3, (int)lua_tointeger(m_lua, -1)) << "Free list index does not point to first assignable element"; lua_pop(m_lua, 1); lua_rawgeti(m_lua, -1, 3); // Get the first free element EXPECT_TRUE(lua_isnil(m_lua, -1)) << "First assignable element is not empty"; lua_pop(m_lua, 1); EXPECT_EQ(2u, lua_objlen(m_lua, -1)) << "Cache table contains more than just header"; lua_pop(m_lua, 1); } class UnregisteredSharedPointerTest : public BehaviorContextFixture { public: struct UnregisteredType { AZ_CLASS_ALLOCATOR(UnregisteredType, SystemAllocator, 0); AZ_TYPE_INFO(UnregisteredType, "{6C5C91A6-A9F6-470D-8841-179201A0D9E6}"); }; static AZStd::shared_ptr GetPtr() { return AZStd::make_shared(); } static void TestIsPtrNullptr(AZStd::shared_ptr ptr) { ASSERT_NE(nullptr, ptr); } void SetUp() override { BehaviorContextFixture::SetUp(); m_behaviorContext->Method("AZTestAssert", &AZTestAssert); m_behaviorContext->Method("GetPtr", &GetPtr); m_behaviorContext->Method("TestIsPtrNullptr", &TestIsPtrNullptr); m_script = aznew ScriptContext(); m_script->BindTo(m_behaviorContext); } void TearDown() override { delete m_script; BehaviorContextFixture::TearDown(); } ScriptContext* m_script = nullptr; }; TEST_F(UnregisteredSharedPointerTest, Test) { // Test that the pointer remains valid m_script->Execute(R"LUA( instance = GetPtr(); TestIsPtrNullptr(instance); AZTestAssert(instance:get() ~= nil); )LUA"); ScriptContext::ErrorHook errorHook = [](ScriptContext*, ScriptContext::ErrorType error, const char*) { ASSERT_GT(error, ScriptContext::ErrorType::Log); }; // Test that actually doing stuff with it alerts the user auto oldHook = m_script->GetErrorHook(); m_script->SetErrorHook(errorHook); m_script->Execute(R"LUA( instance:DoThing(); )LUA"); m_script->SetErrorHook(oldHook); } } #endif // #if !defined(AZCORE_EXCLUDE_LUA)