Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,165 @@
/*
* 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.
*
*/
#ifndef AZ_SCRIPT_COMPONENT_H
#define AZ_SCRIPT_COMPONENT_H
#include <AzCore/Script/ScriptAsset.h>
#include <AzCore/Script/ScriptContext.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/DynamicSerializableField.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <AzFramework/Network/NetBindable.h>
namespace AZ
{
class ScriptProperty;
}
namespace AzToolsFramework
{
namespace Components
{
class ScriptEditorComponent;
}
}
namespace AzFramework
{
class ScriptNetBindingTable;
struct ScriptCompileRequest;
using WriteFunction = AZStd::function< AZ::Outcome<void, AZStd::string>(const ScriptCompileRequest&, AZ::IO::GenericStream& in, AZ::IO::GenericStream& out) >;
struct ScriptCompileRequest
{
AZStd::string_view m_errorWindow;
AZStd::string_view m_sourceFile;
AZStd::string_view m_fullPath;
AZStd::string_view m_fileName;
AZStd::string_view m_tempDirPath;
AZ::IO::GenericStream* m_input = nullptr;
AZ::IO::GenericStream* m_output = nullptr;
WriteFunction m_prewriteCallback;
WriteFunction m_postwriteCallback;
AZStd::string m_destFileName;
AZStd::string m_destPath;
};
void ConstructScriptAssetPaths(ScriptCompileRequest& request);
AZ::Outcome<void, AZStd::string> CompileScript(ScriptCompileRequest& request);
AZ::Outcome<void, AZStd::string> CompileScriptAndAsset(ScriptCompileRequest& request);
AZ::Outcome<void, AZStd::string> CompileScript(ScriptCompileRequest& request, AZ::ScriptContext& context);
AZ::Outcome<AZStd::string, AZStd::string> CompileScriptAndSaveAsset(ScriptCompileRequest& request, bool writeAssetInfo = true);
struct ScriptPropertyGroup
{
AZ_TYPE_INFO(ScriptPropertyGroup, "{79682522-2f81-4b36-9fc2-a091c7504f7f}");
AZStd::string m_name;
AZStd::vector<AZ::ScriptProperty*> m_properties;
AZStd::vector<ScriptPropertyGroup> m_groups;
// Get the pointer to the specified group in m_groups. Returns nullptr if not found.
ScriptPropertyGroup* GetGroup(const char* groupName);
// Get the pointer to the specified property in m_properties. Returns nullptr if not found.
AZ::ScriptProperty* GetProperty(const char* propertyName);
// Remove all properties and groups
void Clear();
ScriptPropertyGroup() = default;
~ScriptPropertyGroup();
ScriptPropertyGroup(const ScriptPropertyGroup& rhs) = delete;
ScriptPropertyGroup& operator=(ScriptPropertyGroup&) = delete;
public:
ScriptPropertyGroup(ScriptPropertyGroup&& rhs) { *this = AZStd::move(rhs); }
ScriptPropertyGroup& operator=(ScriptPropertyGroup&& rhs);
};
class ScriptComponent
: public AZ::Component
, private AZ::Data::AssetBus::Handler
, public AzFramework::NetBindable
{
friend class AzToolsFramework::Components::ScriptEditorComponent;
public:
static const char* NetRPCFieldName;
static const char* DefaultFieldName;
AZ_COMPONENT(AzFramework::ScriptComponent, "{8D1BC97E-C55D-4D34-A460-E63C57CD0D4B}", NetBindable);
ScriptComponent();
~ScriptComponent();
AZ::ScriptContext* GetScriptContext() const { return m_context; }
void SetScriptContext(AZ::ScriptContext* context);
const AZ::Data::Asset<AZ::ScriptAsset>& GetScript() const { return m_script; }
void SetScript(const AZ::Data::Asset<AZ::ScriptAsset>& script);
protected:
ScriptComponent(const ScriptComponent&) = delete;
//////////////////////////////////////////////////////////////////////////
// Component base
void Init() override;
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AssetBus
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// NetBindable
GridMate::ReplicaChunkPtr GetNetworkBinding() override;
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk) override;
void UnbindFromNetwork() override;
//////////////////////////////////////////////////////////////////////////
/// Load script (unless already by other instances) and creates the script instance into the VM
void LoadScript();
/// Removes the script instance and unloads the script (unless needed by other instances)
void UnloadScript();
/// Loads the script into the context/VM, \returns true if the script is loaded
bool LoadInContext();
// Create script instance table.
void CreateEntityTable();
void DestroyEntityTable();
void CreateNetworkBindingTable(int baseTableIndex, int entityTableIndex);
void CreatePropertyGroup(const ScriptPropertyGroup& group, int prototypeParentIndex, int parentIndex, int metatableIndex, bool isRoot);
/// \red ComponentDescriptor::Reflect
static void Reflect(AZ::ReflectContext* reflection);
AZ::ScriptContext* m_context; ///< Context in which the script will be running
AZ::ScriptContextId m_contextId; ///< Id of the script context.
AZ::Data::Asset<AZ::ScriptAsset> m_script; ///< Reference to the script asset used for this component.
int m_table; ///< Cached table index
ScriptPropertyGroup m_properties; ///< List with all properties that were tweaked in the editor and should override values in the m_scourceScriptName class inside m_script.
ScriptNetBindingTable* m_netBindingTable; ///< Table that will hold our networked script values, and manage callbacks
};
} // namespace AZ
#endif // AZ_SCRIPT_COMPONENTH_
#pragma once
@@ -0,0 +1,37 @@
/*
* 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.
*
*/
#ifndef SCRIPT_DEBUG_AGENT_BUS_H
#define SCRIPT_DEBUG_AGENT_BUS_H
#include <AzCore/EBus/EBus.h>
#include <AzCore/Script/ScriptContextDebug.h>
namespace AzFramework
{
/*
* The script debug agent, if available, will listen on this bus.
*/
class ScriptDebugAgentEvents
: public AZ::EBusTraits
{
public:
virtual ~ScriptDebugAgentEvents() {}
virtual void RegisterContext(AZ::ScriptContext* sc, const char* name) = 0; // Tells the agent that a new script context is available for debugging.
virtual void UnregisterContext(AZ::ScriptContext* sc) = 0; // Remove a script context from the agent's list.
};
typedef AZ::EBus<ScriptDebugAgentEvents> ScriptDebugAgentBus;
} // namespace HExFramework
#endif
#pragma once
@@ -0,0 +1,106 @@
/*
* 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 "ScriptDebugMsgReflection.h"
#include <AzCore/Component/Component.h>
#include <AzCore/Serialization/SerializeContext.h>
namespace AzFramework
{
void ReflectScriptDebugClasses(AZ::ReflectContext* reflection)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
if (serializeContext)
{
serializeContext->Class<AZ::ScriptContextDebug::DebugValue>()
->Field("name", &AZ::ScriptContextDebug::DebugValue::m_name)
->Field("value", &AZ::ScriptContextDebug::DebugValue::m_value)
->Field("type", &AZ::ScriptContextDebug::DebugValue::m_type)
->Field("flags", &AZ::ScriptContextDebug::DebugValue::m_flags)
->Field("elements", &AZ::ScriptContextDebug::DebugValue::m_elements);
serializeContext->Class<ScriptUserMethodInfo>()
->Field("name", &ScriptUserMethodInfo::m_name)
->Field("info", &ScriptUserMethodInfo::m_dbgParamInfo);
serializeContext->Class<ScriptUserPropertyInfo>()
->Field("name", &ScriptUserPropertyInfo::m_name)
->Field("isRead", &ScriptUserPropertyInfo::m_isRead)
->Field("isWrite", &ScriptUserPropertyInfo::m_isWrite);
serializeContext->Class<ScriptUserClassInfo>()
->Field("name", &ScriptUserClassInfo::m_name)
->Field("type", &ScriptUserClassInfo::m_typeId)
->Field("methods", &ScriptUserClassInfo::m_methods)
->Field("properties", &ScriptUserClassInfo::m_properties);
serializeContext->Class<ScriptUserEBusMethodInfo, ScriptUserMethodInfo>()
->Field("category", &ScriptUserEBusMethodInfo::m_category);
serializeContext->Class<ScriptUserEBusInfo>()
->Field("name", &ScriptUserEBusInfo::m_name)
->Field("events", &ScriptUserEBusInfo::m_events)
->Field("canBroadcast", &ScriptUserEBusInfo::m_canBroadcast)
->Field("canQueue", &ScriptUserEBusInfo::m_canQueue)
->Field("hasHandler", &ScriptUserEBusInfo::m_hasHandler);
serializeContext->Class<ScriptDebugRequest, TmMsg>()
->Field("request", &ScriptDebugRequest::m_request)
->Field("context", &ScriptDebugRequest::m_context);
serializeContext->Class<ScriptDebugBreakpointRequest, ScriptDebugRequest>()
->Field("line", &ScriptDebugBreakpointRequest::m_line);
serializeContext->Class<ScriptDebugSetValue, TmMsg>()
->Field("value", &ScriptDebugSetValue::m_value);
serializeContext->Class<ScriptDebugAck, TmMsg>()
->Field("request", &ScriptDebugAck::m_request)
->Field("ackCode", &ScriptDebugAck::m_ackCode);
serializeContext->Class<ScriptDebugAckBreakpoint, TmMsg>()
->Field("id", &ScriptDebugAckBreakpoint::m_id)
->Field("moduleName", &ScriptDebugAckBreakpoint::m_moduleName)
->Field("line", &ScriptDebugAckBreakpoint::m_line);
serializeContext->Class<ScriptDebugAckExecute, TmMsg>()
->Field("moduleName", &ScriptDebugAckExecute::m_moduleName)
->Field("result", &ScriptDebugAckExecute::m_result);
serializeContext->Class<ScriptDebugEnumLocalsResult, TmMsg>()
->Field("names", &ScriptDebugEnumLocalsResult::m_names);
serializeContext->Class<ScriptDebugEnumContextsResult, TmMsg>()
->Field("names", &ScriptDebugEnumContextsResult::m_names);
serializeContext->Class<ScriptDebugGetValueResult, TmMsg>()
->Field("value", &ScriptDebugGetValueResult::m_value);
serializeContext->Class<ScriptDebugSetValueResult, TmMsg>()
->Field("name", &ScriptDebugSetValueResult::m_name)
->Field("result", &ScriptDebugSetValueResult::m_result);
serializeContext->Class<ScriptDebugCallStackResult, TmMsg>()
->Field("callstack", &ScriptDebugCallStackResult::m_callstack);
serializeContext->Class<ScriptDebugRegisteredGlobalsResult, TmMsg>()
->Field("methods", &ScriptDebugRegisteredGlobalsResult::m_methods)
->Field("properties", &ScriptDebugRegisteredGlobalsResult::m_properties);
serializeContext->Class<ScriptDebugRegisteredClassesResult, TmMsg>()
->Field("classes", &ScriptDebugRegisteredClassesResult::m_classes);
serializeContext->Class<ScriptDebugRegisteredEBusesResult, TmMsg>()
->Field("EBusses", &ScriptDebugRegisteredEBusesResult::m_ebusList);
}
}
} // namespace AzFramework
@@ -0,0 +1,236 @@
/*
* 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.
*
*/
#ifndef HEXFRAMEWORK_SCRIPT_DEBUGGER_CLASSES_H
#define HEXFRAMEWORK_SCRIPT_DEBUGGER_CLASSES_H
#include <AzFramework/Script/ScriptRemoteDebugging.h>
#include <AzFramework/TargetManagement/TargetManagementAPI.h>
#include <AzCore/Script/ScriptContextDebug.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/RTTI/RTTI.h>
namespace DH {
struct ClassDataReflection;
} // namespace DH
namespace AzFramework
{
class ScriptDebugRequest
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugRequest, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugRequest, "{2137E01A-F2AE-4137-A17E-6B82F3B7E4DE}", TmMsg);
ScriptDebugRequest()
: TmMsg(AZ_CRC("ScriptDebugAgent", 0xb6be0836)) {}
ScriptDebugRequest(AZ::u32 request)
: TmMsg(AZ_CRC("ScriptDebugAgent", 0xb6be0836))
, m_request(request) {}
ScriptDebugRequest(AZ::u32 request, const char* context)
: TmMsg(AZ_CRC("ScriptDebugAgent", 0xb6be0836))
, m_request(request)
, m_context(context) {}
AZ::u32 m_request;
AZStd::string m_context;
};
class ScriptDebugBreakpointRequest
: public ScriptDebugRequest
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugBreakpointRequest, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugBreakpointRequest, "{707F97AB-1CA0-4191-82E0-FFE9C9D0F788}", ScriptDebugRequest);
ScriptDebugBreakpointRequest() {}
ScriptDebugBreakpointRequest(AZ::u32 request, const char* context, AZ::u32 line)
: ScriptDebugRequest(request, context)
, m_line(line) {}
AZ::u32 m_line;
};
class ScriptDebugSetValue
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugSetValue, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugSetValue, "{11E0E012-BD54-457D-A44B-FDDA55736ED3}", TmMsg);
ScriptDebugSetValue()
: TmMsg(AZ_CRC("ScriptDebugAgent", 0xb6be0836)) {}
AZ::ScriptContextDebug::DebugValue m_value;
};
class ScriptDebugAck
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugAck, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugAck, "{0CA1671A-BAFD-499C-B2CD-7B7E3DD5E2A8}", TmMsg);
ScriptDebugAck(AZ::u32 request = 0, AZ::u32 ackCode = 0)
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e))
, m_request(request)
, m_ackCode(ackCode)
{}
AZ::u32 m_request;
AZ::u32 m_ackCode;
};
class ScriptDebugAckBreakpoint
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugAckBreakpoint, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugAckBreakpoint, "{D9644B8A-92FD-43B6-A579-77E123A72EC2}", TmMsg);
ScriptDebugAckBreakpoint()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
AZ::u32 m_id;
AZStd::string m_moduleName;
AZ::u32 m_line;
};
class ScriptDebugAckExecute
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugAckExecute, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugAckExecute, "{F5B24F7E-85DA-4FE8-B720-AABE35CE631D}", TmMsg);
ScriptDebugAckExecute()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
AZStd::string m_moduleName;
bool m_result;
};
class ScriptDebugEnumLocalsResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugEnumLocalsResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugEnumLocalsResult, "{201701DD-0B74-4886-AB84-93BDB338A8DD}", TmMsg);
ScriptDebugEnumLocalsResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
AZStd::vector<AZStd::string> m_names;
};
class ScriptDebugEnumContextsResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugEnumContextsResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugEnumContextsResult, "{8CE74569-9B7D-4993-AFE8-38BB8CE419F5}", TmMsg);
ScriptDebugEnumContextsResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
AZStd::vector<AZStd::string> m_names;
};
class ScriptDebugGetValueResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugGetValueResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugGetValueResult, "{B10720F1-B8FE-476F-A39D-6E80711580FD}", TmMsg);
ScriptDebugGetValueResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
AZ::ScriptContextDebug::DebugValue m_value;
};
class ScriptDebugSetValueResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugSetValueResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugSetValueResult, "{2E2BD168-1805-43D6-8602-FDE14CED8E53}", TmMsg);
ScriptDebugSetValueResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
AZStd::string m_name;
bool m_result;
};
class ScriptDebugCallStackResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugCallStackResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugCallStackResult, "{B2606AC6-F966-4991-8144-BA6117F4A54E}", TmMsg);
ScriptDebugCallStackResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
AZStd::string m_callstack;
};
class ScriptDebugRegisteredGlobalsResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugRegisteredGlobalsResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugRegisteredGlobalsResult, "{CEE4E889-0249-4D59-9D56-CD4BD159E411}", TmMsg);
ScriptDebugRegisteredGlobalsResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
ScriptUserMethodList m_methods;
ScriptUserPropertyList m_properties;
};
class ScriptDebugRegisteredClassesResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugRegisteredClassesResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugRegisteredClassesResult, "{7DF455AB-9AB1-4A95-B906-5DB1D1087EBB}", TmMsg);
ScriptDebugRegisteredClassesResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
ScriptUserClassList m_classes;
};
class ScriptDebugRegisteredEBusesResult
: public TmMsg
{
public:
AZ_CLASS_ALLOCATOR(ScriptDebugRegisteredEBusesResult, AZ::SystemAllocator, 0);
AZ_RTTI(ScriptDebugRegisteredEBusesResult, "{D2B5D77C-09F3-476D-A611-49B0A1B9EDFB}", TmMsg);
ScriptDebugRegisteredEBusesResult()
: TmMsg(AZ_CRC("ScriptDebugger", 0xf8ab685e)) {}
ScriptUserEBusList m_ebusList;
};
void ReflectScriptDebugClasses(AZ::ReflectContext* reflection);
} // namespace AzFramework
#endif // HEXFRAMEWORK_SCRIPT_DEBUGGER_CLASSES_H
#pragma once
@@ -0,0 +1,573 @@
/*
* 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 <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/string/string.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/DataMarshal.h>
#include <GridMate/Serialize/UuidMarshal.h>
#include <GridMate/Serialize/ContainerMarshal.h>
#include <AzFramework/Script/ScriptNetBindings.h>
#include <AzFramework/Network/DynamicSerializableFieldMarshaler.h>
#include <AzFramework/Network/EntityIdMarshaler.h>
#include "AzFramework/Script/ScriptMarshal.h"
namespace AzFramework
{
////////////////////////////
// ScriptPropertyMarshaler
////////////////////////////
template<class T>
bool UnmarshalGenericType(AZ::DynamicSerializableField& serializableField, GridMate::ReadBuffer& rb)
{
bool valueChanged = true;
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
// Store the old value, to compare with the unmarshaled value, to signal
T oldValue = (*serializableField.Get<T>());
serializableFieldMarshaler.Unmarshal(serializableField,rb);
// If our type hasn't changed, compare the values.
if (serializableField.m_typeId == T::TYPEINFO_Uuid())
{
valueChanged = !(oldValue == (*serializableField.Get<T>()));
}
return valueChanged;
}
class ScriptPropertyTableMarshalerHelper
{
public:
template<typename T>
static void MarshalScriptPropertyGenericMap(const ScriptPropertyMarshaler& scriptPropertyMarshaler, GridMate::WriteBuffer& wb, const AZ::ScriptPropertyTable* scriptPropertyTable)
{
GridMate::Marshaler<AZ::u32> sizeMarshaler;
auto mapIter = scriptPropertyTable->m_genericMapping.find(T::TYPEINFO_Uuid());
if (mapIter != scriptPropertyTable->m_genericMapping.end())
{
AZ::ScriptPropertyGenericClassMapImpl<T>* genericClassKeyMap = static_cast<AZ::ScriptPropertyGenericClassMapImpl<T>*>(mapIter->second);
auto& valueMap = genericClassKeyMap->GetPairMapping();
// We will write out all of our keys. Since it is easier to write out nil values for the properties.
sizeMarshaler.Marshal(wb,static_cast<AZ::u32>(valueMap.size()));
GridMate::Marshaler<T> keyMarshaler;
for (auto& mapPair : valueMap)
{
keyMarshaler.Marshal(wb,mapPair.first);
scriptPropertyMarshaler.Marshal(wb,mapPair.second.m_valueProperty);
}
}
else
{
sizeMarshaler.Marshal(wb,0);
}
}
template<typename T>
static bool UnmarshalScriptPropertyGenericMap(const ScriptPropertyMarshaler& scriptPropertyMarshaler, AZ::ScriptPropertyTable* scriptPropertyTable, GridMate::ReadBuffer& rb)
{
bool valueChanged = false;
AZ::SerializeContext* useContext = nullptr;
EBUS_EVENT_RESULT(useContext, AZ::ComponentApplicationBus, GetSerializeContext);
if (useContext)
{
const AZ::SerializeContext::ClassData* classData = useContext->FindClassData(T::TYPEINFO_Uuid());
if (classData && classData->m_factory)
{
auto mapIter = scriptPropertyTable->m_genericMapping.find(T::TYPEINFO_Uuid());
if (mapIter != scriptPropertyTable->m_genericMapping.end())
{
// This whole thing is an in-place map update.
// to try to minimize the number of allocations. We try to re-use objects as much as possible.
//
// Two phase approach: Step one, update all of the existing properties, while keeping track of all of the used keys.
// Step two, go through and delete any unupdated keys from the mapping.
AZ::ScriptPropertyGenericClassMapImpl<T>* genericClassKeyMap = static_cast<AZ::ScriptPropertyGenericClassMapImpl<T>*>(mapIter->second);
AZStd::unordered_set<T> newKeys;
GridMate::Marshaler<AZ::u32> sizeMarshaler;
AZ::u32 mapSize;
sizeMarshaler.Unmarshal(mapSize,rb);
auto& valueMap = genericClassKeyMap->GetPairMapping();
GridMate::Marshaler<T> keyMarshaler;
for (unsigned int i=0; i < mapSize; ++i)
{
T propertyKey;
keyMarshaler.Unmarshal(propertyKey,rb);
newKeys.insert(propertyKey);
auto valueIter = valueMap.find(propertyKey);
if (valueIter != valueMap.end())
{
if (scriptPropertyMarshaler.UnmarshalToPointer(valueIter->second.m_valueProperty,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* newValueProperty = nullptr;
scriptPropertyMarshaler.UnmarshalToPointer(newValueProperty,rb);
AZ::ScriptPropertyGenericClassMap::MapValuePair newPair;
newPair.m_valueProperty = newValueProperty;
T* serializableData = nullptr;
serializableData = static_cast<T*>(classData->m_factory->Create("ScriptProperty"));
(*serializableData) = propertyKey;
AZ::ScriptPropertyGenericClass* genericPropertyClass = aznew AZ::ScriptPropertyGenericClass();
genericPropertyClass->Set<T>(serializableData);
newPair.m_keyProperty = genericPropertyClass;
valueMap.emplace(propertyKey,newPair);
}
}
// Delete all of the unused keyes from the map
auto valueIter = valueMap.begin();
while (valueIter != valueMap.end())
{
if (newKeys.find(valueIter->first) == newKeys.end())
{
valueChanged = true;
valueIter->second.Destroy();
valueIter = valueMap.erase(valueIter);
}
else
{
++valueIter;
}
}
}
}
}
return valueChanged;
}
};
void ScriptPropertyMarshaler::Marshal(GridMate::WriteBuffer& wb, AZ::ScriptProperty*const& property) const
{
GridMate::Marshaler<AZ::Uuid> typeMarshaler;
GridMate::Marshaler<AZ::u64> idMarshaler;
GridMate::Marshaler<AZStd::string> nameMarshaler;
if (property == nullptr)
{
// Write out a nil property if we have a nullptr property
nameMarshaler.Marshal(wb,"");
idMarshaler.Marshal(wb,0);
typeMarshaler.Marshal(wb,AZ::ScriptPropertyNil::RTTI_Type());
return;
}
// Common points:
// Always going to marshal the uuid of the type(or something similar)
// so we know what type we have on the other side.
//
// Next need to pass along the name field.
const AZ::Uuid& typeId = azrtti_typeid(property);
nameMarshaler.Marshal(wb,property->m_name);
idMarshaler.Marshal(wb,property->m_id);
// Method 1:
// - Allow each ScriptProperty to marshal itself.
// - Currently unavailable since the ScriptProperties live in AZCore
// and the WriteBuffer is in GridMate.
// cont.Marshal(wb);
// Method 2:
// - Process all of our known marshallable types and use the appropriate marshaler
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<bool> boolMarshaler;
boolMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyBoolean*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<double> doubleMarshaler;
doubleMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyNumber*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Marshal(wb,static_cast<const AZ::ScriptPropertyString*>(property)->m_value);
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
const AZ::DynamicSerializableField& serializableField = static_cast<const AZ::ScriptPropertyGenericClass*>(property)->GetSerializableField();
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
serializableFieldMarshaler.Marshal(wb,serializableField);
}
else if (typeId == AZ::ScriptPropertyTable::TYPEINFO_Uuid())
{
const AZ::ScriptPropertyTable* scriptPropertyTable = static_cast<const AZ::ScriptPropertyTable*>(property);
typeMarshaler.Marshal(wb,typeId);
GridMate::Marshaler<AZ::u32> mapSizeMarshaler;
mapSizeMarshaler.Marshal(wb,static_cast<AZ::u32>(scriptPropertyTable->m_indexMapping.size()));
GridMate::Marshaler<int> indexMarshaler;
// Currently only support integers as keys inside of the table.
for (auto& mapPair : scriptPropertyTable->m_indexMapping)
{
indexMarshaler.Marshal(wb,mapPair.first);
this->Marshal(wb,mapPair.second);
}
mapSizeMarshaler.Marshal(wb, static_cast<AZ::u32>(scriptPropertyTable->m_keyMapping.size()));
GridMate::Marshaler<AZ::u32> hashMarshaler;
for (auto& mapPair : scriptPropertyTable->m_keyMapping)
{
// For hashed values. The name of the script property is the same as the hash it should be using.
// We still synchronize the Crc so we can unmarshal in place on the other side.
hashMarshaler.Marshal(wb,mapPair.first);
Marshal(wb,mapPair.second);
}
// EntityId's
ScriptPropertyTableMarshalerHelper::MarshalScriptPropertyGenericMap<AZ::EntityId>((*this), wb, scriptPropertyTable);
}
else
{
typeMarshaler.Marshal(wb,AZ::ScriptPropertyNil::RTTI_Type());
}
}
bool ScriptPropertyMarshaler::UnmarshalToPointer(AZ::ScriptProperty*& target, GridMate::ReadBuffer& rb) const
{
bool typeChanged = false;
AZ::Uuid typeId;
AZ::u64 id;
AZStd::string name;
GridMate::Marshaler<AZ::Uuid> typeMarshaler;
GridMate::Marshaler<AZ::u64> idMarshaler;
GridMate::Marshaler<AZStd::string> nameMarshaler;
nameMarshaler.Unmarshal(name,rb);
idMarshaler.Unmarshal(id,rb);
typeMarshaler.Unmarshal(typeId,rb);
if (target == nullptr || typeId != azrtti_typeid(target))
{
typeChanged = true;
AZ::ScriptProperty* actualScriptProperty = nullptr;
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyBoolean();
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyNumber();
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyString();
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyGenericClass();
}
else if (typeId == AZ::ScriptPropertyTable::RTTI_Type())
{
actualScriptProperty = aznew AZ::ScriptPropertyTable();
}
else
{
actualScriptProperty = aznew AZ::ScriptPropertyNil();
}
actualScriptProperty->m_name = name;
delete target;
target = actualScriptProperty;
}
// Update our ID
target->m_id = id;
// Method 1:
// - Allow each ScriptProperty to unmarshal itself
// - Currently unavailable since the ScriptProperties live in AZCore
// and the WriteBuffer is in GridMate
// actualScriptProperty->Unmarshal(rb);
//
// Method 2:
// - Process all of our known marshallable types and use the appropriate marshaler
bool valueChanged = false;
if (typeId == AZ::ScriptPropertyBoolean::RTTI_Type())
{
AZ::ScriptPropertyBoolean* booleanProperty = static_cast<AZ::ScriptPropertyBoolean*>(target);
bool oldValue = booleanProperty->m_value;
GridMate::Marshaler<bool> boolMarshaler;
boolMarshaler.Unmarshal(booleanProperty->m_value,rb);
valueChanged = !(oldValue == booleanProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyString::RTTI_Type())
{
AZ::ScriptPropertyString* stringProperty = static_cast<AZ::ScriptPropertyString*>(target);
AZStd::string oldValue = stringProperty->m_value;
GridMate::Marshaler<AZStd::string> stringMarshaler;
stringMarshaler.Unmarshal(stringProperty->m_value,rb);
valueChanged = !(oldValue == stringProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyNumber::RTTI_Type())
{
AZ::ScriptPropertyNumber* numberProperty = static_cast<AZ::ScriptPropertyNumber*>(target);
double oldValue = numberProperty->m_value;
GridMate::Marshaler<double> numberMarshaler;
numberMarshaler.Unmarshal(numberProperty->m_value,rb);
valueChanged = !(oldValue == numberProperty->m_value);
}
else if (typeId == AZ::ScriptPropertyGenericClass::RTTI_Type())
{
AZ::ScriptPropertyGenericClass* genericProperty = static_cast<AZ::ScriptPropertyGenericClass*>(target);
AZ::DynamicSerializableField& serializableField = genericProperty->m_value;
AZ::DynamicSerializableField oldField;
oldField.CopyDataFrom(serializableField);
GridMate::Marshaler<AZ::DynamicSerializableField> serializableFieldMarshaler;
serializableFieldMarshaler.Unmarshal(serializableField,rb);
// If our type hasn't changed, compare the values.
valueChanged = !oldField.IsEqualTo(serializableField);
}
else if (typeId == AZ::ScriptPropertyTable::RTTI_Type())
{
AZ::ScriptPropertyTable* scriptPropertyTable = static_cast<AZ::ScriptPropertyTable*>(target);
GridMate::Marshaler<AZ::u32> mapSizeMarshaler;
// Unmarshal all of the indexes properties
{
AZ::u32 mapSize = 0;
mapSizeMarshaler.Unmarshal(mapSize, rb);
AZStd::unordered_set<int> newIndexes;
GridMate::Marshaler<int> indexMarshaler;
for (AZ::u32 i=0; i < mapSize; ++i)
{
int index = 0;
indexMarshaler.Unmarshal(index,rb);
auto mapIter = scriptPropertyTable->m_indexMapping.find(index);
if (mapIter != scriptPropertyTable->m_indexMapping.end())
{
if (UnmarshalToPointer(mapIter->second,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* scriptProperty = nullptr;
UnmarshalToPointer(scriptProperty,rb);
auto insertResult = scriptPropertyTable->m_indexMapping.emplace(index,scriptProperty);
mapIter = insertResult.first;
}
if (mapIter->second == nullptr || azrtti_istypeof<AZ::ScriptPropertyNil>(mapIter->second))
{
valueChanged = true;
delete mapIter->second;
scriptPropertyTable->m_indexMapping.erase(mapIter);
}
else
{
newIndexes.insert(index);
}
}
auto mapIter = scriptPropertyTable->m_indexMapping.begin();
while (mapIter != scriptPropertyTable->m_indexMapping.end())
{
if (newIndexes.find(mapIter->first) == newIndexes.end())
{
valueChanged = true;
delete mapIter->second;
mapIter = scriptPropertyTable->m_indexMapping.erase(mapIter);
}
else
{
++mapIter;
}
}
}
// Unmarshal all of the hashed values
{
AZ::u32 mapSize = 0;
mapSizeMarshaler.Unmarshal(mapSize, rb);
AZStd::unordered_set<AZ::u32> newHashes;
GridMate::Marshaler<AZ::u32> hashMarshaler;
for (AZ::u32 i=0; i < mapSize; ++i)
{
AZ::u32 newHash;
hashMarshaler.Unmarshal(newHash, rb);
auto mapIter = scriptPropertyTable->m_keyMapping.find(newHash);
if (mapIter != scriptPropertyTable->m_keyMapping.end())
{
if (UnmarshalToPointer(mapIter->second,rb))
{
valueChanged = true;
}
}
else
{
valueChanged = true;
AZ::ScriptProperty* scriptProperty = nullptr;
UnmarshalToPointer(scriptProperty,rb);
auto emplaceResult = scriptPropertyTable->m_keyMapping.emplace(newHash,scriptProperty);
mapIter = emplaceResult.first;
}
if (mapIter->second == nullptr || azrtti_istypeof<AZ::ScriptPropertyNil>(mapIter->second))
{
valueChanged = true;
delete mapIter->second;
scriptPropertyTable->m_keyMapping.erase(mapIter);
}
else
{
newHashes.insert(newHash);
}
}
auto mapIter = scriptPropertyTable->m_keyMapping.begin();
while (mapIter != scriptPropertyTable->m_keyMapping.end())
{
if (newHashes.find(mapIter->first) == newHashes.end())
{
valueChanged = true;
delete mapIter->second;
mapIter = scriptPropertyTable->m_keyMapping.erase(mapIter);
}
else
{
++mapIter;
}
}
}
// Unmarshal all of the generic properties
// EntityId's
if (ScriptPropertyTableMarshalerHelper::UnmarshalScriptPropertyGenericMap<AZ::EntityId>((*this), scriptPropertyTable, rb))
{
valueChanged = true;
}
}
return typeChanged || valueChanged;
}
////////////////////////////
// ScriptPropertyThrottler
////////////////////////////
ScriptPropertyThrottler::ScriptPropertyThrottler()
: m_isDirty(true)
{
}
void ScriptPropertyThrottler::SignalDirty()
{
m_isDirty = true;
}
bool ScriptPropertyThrottler::WithinThreshold(AZ::ScriptProperty* newValue) const
{
return newValue == nullptr || !m_isDirty;
}
void ScriptPropertyThrottler::UpdateBaseline(AZ::ScriptProperty* baseline)
{
(void)baseline;
m_isDirty = false;
}
}
@@ -0,0 +1,94 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_SCRIPT_SCRIPTMARSHAL_H
#define AZFRAMEWORK_SCRIPT_SCRIPTMARSHAL_H
#include <GridMate/Serialize/ContainerMarshal.h>
#include <AzCore/RTTI/BehaviorObjectSignals.h>
namespace AZ
{
class ScriptProperty;
}
namespace AzFramework
{
/**
* Specalized helper marshaler for ScriptProperty class
*/
class ScriptPropertyMarshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, AZ::ScriptProperty*const& cont) const;
bool UnmarshalToPointer(AZ::ScriptProperty*& target, GridMate::ReadBuffer& rb) const;
};
class ScriptPropertyThrottler
{
public:
ScriptPropertyThrottler();
void SignalDirty();
bool WithinThreshold(AZ::ScriptProperty* newValue) const;
void UpdateBaseline(AZ::ScriptProperty* baseline);
private:
bool m_isDirty;
};
/**
* Specialized helper marshaler to help with the vector creation/destruction
*/
class ScriptRPCMarshaler
{
public:
typedef AZStd::vector< AZ::ScriptProperty* > Container;
ScriptRPCMarshaler()
{
}
AZ_FORCE_INLINE void Marshal(GridMate::WriteBuffer& wb, const Container& container) const
{
AZ_Assert(container.size() < USHRT_MAX, "Container has too many elements for marshaling!");
AZ::u16 size = static_cast<AZ::u16>(container.size());
wb.Write(size);
for (const auto& i : container)
{
m_marshaler.Marshal(wb, i);
}
}
AZ_FORCE_INLINE void Unmarshal(Container& container, GridMate::ReadBuffer& rb) const
{
container.clear();
AZ::u16 size;
rb.Read(size);
container.reserve(size);
for (AZ::u16 i = 0; i < size; ++i)
{
AZ::ScriptProperty* readProperty = nullptr;
m_marshaler.UnmarshalToPointer(readProperty, rb);
container.insert(container.end(), readProperty);
}
}
protected:
ScriptPropertyMarshaler m_marshaler;
};
}
#endif
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,314 @@
/*
* 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.
*
*/
#ifndef AZFRAMEWORK_SCRIPT_NET_BINDINGS_H
#define AZFRAMEWORK_SCRIPT_NET_BINDINGS_H
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <GridMate/Replica/ReplicaChunkInterface.h>
#include <GridMate/Replica/DataSet.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <GridMate/Replica/RemoteProcedureCall.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/Script/ScriptPropertyTable.h>
#include <AzCore/Script/ScriptPropertyWatcherBus.h>
#include <AzFramework/Script/ScriptMarshal.h>
namespace AzFramework
{
class ScriptPropertyDataSet;
class ScriptComponentReplicaChunk;
// ScriptNetBindingTable will act as the go between for the ScriptComponent and the Replica's.
// It will also allow for holding of values in the case where you haven't been bound to a replica chunk yet and the
// script tries to interact with something that is networked.
//
// Allows for scripts to be re-used seamlessly in a offline vs online scenario(and support for going from offline to online),
// including RPCs(will alawys call the master version if offline)
class ScriptNetBindingTable
: public GridMate::ReplicaChunkInterface
{
private:
friend class ScriptComponentReplicaChunk;
friend class ScriptPropertyDataSet;
// Helper struct to keep track of a a ScriptConctext
// and the entityTableReference. Mainly used for
// calling in to functions in LUA where we want
// to push in the table reference as the first parameter
struct EntityScriptContext
{
public:
EntityScriptContext();
void Unload();
bool HasEntityTableRegistryIndex() const;
int GetEntityTableRegistryIndex() const;
bool HasScriptContext() const;
AZ::ScriptContext* GetScriptContext() const;
void ConfigureContext(AZ::ScriptContext* scriptContext, int entityTableRegistryIndex);
private:
bool SanityCheckContext() const;
AZ::ScriptContext* m_scriptContext;
int m_entityTableRegistryIndex;
};
class NetworkedTableValue;
friend NetworkedTableValue;
typedef AZStd::unordered_map<AZStd::string, NetworkedTableValue> NetworkedTableMap;
class RPCBindingHelper;
friend RPCBindingHelper;
typedef AZStd::unordered_map<AZStd::string, RPCBindingHelper> RPCHelperMap;
// Helper class that will wrap up our interactions with the actual stored value
// to hide the general use case of if we are connected to a replica or not.
//
// Additionally this will serve as a holding ground for a 'networked'
// value that doesn't have a dataset.
//
// Lastly holds onto the Callback references.
class NetworkedTableValue
{
public:
AZ_CLASS_ALLOCATOR(NetworkedTableValue, AZ::SystemAllocator, 0);
NetworkedTableValue(AZ::ScriptProperty* initialValue = nullptr);
~NetworkedTableValue();
void Destroy();
// Methods to register this value to a chunk
bool HasDataSet() const;
void RegisterDataSet(ScriptPropertyDataSet* dataSet);
void UnbindFromDataSet();
ScriptPropertyDataSet* GetDataSet() const;
// Information kept in order to force these values to use a particular dataset for debugging.
bool HasForcedDataSetIndex() const;
void SetForcedDataSetIndex(int index);
int GetForcedDataSetIndex() const;
// Callback functions
bool HasCallback() const;
void RegisterCallback(int functionReference);
void ReleaseCallback(AZ::ScriptContext& scriptContext);
void InvokeCallback(EntityScriptContext& scriptContext, const GridMate::TimeContext& timeContext);
bool AssignValue(AZ::ScriptDataContext& scriptDataContext, const AZStd::string& propertyName);
bool InspectValue(AZ::ScriptContext* scriptContext) const;
private:
// This value will be used if we have a networked property, but don't have a valid chunk yet.
// Works as a temporary store, which will be resolved once we get assigned to a DataSet
AZ::ScriptProperty* m_shimmedScriptProperty;
// The data set we are bound to
ScriptPropertyDataSet* m_dataSet;
int m_forcedDataSetIndex;
int m_functionReference;
};
// Future thoughts
// - Move the actual RPC meta table creation
// into this guy
class RPCBindingHelper
{
public:
AZ_CLASS_ALLOCATOR(RPCBindingHelper, AZ::SystemAllocator, 0);
RPCBindingHelper();
~RPCBindingHelper();
void ReleaseTableIndex(AZ::ScriptContext& scriptContext);
bool IsValid() const;
void SetMasterFunction(int masterReference);
bool InvokeMaster(EntityScriptContext& entityScriptContext, const ScriptRPCMarshaler::Container& params);
void SetProxyFunction(int masterReference);
void InvokeProxy(EntityScriptContext& entityScriptContext, const ScriptRPCMarshaler::Container& params);
private:
int m_masterReference;
int m_proxyReference;
};
public:
AZ_CLASS_ALLOCATOR(ScriptNetBindingTable, AZ::SystemAllocator, 0);
static void Reflect(AZ::ReflectContext* reflect);
ScriptNetBindingTable();
~ScriptNetBindingTable();
void Unload();
void CreateNetworkBindingTable(AZ::ScriptContext* scriptContext, int baseTableIndex, int entityTableIndex);
void FinalizeNetworkTable(AZ::ScriptContext* scriptContext, int entityTableRegistryIndex);
AZ::ScriptContext* GetScriptContext() const;
bool IsMaster() const;
//////////////////////////////////////////////////////////////////////////////////////////////////////
// DataSet Functionality
//
// Called when the script wants to bind a function callback to when
// a value changes
//
// Might change this to just be register DataSet
bool RegisterDataSet(AZ::ScriptDataContext& stackContext, AZ::ScriptProperty* scriptProperty);
// Called when the script wants to assign a value to the script value
bool AssignTableValue(AZ::ScriptDataContext& stackContext);
// Called when the script wants to know the value of a script value.
bool InspectTableValue(AZ::ScriptDataContext& stackContext) const;
//////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////
/// RPC Functionality
void RegisterRPC(AZ::ScriptDataContext& rpcTableContext, const AZStd::string& rpcName, int elementIndex, int tableStackIndex);
bool InvokeRPC(AZ::ScriptDataContext& stackContext);
//////////////////////////////////////////////////////////////////////////////////////////////////////
// Netbinding Interface duplication here to be called from the ScriptComponent
GridMate::ReplicaChunkPtr GetNetworkBinding();
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk);
void UnbindFromNetwork();
void OnPropertyUpdate(AZ::ScriptProperty*const& scriptProperty, const GridMate::TimeContext& tc);
bool OnInvokeRPC(AZStd::string functionName, AZStd::vector< AZ::ScriptProperty*> properties, const GridMate::RpcContext& rpcContext);
private:
void RegisterMetaTableCache();
template<typename PropertyType, typename PropertyArrayType>
AZ::ScriptPropertyTable* ConvertPropertyArrayToTable(PropertyArrayType* arrayProperty)
{
AZ::ScriptPropertyTable* scriptPropertyTable = aznew AZ::ScriptPropertyTable(arrayProperty->m_name.c_str());
PropertyType propertyType;
for (unsigned int i=0; i < arrayProperty->m_values.size(); ++i)
{
propertyType.m_value = arrayProperty->m_values[i];
// Offset by 1 to deal with lua 1 indexing.
// Table will make a clone of our object.
scriptPropertyTable->SetTableValue(i+1, &propertyType);
}
return scriptPropertyTable;
}
void AssignDataSets();
NetworkedTableValue* FindTableValue(const AZStd::string& name);
const NetworkedTableValue* FindTableValue(const AZStd::string& name) const;
EntityScriptContext m_entityScriptContext;
GridMate::ReplicaChunkPtr m_replicaChunk;
NetworkedTableMap m_networkedTable;
RPCHelperMap m_rpcHelperMap;
};
// Typedeffing out the RPC and DataSet definitions.
typedef GridMate::Rpc< GridMate::RpcArg< AZStd::string >, GridMate::RpcArg< ScriptRPCMarshaler::Container, ScriptRPCMarshaler > >::BindInterface<ScriptNetBindingTable, &ScriptNetBindingTable::OnInvokeRPC> ScriptPropertyRPC;
typedef GridMate::DataSet<AZ::ScriptProperty*, ScriptPropertyMarshaler, ScriptPropertyThrottler>::BindInterface<ScriptNetBindingTable, &ScriptNetBindingTable::OnPropertyUpdate> ScriptPropertyDataSetType;
class ScriptComponentReplicaChunk;
// Specialized DataSet used by the ScriptProperties, just to add some wrapped around functionality
// and to allow me to manipulate the DataSet throttler in order to properly manage a dirty flag
class ScriptPropertyDataSet
: public ScriptPropertyDataSetType
, public AZ::ScriptPropertyWatcherBus::Handler
, public AZ::ScriptPropertyWatcher
{
private:
friend class ScriptComponentReplicaChunk;
friend class ScriptNetBindingTable::NetworkedTableValue;
const char* GetDataSetName();
public:
ScriptPropertyDataSet();
~ScriptPropertyDataSet();
bool IsReserved() const;
bool UpdateScriptProperty(AZ::ScriptDataContext& scriptDataContext, const AZStd::string& propertyName);
void SetScriptProperty(AZ::ScriptProperty* scriptProperty);
void OnObjectModified() override;
private:
void Reserve(ScriptNetBindingTable::NetworkedTableValue* reserver);
void Release(ScriptNetBindingTable::NetworkedTableValue* reserver);
ScriptNetBindingTable::NetworkedTableValue* m_reserver;
};
// The actual ReplicaChunk that the script will use
class ScriptComponentReplicaChunk
: public GridMate::ReplicaChunkBase
{
public:
AZ_CLASS_ALLOCATOR(ScriptComponentReplicaChunk, AZ::SystemAllocator,0);
static const int k_maxScriptableDataSets = GM_MAX_DATASETS_IN_CHUNK;
static const char* GetChunkName() { return "ScriptComponentReplicaChunk"; }
// Might want to add some type of comment field into the various fields so this can be properly parsed
// and determined what we are actually sending.
ScriptComponentReplicaChunk();
~ScriptComponentReplicaChunk();
bool IsReplicaMigratable() override;
AZ::u32 CalculateDirtyDataSetMask(GridMate::MarshalContext& marshalContext) override;
// Called from the Master, will assign the table value to the DataSet specified by the helper.
bool AssignDataSet(ScriptNetBindingTable::NetworkedTableValue& helper);
// Called from teh Proxy. Will Assign the TableValue to the DataSet that contains the target property
void AssignDataSetForProperty(ScriptNetBindingTable::NetworkedTableValue& helper, AZ::ScriptProperty* targetProperty);
// Only called inside of an assert, checks that the DataSet that the targetProperty is in is the same as the assumedDataSet
// Used to confirm that we don't get a confusion between master/proxy about which ScriptProperty is assigned to which DataSet.
bool SanityCheckDataSet(AZ::ScriptProperty* targetProperty, ScriptPropertyDataSet* assumedDataSet);
ScriptPropertyRPC m_scriptRPC;
private:
AZ::u32 m_enabledDataSetMask;
ScriptPropertyDataSet m_propertyDataSets[k_maxScriptableDataSets];
};
}
#endif
@@ -0,0 +1,789 @@
/*
* 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 "ScriptRemoteDebugging.h"
#include "ScriptDebugAgentBus.h"
#include "ScriptDebugMsgReflection.h"
#include <AzFramework/TargetManagement/TargetManagementAPI.h>
#include <AzFramework/Metrics/MetricsPlainTextNameRegistration.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Script/ScriptSystemBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/parallel/lock.h>
#include <AzCore/Math/Crc.h>
#include <AzCore/std/parallel/thread.h>
#include <AzCore/std/parallel/atomic.h>
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/DataMarshal.h>
namespace AzFramework
{
namespace ScriptDebugAgentInternal
{
//-------------------------------------------------------------------------
static bool EnumClass(const char* name, const AZ::Uuid& typeId, void* userData)
{
ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
output.push_back();
output.back().m_name = name;
output.back().m_typeId = typeId;
return true;
}
//-------------------------------------------------------------------------
static bool EnumMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
{
(void)classTypeId;
ScriptUserMethodList& output = *reinterpret_cast<ScriptUserMethodList*>(userData);
output.push_back();
output.back().m_name = name;
output.back().m_dbgParamInfo = dbgParamInfo ? dbgParamInfo : "null";
return true;
}
//-------------------------------------------------------------------------
static bool EnumProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
{
(void)classTypeId;
ScriptUserPropertyList& output = *reinterpret_cast<ScriptUserPropertyList*>(userData);
output.push_back();
output.back().m_name = name;
output.back().m_isRead = isRead;
output.back().m_isWrite = isWrite;
return true;
}
//-------------------------------------------------------------------------
static bool EnumEBus(const AZStd::string& name, bool canBroadcast, bool canQueue, bool hasHandler, void* userData)
{
ScriptUserEBusList& output = *reinterpret_cast<ScriptUserEBusList*>(userData);
bool found = false;
for (ScriptUserEBusList::iterator it = output.begin(); it != output.end(); ++it)
{
if (name == it->m_name)
{
found = true;
}
}
AZ_Warning("ScriptRemoteDebugging", !found, "Ebus (%s) already enumerated", name.c_str());
if (!found)
{
output.push_back();
auto& ebus = output.back();
ebus.m_name = name;
ebus.m_canBroadcast = canBroadcast;
ebus.m_canQueue = canQueue;
ebus.m_hasHandler = hasHandler;
}
return true;
}
//-------------------------------------------------------------------------
static bool EnumEBusSender(const AZStd::string& ebusName, const AZStd::string& senderName, const AZStd::string& dbgParamInfo, const AZStd::string& category, void* userData)
{
ScriptUserEBusList& output = *reinterpret_cast<ScriptUserEBusList*>(userData);
for (ScriptUserEBusList::iterator it = output.begin(); it != output.end(); ++it)
{
if (ebusName == it->m_name)
{
it->m_events.push_back();
auto& event = it->m_events.back();
event.m_name = senderName;
event.m_dbgParamInfo = dbgParamInfo;
event.m_category = category;
return true;
}
}
AZ_Assert(false, "Received an enumeration of an eBus sender method for an eBus we have not enumerated yet!");
return false;
}
//-------------------------------------------------------------------------
static bool EnumClassMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
{
ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
for (ScriptUserClassList::iterator it = output.begin(); it != output.end(); ++it)
{
if (classTypeId && *classTypeId == it->m_typeId)
{
return EnumMethod(classTypeId, name, dbgParamInfo, &(it->m_methods));
}
}
AZ_Assert(false, "Received enumeration of a class method for a class we have not enumerated yet!");
return true;
}
//-------------------------------------------------------------------------
static bool EnumClassProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
{
ScriptUserClassList& output = *reinterpret_cast<ScriptUserClassList*>(userData);
for (ScriptUserClassList::iterator it = output.begin(); it != output.end(); ++it)
{
if (classTypeId && *classTypeId == it->m_typeId)
{
return EnumProperty(classTypeId, name, isRead, isWrite, &(it->m_properties));
}
}
AZ_Assert(false, "Received enumeration of a class property for a class we have not enumerated yet!");
return true;
}
//-------------------------------------------------------------------------
static bool EnumGlobalMethod(const AZ::Uuid* classTypeId, const char* name, const char* dbgParamInfo, void* userData)
{
ScriptDebugRegisteredGlobalsResult* output = reinterpret_cast<ScriptDebugRegisteredGlobalsResult*>(userData);
return EnumMethod(classTypeId, name, dbgParamInfo, &output->m_methods);
}
//-------------------------------------------------------------------------
static bool EnumGlobalProperty(const AZ::Uuid* classTypeId, const char* name, bool isRead, bool isWrite, void* userData)
{
ScriptDebugRegisteredGlobalsResult* output = reinterpret_cast<ScriptDebugRegisteredGlobalsResult*>(userData);
return EnumProperty(classTypeId, name, isRead, isWrite, &output->m_properties);
}
//-------------------------------------------------------------------------
static bool EnumLocals(AZStd::vector<AZStd::string>* output, const char* name, AZ::ScriptDataContext& dataContext)
{
(void)dataContext;
output->push_back(name);
return true;
}
//-------------------------------------------------------------------------
} // namespace ScriptDebugAgentInternal
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
class ScriptDebugAgent
: public AZ::Component
, public ScriptDebugAgentBus::Handler
, public TmMsgBus::Handler
, AZ::SystemTickBus::Handler
{
public:
AZ_COMPONENT(ScriptDebugAgent, "{624a7be2-3c7e-4119-aee2-1db2bdb6cc89}");
ScriptDebugAgent() = default;
//////////////////////////////////////////////////////////////////////////
// Component base
virtual void Init();
virtual void Activate();
virtual void Deactivate();
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::SystemTickBus
virtual void OnSystemTick();
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// ScriptDebugAgentBus
virtual void RegisterContext(AZ::ScriptContext* sc, const char* name);
virtual void UnregisterContext(AZ::ScriptContext* sc);
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// TmMsgBus
virtual void OnReceivedMsg(TmMsgPtr msg);
//////////////////////////////////////////////////////////////////////////
protected:
ScriptDebugAgent(const ScriptDebugAgent&) = delete;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
static void Reflect(AZ::ReflectContext* context);
void Attach(const TargetInfo& ti, const char* scriptContextName);
void Detach();
void BreakpointCallback(AZ::ScriptContextDebug* debugContext, const AZ::ScriptContextDebug::Breakpoint* breakpoint);
void DebugCommandCallback(AZ::ScriptContextDebug* debugContext);
void Process();
TargetInfo m_debugger;
TmMsgQueue m_msgQueue;
AZStd::mutex m_msgMutex;
AZ::ScriptContext* m_curContext;
struct ContextRecord
{
AZ::ScriptContext* m_context;
AZStd::string m_name;
};
typedef AZStd::vector<ContextRecord> ContextMap;
ContextMap m_availableContexts;
enum SDA_STATE
{
SDA_STATE_DETACHED,
SDA_STATE_RUNNING,
SDA_STATE_PAUSED,
SDA_STATE_DETACHING,
};
AZStd::atomic_uint m_executionState;
};
//-------------------------------------------------------------------------
void ScriptDebugAgent::Init()
{
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::Activate()
{
m_executionState = SDA_STATE_DETACHED;
m_curContext = NULL;
// register default app script context if there is one
AZ::ScriptContext* defaultScriptContext = NULL;
EBUS_EVENT_RESULT(defaultScriptContext, AZ::ScriptSystemRequestBus, GetContext, AZ::ScriptContextIds::DefaultScriptContextId);
if (defaultScriptContext)
{
RegisterContext(defaultScriptContext, "Default");
}
AZ::ScriptContext* cryScriptContext = nullptr;
EBUS_EVENT_RESULT(cryScriptContext, AZ::ScriptSystemRequestBus, GetContext, AZ::ScriptContextIds::CryScriptContextId);
if (cryScriptContext)
{
RegisterContext(cryScriptContext, "Cry");
}
ScriptDebugAgentBus::Handler::BusConnect();
AZ::SystemTickBus::Handler::BusConnect();
TmMsgBus::Handler::BusConnect(AZ_CRC("ScriptDebugAgent", 0xb6be0836));
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::Deactivate()
{
TmMsgBus::Handler::BusDisconnect(AZ_CRC("ScriptDebugAgent", 0xb6be0836));
AZ::SystemTickBus::Handler::BusDisconnect();
// TODO: Make thread safe if we ever have multithreaded script contexts!
if (m_executionState != SDA_STATE_DETACHED)
{
Detach();
}
AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
m_msgQueue.clear();
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::OnSystemTick()
{
// If we are attached, then all processing should happen
// in the attached context.
if (m_executionState == SDA_STATE_DETACHED)
{
Process();
}
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::RegisterContext(AZ::ScriptContext* sc, const char* name)
{
for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
{
if (it->m_context == sc)
{
AZ_Assert(false, "ScriptContext 0x%p is already registered as %s! New registration ignored.", sc, it->m_name.c_str());
return;
}
}
m_availableContexts.push_back();
m_availableContexts.back().m_context = sc;
m_availableContexts.back().m_name = name;
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::UnregisterContext(AZ::ScriptContext* sc)
{
for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
{
if (it->m_context == sc)
{
if (m_curContext == sc)
{
// TODO: This operation needs to be thread-safe if we ever run contexts from multiple threads.
Detach();
}
m_availableContexts.erase(it);
return;
}
}
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::OnReceivedMsg(TmMsgPtr msg)
{
AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
m_msgQueue.push_back(msg);
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::Attach(const TargetInfo& ti, const char* scriptContextName)
{
for (ContextMap::iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
{
if (azstricmp(scriptContextName, it->m_name.c_str()) == 0)
{
AZ::ScriptContext* sc = it->m_context;
AZ_Assert(sc, "How did we end up with a NULL in the available contexts map?");
m_debugger = ti;
m_curContext = sc;
sc->EnableDebug();
AZ::ScriptContextDebug* dbgContext = sc->GetDebugContext();
if (dbgContext)
{
dbgContext->EnableStackRecord();
AZ::ScriptContextDebug::BreakpointCallback breakpointCallback = AZStd::bind(&ScriptDebugAgent::BreakpointCallback, this, AZStd::placeholders::_1, AZStd::placeholders::_2);
dbgContext->EnableBreakpoints(breakpointCallback);
AZ::ScriptContextDebug::ProcessDebugCmdCallback debugCommandCallback = AZStd::bind(&ScriptDebugAgent::DebugCommandCallback, this, AZStd::placeholders::_1);
dbgContext->EnableDebugCmdProcess(debugCommandCallback);
}
// Notify debugger that he successfully connected
EBUS_EVENT(TargetManager::Bus, SendTmMessage, ti, ScriptDebugAck(AZ_CRC("AttachDebugger", 0x6590ff36), AZ_CRC("Ack", 0x22e4f8b1)));
AZ_TracePrintf("LUA", "Remote debugger %s has attached to context %s.\n", m_debugger.GetDisplayName(), it->m_name.c_str());
m_executionState = SDA_STATE_RUNNING;
return;
}
}
// Failed to find context, notify debugger that the connection was rejected.
EBUS_EVENT(TargetManager::Bus, SendTmMessage, ti, ScriptDebugAck(AZ_CRC("AttachDebugger", 0x6590ff36), AZ_CRC("IllegalOperation", 0x437dc900)));
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::Detach()
{
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_debugger, ScriptDebugAck(AZ_CRC("DetachDebugger", 0x88a2ee04), AZ_CRC("Ack", 0x22e4f8b1)));
// TODO: We need to make sure we are thread safe if the contexts are running on
// different threads.
//if (m_curContext->GetErrorHookUserData() == this) {
// m_curContext->SetErrorHook(NULL);
//}
AZ::ScriptContextDebug* debugContext = m_curContext->GetDebugContext();
debugContext->DisableBreakpoints();
debugContext->DisableStackRecord();
debugContext->DisableDebugCmdProcess();
m_curContext->DisableDebug();
AZ_TracePrintf("LUA", "Remote debugger %s has detached from context 0x%p.\n", m_debugger.GetDisplayName(), m_curContext);
m_debugger = TargetInfo();
m_curContext = NULL;
m_executionState = SDA_STATE_DETACHED;
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::DebugCommandCallback(AZ::ScriptContextDebug* debugContext)
{
(void)debugContext;
AZ_Assert(m_curContext, "We are debugging without a script context!");
AZ_Assert(m_curContext->GetDebugContext() == debugContext, "Context mismatch. Are we attached to the correct script context?");
if (m_executionState != SDA_STATE_DETACHED)
{
// This is the only safe place to tear down the debug context because
// it is the only function that runs in the script context thread and is never
// called from within any debugContext callbacks.
if (m_executionState == SDA_STATE_DETACHING)
{
AZ_TracePrintf("LUA", "Disabling debugging for script context(0x%p).\n", m_curContext);
Detach();
}
else
{
Process();
}
}
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::BreakpointCallback(AZ::ScriptContextDebug* debugContext, const AZ::ScriptContextDebug::Breakpoint* breakpoint)
{
(void)debugContext;
AZ_Assert(m_curContext, "We are debugging without a script context!");
AZ_Assert(m_curContext->GetDebugContext() == debugContext, "Context mismatch. Are we attached to the correct script context?");
if (m_executionState == SDA_STATE_RUNNING)
{
m_executionState = SDA_STATE_PAUSED;
if (m_debugger.IsValid())
{
ScriptDebugAckBreakpoint response;
response.m_id = AZ_CRC("BreakpointHit", 0xf1a38e0b);
response.m_moduleName = breakpoint->m_sourceName;
response.m_line = static_cast<AZ::u32>(breakpoint->m_lineNumber);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, m_debugger, response);
}
while (m_executionState == SDA_STATE_PAUSED)
{
EBUS_EVENT(TargetManager::Bus, DispatchMessages, AZ_CRC("ScriptDebugAgent", 0xb6be0836));
Process();
AZStd::this_thread::yield();
}
}
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::Process()
{
// Process messages
AZ::ScriptContextDebug* dbgContext = m_curContext ? m_curContext->GetDebugContext() : NULL;
while (!m_msgQueue.empty())
{
m_msgMutex.lock();
TmMsgPtr msg = *m_msgQueue.begin();
m_msgQueue.pop_front();
m_msgMutex.unlock();
AZ_Assert(msg, "We received a NULL message in the script debug agent's message queue!");
TargetInfo sender;
EBUS_EVENT_RESULT(sender, TargetManager::Bus, GetTargetInfo, msg->GetSenderTargetId());
// The only message we accept without a target match is AttachDebugger
if (m_debugger.GetNetworkId() != sender.GetNetworkId())
{
ScriptDebugRequest* request = azdynamic_cast<ScriptDebugRequest*>(msg.get());
if (!request || (request->m_request != AZ_CRC("AttachDebugger", 0x6590ff36) && request->m_request != AZ_CRC("EnumContexts", 0xbdb959ba)))
{
AZ_TracePrintf("LUA", "Rejecting msg 0x%x (%s is not the attached debugger)\n", request->m_request, sender.GetDisplayName());
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("AccessDenied", 0xde72ce21)));
continue;
}
}
if (azrtti_istypeof<ScriptDebugBreakpointRequest*>(msg.get()))
{
ScriptDebugBreakpointRequest* request = azdynamic_cast<ScriptDebugBreakpointRequest*>(msg.get());
AZ::ScriptContextDebug::Breakpoint bp;
bp.m_sourceName = request->m_context.c_str();
bp.m_lineNumber = request->m_line;
if (request->m_request == AZ_CRC("AddBreakpoint", 0xba71daa4))
{
AZ_TracePrintf("LUA", "Adding breakpoint %s:%d\n", bp.m_sourceName.c_str(), bp.m_lineNumber);
dbgContext->AddBreakpoint(bp);
}
else if (request->m_request == AZ_CRC("RemoveBreakpoint", 0x90ade500))
{
AZ_TracePrintf("LUA", "Removing breakpoint %s:%d\n", bp.m_sourceName.c_str(), bp.m_lineNumber);
dbgContext->RemoveBreakpoint(bp);
}
ScriptDebugAckBreakpoint response;
response.m_id = request->m_request;
response.m_moduleName = request->m_context;
response.m_line = request->m_line;
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
}
else if (azrtti_istypeof<ScriptDebugSetValue*>(msg.get())) // sets the value of a variable
{
if (m_executionState == SDA_STATE_PAUSED)
{
ScriptDebugSetValue* request = azdynamic_cast<ScriptDebugSetValue*>(msg.get());
ScriptDebugSetValueResult response;
response.m_name = request->m_value.m_name;
response.m_result = dbgContext->SetValue(request->m_value);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'SetValue' can only be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(AZ_CRC("SetValue", 0xd595caa6), AZ_CRC("IllegalOperation", 0x437dc900)));
}
}
else if (azrtti_istypeof<ScriptDebugRequest*>(msg.get()))
{
ScriptDebugRequest* request = azdynamic_cast<ScriptDebugRequest*>(msg.get());
// Check request type
// EnumLocals
if (request->m_request == AZ_CRC("EnumLocals", 0x4aa29dcf)) // enumerates local variables
{
if (m_executionState == SDA_STATE_PAUSED)
{
ScriptDebugEnumLocalsResult response;
AZ::ScriptContextDebug::EnumLocalCallback enumCB = AZStd::bind(&ScriptDebugAgentInternal::EnumLocals, &response.m_names, AZStd::placeholders::_1, AZStd::placeholders::_2);
dbgContext->EnumLocals(enumCB);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'EnumLocals' can only be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
}
// GetValue
}
else if (request->m_request == AZ_CRC("GetValue", 0x2d64f577))
{
ScriptDebugGetValueResult response;
response.m_value.m_name = request->m_context;
dbgContext->GetValue(response.m_value);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
// StepOver
}
else if (request->m_request == AZ_CRC("StepOver", 0x6b89bf41))
{
if (m_executionState == SDA_STATE_PAUSED)
{
dbgContext->StepOver();
m_executionState = SDA_STATE_RUNNING;
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'StepOver' can only be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
}
// StepIn
}
else if (request->m_request == AZ_CRC("StepIn", 0x761a6b13))
{
if (m_executionState == SDA_STATE_PAUSED)
{
dbgContext->StepInto();
m_executionState = SDA_STATE_RUNNING;
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'StepIn' can only be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
}
// StepOut
}
else if (request->m_request == AZ_CRC("StepOut", 0xac19b635))
{
if (m_executionState == SDA_STATE_PAUSED)
{
dbgContext->StepOut();
m_executionState = SDA_STATE_RUNNING;
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'StepOut' can only be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
}
// Continue
}
else if (request->m_request == AZ_CRC("Continue", 0x13e32adf))
{
if (m_executionState == SDA_STATE_PAUSED)
{
m_executionState = SDA_STATE_RUNNING;
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("Ack", 0x22e4f8b1)));
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'Continue' can only be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
}
// GetCallstack
}
else if (request->m_request == AZ_CRC("GetCallstack", 0x343b24f3))
{
if (m_executionState == SDA_STATE_PAUSED)
{
char bufStackTrace[4096];
dbgContext->StackTrace(bufStackTrace, 4096);
ScriptDebugCallStackResult response;
response.m_callstack = bufStackTrace;
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'GetCallstack' can only be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
}
// enumerates global C++ functions that have been exposed to script
}
else if (request->m_request == AZ_CRC("EnumRegisteredGlobals", 0x80d1e6af))
{
ScriptDebugRegisteredGlobalsResult response;
dbgContext->EnumRegisteredGlobals(&ScriptDebugAgentInternal::EnumGlobalMethod, &ScriptDebugAgentInternal::EnumGlobalProperty, &response);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
// enumerates C++ classes that have been exposed to script
}
else if (request->m_request == AZ_CRC("EnumRegisteredClasses", 0xed6b8070))
{
ScriptDebugRegisteredClassesResult response;
dbgContext->EnumRegisteredClasses(&ScriptDebugAgentInternal::EnumClass, &ScriptDebugAgentInternal::EnumClassMethod, &ScriptDebugAgentInternal::EnumClassProperty, &response.m_classes);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
// ExecuteScript
}
else if (request->m_request == AZ_CRC("EnumRegisteredEBuses", 0x8237bde7))
{
ScriptDebugRegisteredEBusesResult response;
dbgContext->EnumRegisteredEBuses(&ScriptDebugAgentInternal::EnumEBus, &ScriptDebugAgentInternal::EnumEBusSender, &response.m_ebusList);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
}
else if (request->m_request == AZ_CRC("ExecuteScript", 0xc35e01e7))
{
if (m_executionState == SDA_STATE_RUNNING)
{
AZ_Assert(request->GetCustomBlob(), "ScriptDebugAgent was asked to execute a script but script is missing!");
ScriptDebugAckExecute response;
response.m_moduleName = request->m_context;
response.m_result = m_curContext->Execute(reinterpret_cast<const char*>(request->GetCustomBlob()), request->m_context.c_str());
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
}
else
{
AZ_TracePrintf("LUA", "Command rejected. 'ExecuteScript' cannot be issued while on a breakpoint.\n");
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("IllegalOperation", 0x437dc900)));
}
// AttachDebugger
}
else if (request->m_request == AZ_CRC("AttachDebugger", 0x6590ff36))
{
if (m_executionState == SDA_STATE_DETACHED)
{
Attach(sender, request->m_context.c_str());
}
else
{
// we need to detach from the current context first
AZ_TracePrintf("LUA", "Received connection from %s while still connected to %s! Detaching from %s.\n", sender.GetDisplayName(), m_debugger.GetDisplayName(), m_debugger.GetDisplayName());
AZ_TracePrintf("LUA", "Force disconnecting debugger %s from context 0x%p.\n", m_debugger.GetDisplayName(), m_curContext);
AZStd::lock_guard<AZStd::mutex> l(m_msgMutex);
m_msgQueue.push_front(msg);
m_executionState = SDA_STATE_DETACHING;
}
// We need to switch contexts before any more processing, keep remaining messages
// in the queue and return.
return;
// DetachDebugger
}
else if (request->m_request == AZ_CRC("DetachDebugger", 0x88a2ee04))
{
// We need to switch contexts before any more processing, keep remaining messages
// in the queue and return.
m_executionState = SDA_STATE_DETACHING;
return;
// EnumContexts
}
else if (request->m_request == AZ_CRC("EnumContexts", 0xbdb959ba))
{
AZ_TracePrintf("LUA", "Received EnumContexts request\n");
ScriptDebugEnumContextsResult response;
for (ContextMap::const_iterator it = m_availableContexts.begin(); it != m_availableContexts.end(); ++it)
{
response.m_names.push_back(it->m_name);
}
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, response);
// Invalid command
}
else
{
AZ_TracePrintf("LUA", "Received invalid command 0x%x.\n", request->m_request);
EBUS_EVENT(TargetManager::Bus, SendTmMessage, sender, ScriptDebugAck(request->m_request, AZ_CRC("InvalidCmd", 0x926abd27)));
}
}
else
{
AZ_Assert(false, "ScriptDebugAgent received a message that is not of any recognized types!");
}
}
// Check if our debugger is still around
if (m_executionState != SDA_STATE_DETACHED)
{
bool debuggerOnline = false;
EBUS_EVENT_RESULT(debuggerOnline, TargetManager::Bus, IsTargetOnline, m_debugger.GetNetworkId());
if (!debuggerOnline)
{
m_executionState = SDA_STATE_DETACHING;
}
}
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ScriptDebugService", 0x5b0c3898));
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ScriptDebugService", 0x5b0c3898));
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("ScriptService", 0x787235ab));
}
//-------------------------------------------------------------------------
void ScriptDebugAgent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<ScriptDebugAgent, AZ::Component>()
->Version(1)
;
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<ScriptDebugAgent>(
"Script Debug Agent", "Provides remote debugging services for script contexts")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Profiling")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
;
}
}
ReflectScriptDebugClasses(context);
static bool registeredComponentUuidWithMetricsAlready = false;
if (!registeredComponentUuidWithMetricsAlready)
{
// have to let the metrics system know that it's ok to send back the name of the ScriptDebugAgent component to Amazon as plain text, without hashing
EBUS_EVENT(AzFramework::MetricsPlainTextNameRegistrationBus, RegisterForNameSending, AZStd::vector<AZ::Uuid>{ azrtti_typeid<ScriptDebugAgent>() });
// only ever do this once
registeredComponentUuidWithMetricsAlready = true;
}
}
////-------------------------------------------------------------------------
//class ScriptDebugAgentFactory : public AZ::ComponentFactory<ScriptDebugAgent>
//{
//public:
// AZ_CLASS_ALLOCATOR(ScriptDebugAgentFactory, AZ::SystemAllocator,0);
// ScriptDebugAgentFactory() : AZ::ComponentFactory<ScriptDebugAgent>(AZ_CRC("ScriptDebugAgent", 0xb6be0836))
// {
// }
// virtual const char* GetName() { return "ScriptDebugAgent"; }
// virtual void Reflect(const AZ::ClassDataReflection& reflection)
// {
// if( reflection.m_serialize )
// {
// reflection.m_serialize->Class<ScriptDebugAgent>("ScriptDebugAgent", "{6CEA890A-CEC0-4725-8E9A-97ACCE5941A9}")
// ->Version(1)
// AZ::EditContext *ec = reflection.m_serialize->GetEditContext();
// if (ec) {
// ec->Class<ScriptDebugAgent>("Script Debug Agent", "Provides remote debugging services for script contexts.");
// }
// }
// ReflectScriptDebugClasses(reflection);
// }
//};
//-------------------------------------------------------------------------
AZ::ComponentDescriptor* CreateScriptDebugAgentFactory()
{
return ScriptDebugAgent::CreateDescriptor();
}
//-------------------------------------------------------------------------
} // namespace AzFramework
@@ -0,0 +1,127 @@
/*
* 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.
*
*/
#ifndef SCRIPT_REMOTE_DEBUGGING_H
#define SCRIPT_REMOTE_DEBUGGING_H
#include <AzCore/Component/Component.h>
#include <AzCore/Script/ScriptContextDebug.h>
#include <GridMate/Serialize/Buffer.h>
/*
* Remote script debugging is accomplished through the ScriptDebugAgent, which
* sits on the target running the VM and communicates with the remote debugger
* through the target manager.
*
* To communicate with the agent, send TM messages to AZ_CRC("ScriptDebugAgent").
* The agent will respond by sending TM messages to AZ_CRC("ScriptDebugger").
* See the comments in TargetManagementAPI.h" for information on how to send TM
* messages.
*
* Valid commands and agent's responses:
* Commands that can be sent at any time:
* AZ_CRC("AttachDebugger")
* AZ_CRC("DebuggerAttached")
* AZ_CRC("DebuggerRefused")
* AZ_CRC("EnumContexts")
* AZ_CRC(EnumContextsResult")
* Commands that can be sent when attached:
* AZ_CRC("DetachDebugger")
* AZ_CRC("DebuggerDetached") *** can also be sent to current debugger if another debugger is attaching
* AZ_CRC("AddBreakpoint")
* AZ_CRC("BreakpointAdded")
* AZ_CRC("BreakpointHit") *** sent when a breakpoint hits
* AZ_CRC("RemoveBreakpoint")
* AZ_CRC("BreakpointRemoved")
* AZ_CRC("EnumRegisteredGlobals")
* AZ_CRC("EnumRegisteredGlobalsResult")
* AZ_CRC("EnumRegisteredClasses")
* AZ_CRC("EnumRegisteredClassesResult")
* AZ_CRC("GetValue")
* AZ_CRC("GetValueResult")
* Commands that can only be sent while NOT on a breakpoint
* AZ_CRC("ExecuteScript")
* AZ_CRC("ExecutionCompleted")
* Commands that can only be sent while sitting on a breakpoint
* AZ_CRC("GetCallstack")
* AZ_CRC("CallstackResult")
* AZ_CRC("EnumLocals")
* AZ_CRC("EnumLocalsResult")
* AZ_CRC("SetValue")
* AZ_CRC("SetValueResult")
* AZ_CRC("StepOver")
* AZ_CRC("Ack")
* AZ_CRC("StepIn")
* AZ_CRC("Ack")
* AZ_CRC("StepOut")
* AZ_CRC("Ack")
* AZ_CRC("Continue")
* AZ_CRC("Ack")
*/
namespace AzFramework
{
struct ScriptUserMethodInfo
{
AZ_TYPE_INFO(ScriptUserMethodInfo, "{32fe4b43-2c23-4ab4-9374-3d13cf050002}");
AZStd::string m_name;
AZStd::string m_dbgParamInfo;
};
typedef AZStd::vector<ScriptUserMethodInfo> ScriptUserMethodList;
struct ScriptUserPropertyInfo
{
AZ_TYPE_INFO(ScriptUserPropertyInfo, "{6cd9f5be-b2cd-41bb-9da5-1b053548cf56}");
AZStd::string m_name;
bool m_isRead;
bool m_isWrite;
};
typedef AZStd::vector<ScriptUserPropertyInfo> ScriptUserPropertyList;
struct ScriptUserClassInfo
{
AZ_TYPE_INFO(ScriptUserClassInfo, "{08b32f99-2ea2-4abe-a05f-1aa32ef44b15}");
AZStd::string m_name;
AZ::Uuid m_typeId;
ScriptUserMethodList m_methods;
ScriptUserPropertyList m_properties;
};
typedef AZStd::vector<ScriptUserClassInfo> ScriptUserClassList;
struct ScriptUserEBusMethodInfo : public ScriptUserMethodInfo
{
AZ_TYPE_INFO(ScriptUserEBusMethodInfo, "{FD805F6C-8612-41CF-85FE-3B97683C98F2}");
AZStd::string m_category;
};
typedef AZStd::vector<ScriptUserEBusMethodInfo> ScriptUserEBusMethodList;
struct ScriptUserEBusInfo
{
AZ_TYPE_INFO(ScriptUserEBusInfo, "{2376407e-1621-4d7f-b4ad-de04a81a2616}");
AZStd::string m_name;
ScriptUserEBusMethodList m_events;
bool m_canBroadcast;
bool m_canQueue;
bool m_hasHandler;
};
typedef AZStd::vector<ScriptUserEBusInfo> ScriptUserEBusList;
AZ::ComponentDescriptor* CreateScriptDebugAgentFactory();
} // namespace AzFramework
#endif // SCRIPT_REMOTE_DEBUGGING_H
#pragma once