From 177696ee001d69467e18a7246b7e40db13d193aa Mon Sep 17 00:00:00 2001 From: xKamuna Date: Fri, 24 Dec 2021 04:43:28 -0800 Subject: [PATCH 01/25] Fix blend shape offsets --- Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp index c6545449b..1d527089d 100644 --- a/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp +++ b/Source/Engine/Tools/ModelTool/ModelTool.OpenFBX.cpp @@ -720,9 +720,9 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb continue; const ofbx::Shape* shape = channel->getShape(targetShapeCount - 1); - if (shape->getVertexCount() != vertexCount) + if (shape->getVertexCount() != aGeometry->getVertexCount()) { - LOG(Error, "Blend shape '{0}' in mesh '{1}' has different amount of vertices ({2}) than mesh ({3})", String(shape->name), mesh.Name, shape->getVertexCount(), vertexCount); + LOG(Error, "Blend shape '{0}' in mesh '{1}' has different amount of vertices ({2}) than mesh ({3})", String(shape->name), mesh.Name, shape->getVertexCount(), aGeometry->getVertexCount()); continue; } @@ -730,21 +730,21 @@ bool ProcessMesh(ImportedModelData& result, OpenFbxImporterData& data, const ofb blendShapeData.Name = shape->name; blendShapeData.Weight = channel->getShapeCount() > 1 ? (float)(channel->getDeformPercent() / 100.0) : 1.0f; - blendShapeData.Vertices.Resize(shape->getVertexCount()); + blendShapeData.Vertices.Resize(vertexCount); for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++) blendShapeData.Vertices.Get()[i].VertexIndex = i; auto shapeVertices = shape->getVertices(); for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++) { - auto delta = ToVector3(shapeVertices[i]) - mesh.Positions.Get()[i]; + auto delta = ToVector3(shapeVertices[i + firstVertexOffset]) - mesh.Positions.Get()[i]; blendShapeData.Vertices.Get()[i].PositionDelta = delta; } auto shapeNormals = shape->getNormals(); for (int32 i = 0; i < blendShapeData.Vertices.Count(); i++) { - /*auto delta = ToVector3(shapeNormals[i]) - mesh.Normals[i]; + /*auto delta = ToVector3(shapeNormals[i + firstVertexOffset]) - mesh.Normals[i]; auto length = delta.Length(); if (length > ZeroTolerance) delta /= length;*/ From ef40ccbe14394e3b9568586c834dc1e53337656f Mon Sep 17 00:00:00 2001 From: xKamuna Date: Thu, 30 Dec 2021 23:47:26 -0800 Subject: [PATCH 02/25] Fix issue with filters being ignored for two controllers colliding --- .../Physics/Colliders/CharacterController.cpp | 1 + Source/Engine/Physics/Physics.Queries.cpp | 54 +++++++++++++++++++ Source/Engine/Physics/Physics.h | 5 ++ Source/Engine/Physics/Types.h | 1 + 4 files changed, 61 insertions(+) diff --git a/Source/Engine/Physics/Colliders/CharacterController.cpp b/Source/Engine/Physics/Colliders/CharacterController.cpp index 288d32d56..c57c0f395 100644 --- a/Source/Engine/Physics/Colliders/CharacterController.cpp +++ b/Source/Engine/Physics/Colliders/CharacterController.cpp @@ -173,6 +173,7 @@ CharacterController::CollisionFlags CharacterController::Move(const Vector3& dis filters.mFilterData = (PxFilterData*)&_filterData; filters.mFilterCallback = Physics::GetCharacterQueryFilterCallback(); filters.mFilterFlags = PxQueryFlag::eDYNAMIC | PxQueryFlag::eSTATIC | PxQueryFlag::ePREFILTER; + filters.mCCTFilterCallback = Physics::GetCharacterControllerFilterCallback(); result = (CollisionFlags)(byte)_controller->move(C2P(displacement), _minMoveDistance, deltaTime, filters); _lastFlags = result; diff --git a/Source/Engine/Physics/Physics.Queries.cpp b/Source/Engine/Physics/Physics.Queries.cpp index bb6940db5..f7986397e 100644 --- a/Source/Engine/Physics/Physics.Queries.cpp +++ b/Source/Engine/Physics/Physics.Queries.cpp @@ -6,6 +6,8 @@ #include "Actors/PhysicsColliderActor.h" #include #include +#include +#include // Temporary result buffer size #define HIT_BUFFER_SIZE 128 @@ -217,6 +219,52 @@ public: } }; +class CharacterControllerFilter : public PxControllerFilterCallback +{ +private: + + PxShape* getShape(const PxController& controller) + { + PxRigidDynamic* actor = controller.getActor(); + + // Early out if no actor or no shapes + if (!actor || actor->getNbShapes() < 1) + return nullptr; + + // Get first shape only. + PxShape* shape = nullptr; + actor->getShapes(&shape, 1); + + return shape; + } + +public: + + bool filter(const PxController& a, const PxController& b) override + { + // Early out to avoid crashing + PxShape* shapeA = getShape(a); + if (!shapeA) + return false; + + PxShape* shapeB = getShape(b); + if (!shapeB) + return false; + + // Let triggers through + if (PxFilterObjectIsTrigger(shapeB->getFlags())) + return false; + + // Trigger the contact callback for pairs (A,B) where the filtermask of A contains the ID of B and vice versa + const PxFilterData shapeFilterA = shapeA->getQueryFilterData(); + const PxFilterData shapeFilterB = shapeB->getQueryFilterData(); + if (shapeFilterA.word0 & shapeFilterB.word1) + return true; + + return false; + } +}; + PxQueryFilterCallback* Physics::GetQueryFilterCallback() { static QueryFilter Filter; @@ -229,6 +277,12 @@ PxQueryFilterCallback* Physics::GetCharacterQueryFilterCallback() return &Filter; } +PxControllerFilterCallback* Physics::GetCharacterControllerFilterCallback() +{ + static CharacterControllerFilter Filter; + return &Filter; +} + bool Physics::RayCast(const Vector3& origin, const Vector3& direction, const float maxDistance, uint32 layerMask, bool hitTriggers) { // Prepare data diff --git a/Source/Engine/Physics/Physics.h b/Source/Engine/Physics/Physics.h index 96048b829..6eb38dcd6 100644 --- a/Source/Engine/Physics/Physics.h +++ b/Source/Engine/Physics/Physics.h @@ -112,6 +112,11 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics); /// static PxQueryFilterCallback* GetCharacterQueryFilterCallback(); + /// + /// Gets the default controller filter callback used for the character controller collisions detection. + /// + static PxControllerFilterCallback* GetCharacterControllerFilterCallback(); + /// /// Gets the default physical material. /// diff --git a/Source/Engine/Physics/Types.h b/Source/Engine/Physics/Types.h index 6465dd900..614322488 100644 --- a/Source/Engine/Physics/Types.h +++ b/Source/Engine/Physics/Types.h @@ -39,6 +39,7 @@ namespace physx class PxController; class PxCapsuleController; class PxQueryFilterCallback; + class PxControllerFilterCallback; class PxHeightField; struct PxFilterData; struct PxRaycastHit; From a83d223eec3c2b3fb21b4b9013f25d28470b6a6c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 22 Dec 2021 20:39:45 +0100 Subject: [PATCH 03/25] Fix mouse focus issue with Timeline background --- Source/Editor/GUI/Timeline/GUI/Background.cs | 45 +++++++++++--------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/Source/Editor/GUI/Timeline/GUI/Background.cs b/Source/Editor/GUI/Timeline/GUI/Background.cs index 26c5e65ee..cfe7986c0 100644 --- a/Source/Editor/GUI/Timeline/GUI/Background.cs +++ b/Source/Editor/GUI/Timeline/GUI/Background.cs @@ -16,8 +16,8 @@ namespace FlaxEditor.GUI.Timeline.GUI private readonly Timeline _timeline; private float[] _tickSteps; private float[] _tickStrengths; - private bool _leftMouseDown; - private Vector2 _leftMouseDownPos = Vector2.Minimum; + private bool _isSelecting; + private Vector2 _selectingStartPos = Vector2.Minimum; private Vector2 _mousePos = Vector2.Minimum; /// @@ -33,7 +33,7 @@ namespace FlaxEditor.GUI.Timeline.GUI private void UpdateSelectionRectangle() { - var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos); + var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos); _timeline.OnKeyframesSelection(null, this, selectionRect); } @@ -41,20 +41,17 @@ namespace FlaxEditor.GUI.Timeline.GUI public override bool OnMouseDown(Vector2 location, MouseButton button) { if (base.OnMouseDown(location, button)) - { - _leftMouseDown = false; return true; - } _mousePos = location; if (button == MouseButton.Left) { // Start selecting - _leftMouseDown = true; - _leftMouseDownPos = location; - StartMouseCapture(); + _isSelecting = true; + _selectingStartPos = location; _timeline.OnKeyframesDeselect(null); Focus(); + StartMouseCapture(); return true; } @@ -65,19 +62,16 @@ namespace FlaxEditor.GUI.Timeline.GUI public override bool OnMouseUp(Vector2 location, MouseButton button) { _mousePos = location; - - if (_leftMouseDown && button == MouseButton.Left) + if (_isSelecting && button == MouseButton.Left) { // End selecting - _leftMouseDown = false; + _isSelecting = false; EndMouseCapture(); + return true; } if (base.OnMouseUp(location, button)) - { - _leftMouseDown = false; return true; - } return true; } @@ -88,7 +82,7 @@ namespace FlaxEditor.GUI.Timeline.GUI _mousePos = location; // Selecting - if (_leftMouseDown) + if (_isSelecting) { UpdateSelectionRectangle(); return; @@ -100,11 +94,24 @@ namespace FlaxEditor.GUI.Timeline.GUI /// public override void OnLostFocus() { - _leftMouseDown = false; + if (_isSelecting) + { + _isSelecting = false; + EndMouseCapture(); + } base.OnLostFocus(); } + /// + public override void OnEndMouseCapture() + { + _isSelecting = false; + EndMouseCapture(); + + base.OnEndMouseCapture(); + } + /// public override bool IntersectsContent(ref Vector2 locationParent, out Vector2 location) { @@ -217,9 +224,9 @@ namespace FlaxEditor.GUI.Timeline.GUI } // Draw selection rectangle - if (_leftMouseDown) + if (_isSelecting) { - var selectionRect = Rectangle.FromPoints(_leftMouseDownPos, _mousePos); + var selectionRect = Rectangle.FromPoints(_selectingStartPos, _mousePos); Render2D.FillRectangle(selectionRect, Color.Orange * 0.4f); Render2D.DrawRectangle(selectionRect, Color.Orange); } From 0abb303e8d41e200fbb3a20f10edd4c973a39d2d Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 22 Dec 2021 20:43:59 +0100 Subject: [PATCH 04/25] Add double-click to edit timeline media properties --- Source/Editor/GUI/Timeline/Media.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs index 5fd390817..0df13d1d3 100644 --- a/Source/Editor/GUI/Timeline/Media.cs +++ b/Source/Editor/GUI/Timeline/Media.cs @@ -443,6 +443,20 @@ namespace FlaxEditor.GUI.Timeline return base.OnMouseUp(location, button); } + /// + public override bool OnMouseDoubleClick(Vector2 location, MouseButton button) + { + if (base.OnMouseDoubleClick(location, button)) + return true; + + if (PropertiesEditObject != null) + { + Timeline.ShowEditPopup(PropertiesEditObject, PointToParent(Timeline, location), Track); + return true; + } + return false; + } + /// public override void OnEndMouseCapture() { From 68d8766c56fefa72ebfe97b9e777f96d5becb785 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jan 2022 00:48:29 +0100 Subject: [PATCH 05/25] Optimize `String::ReserveSpace` if length doesn't change --- Source/Engine/Core/Types/String.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Core/Types/String.h b/Source/Engine/Core/Types/String.h index 9b053f8b5..0c2a76083 100644 --- a/Source/Engine/Core/Types/String.h +++ b/Source/Engine/Core/Types/String.h @@ -337,6 +337,8 @@ public: void ReserveSpace(int32 length) { ASSERT(length >= 0); + if (length == _length) + return; Platform::Free(_data); if (length != 0) { From 32a73727b0d273074555fd27cdb93b69bc339997 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jan 2022 01:28:06 +0100 Subject: [PATCH 06/25] Optimize Stream includes to Array --- Source/Engine/Core/Log.cpp | 3 +++ Source/Engine/Serialization/MemoryReadStream.h | 9 +++++---- Source/Engine/Serialization/ReadStream.h | 7 ++++--- Source/Engine/Serialization/Stream.cpp | 2 +- Source/Engine/Serialization/Stream.h | 1 + Source/Engine/Serialization/WriteStream.h | 9 ++++----- 6 files changed, 18 insertions(+), 13 deletions(-) diff --git a/Source/Engine/Core/Log.cpp b/Source/Engine/Core/Log.cpp index f0f2157ca..979133d83 100644 --- a/Source/Engine/Core/Log.cpp +++ b/Source/Engine/Core/Log.cpp @@ -11,6 +11,9 @@ #include "Engine/Serialization/FileWriteStream.h" #include "Engine/Serialization/MemoryWriteStream.h" #include "Engine/Debug/Exceptions/Exceptions.h" +#if USE_EDITOR +#include "Engine/Core/Collections/Array.h" +#endif #include #define LOG_ENABLE_FILE (!PLATFORM_SWITCH) diff --git a/Source/Engine/Serialization/MemoryReadStream.h b/Source/Engine/Serialization/MemoryReadStream.h index 79ee845cb..b05dcece1 100644 --- a/Source/Engine/Serialization/MemoryReadStream.h +++ b/Source/Engine/Serialization/MemoryReadStream.h @@ -3,6 +3,7 @@ #pragma once #include "ReadStream.h" +#include "Engine/Platform/Platform.h" /// /// Super fast advanced data reading from raw bytes without any overhead at all @@ -33,8 +34,8 @@ public: /// Init /// /// Array with data to read from - template - MemoryReadStream(const Array& data) + template + MemoryReadStream(const Array& data) : MemoryReadStream(data.Get(), data.Count() * sizeof(T)) { } @@ -52,8 +53,8 @@ public: /// Init stream to the custom buffer location /// /// Array with data to read from - template - FORCE_INLINE void Init(const Array& data) + template + FORCE_INLINE void Init(const Array& data) { Init(data.Get(), data.Count() * sizeof(T)); } diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index 6b29804cd..f7b77b0d5 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -2,8 +2,8 @@ #pragma once -#include "Engine/Core/Collections/Array.h" #include "Stream.h" +#include "Engine/Core/Templates.h" struct CommonValue; struct Variant; @@ -193,9 +193,10 @@ public: /// Read data array /// /// Array to read - template - void ReadArray(Array* data) + template + void ReadArray(Array* data) { + static_assert(TIsPODType::Value, "Only POD types are valid for ReadArray."); int32 size; ReadInt32(&size); data->Resize(size, false); diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index dafaf3e22..ce1538a36 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -519,7 +519,7 @@ void WriteStream::WriteStringAnsi(const StringAnsiView& data) Write(data.Get(), length); } -void WriteStream::WriteStringAnsi(const StringAnsiView& data, int16 lock) +void WriteStream::WriteStringAnsi(const StringAnsiView& data, int8 lock) { const int32 length = data.Length(); ASSERT(length < STREAM_MAX_STRING_LENGTH); diff --git a/Source/Engine/Serialization/Stream.h b/Source/Engine/Serialization/Stream.h index d988a24df..e107a190f 100644 --- a/Source/Engine/Serialization/Stream.h +++ b/Source/Engine/Serialization/Stream.h @@ -2,6 +2,7 @@ #pragma once +#include "Engine/Core/Core.h" #include "Engine/Core/Types/BaseTypes.h" #define FILESTREAM_BUFFER_SIZE 4096 diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 06e3b3904..94e19fbba 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -2,9 +2,8 @@ #pragma once -#include "Engine/Core/Collections/Array.h" -#include "Engine/Core/Formatting.h" #include "Stream.h" +#include "Engine/Core/Templates.h" struct CommonValue; struct Variant; @@ -211,7 +210,7 @@ public: // Writes Ansi String to the stream // @param data Data to write // @param lock Characters pass in the stream - void WriteStringAnsi(const StringAnsiView& data, int16 lock); + void WriteStringAnsi(const StringAnsiView& data, int8 lock); public: @@ -231,8 +230,8 @@ public: /// Write data array /// /// Array to write - template - void WriteArray(const Array& data) + template + void WriteArray(const Array& data) { static_assert(TIsPODType::Value, "Only POD types are valid for WriteArray."); const int32 size = data.Count(); From 0f9f3905ede6ad1506b4efbe970e5f400581de21 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jan 2022 01:29:31 +0100 Subject: [PATCH 07/25] Add json serialization utils for objects to save/load from raw bytes --- Source/Engine/Serialization/JsonSerializer.cs | 5 +- Source/Engine/Serialization/JsonSerializer.h | 30 +++++++ Source/Engine/Serialization/ReadStream.h | 8 ++ Source/Engine/Serialization/Stream.cpp | 82 +++++++++++++++++++ Source/Engine/Serialization/WriteStream.h | 9 ++ Source/Engine/Utilities/Utils.cs | 40 +++++++++ 6 files changed, 170 insertions(+), 4 deletions(-) create mode 100644 Source/Engine/Serialization/JsonSerializer.h diff --git a/Source/Engine/Serialization/JsonSerializer.cs b/Source/Engine/Serialization/JsonSerializer.cs index 0efb7433d..42d3b9fa7 100644 --- a/Source/Engine/Serialization/JsonSerializer.cs +++ b/Source/Engine/Serialization/JsonSerializer.cs @@ -15,10 +15,7 @@ using Newtonsoft.Json.Serialization; namespace FlaxEngine.Json { - /// - /// Objects serialization tool (json format). - /// - public static class JsonSerializer + partial class JsonSerializer { internal class SerializerCache { diff --git a/Source/Engine/Serialization/JsonSerializer.h b/Source/Engine/Serialization/JsonSerializer.h new file mode 100644 index 000000000..fd831cfc2 --- /dev/null +++ b/Source/Engine/Serialization/JsonSerializer.h @@ -0,0 +1,30 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/ISerializable.h" +#include "Engine/Core/Types/Span.h" +#include "Engine/Core/Collections/Array.h" + +/// +/// Objects serialization tool (json format). +/// +API_CLASS(Static, Namespace="FlaxEngine.Json") class FLAXENGINE_API JsonSerializer +{ + DECLARE_SCRIPTING_TYPE_MINIMAL(JsonSerializer); + + /// + /// Performs object Json serialization to the raw bytes. + /// + /// The object to serialize (can be null). + /// The output data. + API_FUNCTION() static Array SaveToBytes(ISerializable* obj); + + /// + /// Performs object Json deserialization from the raw bytes. + /// + /// The object to deserialize (can be null). + /// The source data to read from. + /// The engine build number of the saved data. Used to resolve old object formats when loading deprecated data. + API_FUNCTION() static void LoadFromBytes(ISerializable* obj, const Span& data, int32 engineBuild); +}; diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index f7b77b0d5..5910ac72d 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -8,6 +8,7 @@ struct CommonValue; struct Variant; struct VariantType; +class ISerializable; /// /// Base class for all data read streams @@ -204,6 +205,13 @@ public: ReadBytes(data->Get(), size * sizeof(T)); } + /// + /// Deserializes object from Json by reading it as a raw data (ver+length+bytes). + /// + /// Reads version number, data length and actual data bytes from the stream. + /// The object to deserialize. + void ReadJson(ISerializable* obj); + public: // [Stream] diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index ce1538a36..067c5e269 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -3,11 +3,16 @@ #include "ReadStream.h" #include "WriteStream.h" #include "JsonWriters.h" +#include "JsonSerializer.h" +#include "MemoryReadStream.h" #include "Engine/Core/Types/CommonValue.h" #include "Engine/Core/Types/Variant.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Content/Asset.h" +#include "Engine/Core/Cache.h" #include "Engine/Debug/DebugLog.h" +#include "Engine/Debug/Exceptions/JsonParseException.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Scripting/ManagedSerialization.h" #include "Engine/Scripting/Scripting.h" #include "Engine/Scripting/ScriptingObject.h" @@ -489,6 +494,29 @@ void ReadStream::ReadVariant(Variant* data) } } +void ReadStream::ReadJson(ISerializable* obj) +{ + int32 engineBuild, size; + ReadInt32(&engineBuild); + ReadInt32(&size); + if (obj) + { + if (const auto memoryStream = dynamic_cast(this)) + { + JsonSerializer::LoadFromBytes(obj, Span((byte*)memoryStream->Read(size), size), engineBuild); + } + else + { + void* data = Allocator::Allocate(size); + ReadBytes(data, size); + JsonSerializer::LoadFromBytes(obj, Span((byte*)data, size), engineBuild); + Allocator::Free(data); + } + } + else + SetPosition(GetPosition() + size); +} + void WriteStream::WriteText(const StringView& text) { for (int32 i = 0; i < text.Length(); i++) @@ -734,3 +762,57 @@ void WriteStream::WriteVariant(const Variant& data) CRASH; } } + +void WriteStream::WriteJson(ISerializable* obj, const void* otherObj) +{ + WriteInt32(FLAXENGINE_VERSION_BUILD); + if (obj) + { + rapidjson_flax::StringBuffer buffer; + CompactJsonWriter writer(buffer); + writer.StartObject(); + obj->Serialize(writer, otherObj); + writer.EndObject(); + + WriteInt32((int32)buffer.GetSize()); + WriteBytes((byte*)buffer.GetString(), (int32)buffer.GetSize()); + } + else + WriteInt32(0); +} + +Array JsonSerializer::SaveToBytes(ISerializable* obj) +{ + Array result; + if (obj) + { + rapidjson_flax::StringBuffer buffer; + CompactJsonWriter writer(buffer); + writer.StartObject(); + obj->Serialize(writer, nullptr); + writer.EndObject(); + result.Set((byte*)buffer.GetString(), (int32)buffer.GetSize()); + } + return result; +} + +void JsonSerializer::LoadFromBytes(ISerializable* obj, const Span& data, int32 engineBuild) +{ + if (!obj || data.Length() == 0) + return; + + ISerializable::SerializeDocument document; + { + PROFILE_CPU_NAMED("Json.Parse"); + document.Parse((const char*)data.Get(), data.Length()); + } + if (document.HasParseError()) + { + Log::JsonParseException(document.GetParseError(), document.GetErrorOffset()); + return; + } + + auto modifier = Cache::ISerializeModifier.Get(); + modifier->EngineBuild = engineBuild; + obj->Deserialize(document, modifier.Value); +} diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 94e19fbba..a189c9714 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -8,6 +8,7 @@ struct CommonValue; struct Variant; struct VariantType; +class ISerializable; /// /// Base class for all data write streams @@ -240,6 +241,14 @@ public: WriteBytes(data.Get(), size * sizeof(T)); } + /// + /// Serializes object to Json and writes it as a raw data (ver+length+bytes). + /// + /// Writes version number, data length and actual data bytes to the stream. + /// The object to serialize. + /// The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties. + void WriteJson(ISerializable* obj, const void* otherObj = nullptr); + public: // [Stream] diff --git a/Source/Engine/Utilities/Utils.cs b/Source/Engine/Utilities/Utils.cs index f0c9d4452..d8e102981 100644 --- a/Source/Engine/Utilities/Utils.cs +++ b/Source/Engine/Utilities/Utils.cs @@ -357,6 +357,26 @@ namespace FlaxEngine return new Matrix(stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle(), stream.ReadSingle()); } + /// + /// Deserializes object from Json by reading it as a raw data (ver+length+bytes). + /// + /// Reads version number, data length and actual data bytes from the stream. + /// The stream. + /// The object to deserialize. + public static void ReadJson(this BinaryReader stream, ISerializable obj) + { + // ReadStream::ReadJson + var engineBuild = stream.ReadInt32(); + var size = stream.ReadInt32(); + if (obj != null) + { + var data = stream.ReadBytes(size); + Json.JsonSerializer.LoadFromBytes(obj, data, engineBuild); + } + else + stream.BaseStream.Seek(size, SeekOrigin.Current); + } + /// /// Writes the color to the binary stream. /// @@ -548,5 +568,25 @@ namespace FlaxEngine stream.Write(value.M43); stream.Write(value.M44); } + + /// + /// Serializes object to Json and writes it as a raw data (ver+length+bytes). + /// + /// The stream. + /// Writes version number, data length and actual data bytes to the stream. + /// The object to serialize. + public static void WriteJson(this BinaryWriter stream, ISerializable obj) + { + // WriteStream::WriteJson + stream.Write(Globals.EngineBuildNumber); + if (obj != null) + { + var bytes = Json.JsonSerializer.SaveToBytes(obj); + stream.Write(bytes.Length); + stream.Write(bytes); + } + else + stream.Write(0); + } } } From bc521978ce52c2a94c0c224f1a066ffb20c294d4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jan 2022 15:11:50 +0100 Subject: [PATCH 08/25] Fix undo modifications notify from nested `SyncPointEditor` --- Source/Editor/CustomEditors/SyncPointEditor.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Source/Editor/CustomEditors/SyncPointEditor.cs b/Source/Editor/CustomEditors/SyncPointEditor.cs index 3555aaea4..8d8b85f2a 100644 --- a/Source/Editor/CustomEditors/SyncPointEditor.cs +++ b/Source/Editor/CustomEditors/SyncPointEditor.cs @@ -102,6 +102,16 @@ namespace FlaxEditor.CustomEditors /// protected virtual void OnModified() { + var parent = ParentEditor; + while (parent != null) + { + if (parent is SyncPointEditor syncPointEditor) + { + syncPointEditor.OnModified(); + break; + } + parent = parent.ParentEditor; + } } /// From 5e9b215548c1146b56a80f240b7d218e0cdffc04 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jan 2022 15:41:47 +0100 Subject: [PATCH 09/25] Fix crash when spawning managed object on a detached native thread --- Source/Engine/Scripting/ScriptingObject.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index c5c6b9dab..ddfb0fca5 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -14,7 +14,7 @@ #include "ManagedCLR/MClass.h" #include "ManagedCLR/MUtils.h" #include "ManagedCLR/MField.h" -#if PLATFORM_LINUX +#if PLATFORM_LINUX || USE_MONO #include "ManagedCLR/MCore.h" #endif #include "FlaxEngine.Gen.h" @@ -235,8 +235,16 @@ MonoObject* ScriptingObject::CreateManagedInternal() return nullptr; } + // Ensure to have managed domain attached (this can be called from custom native thread, eg. content loader) + auto domain = mono_domain_get(); + if (!domain) + { + MCore::AttachThread(); + domain = mono_domain_get(); + } + // Allocate managed instance - MonoObject* managedInstance = mono_object_new(mono_domain_get(), monoClass->GetNative()); + MonoObject* managedInstance = mono_object_new(domain, monoClass->GetNative()); if (managedInstance == nullptr) { LOG(Warning, "Failed to create new instance of the object of type {0}", String(monoClass->GetFullName())); From 32c7d36fcdc5fa71efe83b8ad6fb295188ed50c1 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 2 Jan 2022 15:50:37 +0100 Subject: [PATCH 10/25] Tweaks --- Source/Engine/Serialization/ReadStream.h | 11 ----------- Source/Engine/Serialization/WriteStream.h | 11 ----------- 2 files changed, 22 deletions(-) diff --git a/Source/Engine/Serialization/ReadStream.h b/Source/Engine/Serialization/ReadStream.h index 5910ac72d..c1dc96695 100644 --- a/Source/Engine/Serialization/ReadStream.h +++ b/Source/Engine/Serialization/ReadStream.h @@ -15,15 +15,6 @@ class ISerializable; /// class FLAXENGINE_API ReadStream : public Stream { -public: - - /// - /// Virtual destructor - /// - virtual ~ReadStream() - { - } - public: /// @@ -33,8 +24,6 @@ public: /// Amount of bytes to read virtual void ReadBytes(void* data, uint32 bytes) = 0; -public: - template FORCE_INLINE void Read(T* data) { diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index a189c9714..43bda26e4 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -15,15 +15,6 @@ class ISerializable; /// class FLAXENGINE_API WriteStream : public Stream { -public: - - /// - /// Virtual destructor - /// - virtual ~WriteStream() - { - } - public: /// @@ -33,8 +24,6 @@ public: /// Amount of bytes to write virtual void WriteBytes(const void* data, uint32 bytes) = 0; -public: - template FORCE_INLINE void Write(const T* data) { From fa838694309f50c28ed67396be92146bb1d2925c Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Jan 2022 23:56:54 +0100 Subject: [PATCH 11/25] Add minor changes --- Source/Editor/GUI/Timeline/Media.cs | 4 ++-- Source/Editor/GUI/Timeline/Timeline.cs | 8 ++++---- Source/Editor/Scripting/ScriptsBuilder.h | 1 - Source/Engine/Animations/AnimationUtils.h | 8 +++++++- Source/Engine/Content/Assets/Animation.h | 2 +- Source/Engine/Level/Level.cpp | 6 +++--- .../Attributes/Editor/TypeReferenceAttribute.cs | 2 +- Source/Engine/Serialization/JsonSerializer.h | 11 +++++++++++ 8 files changed, 29 insertions(+), 13 deletions(-) diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs index 0df13d1d3..f902345fa 100644 --- a/Source/Editor/GUI/Timeline/Media.cs +++ b/Source/Editor/GUI/Timeline/Media.cs @@ -91,7 +91,7 @@ namespace FlaxEditor.GUI.Timeline /// The media. protected ProxyBase(TTrack track, TMedia media) { - Track = track ?? throw new ArgumentNullException(nameof(track)); + Track = track; Media = media ?? throw new ArgumentNullException(nameof(media)); } } @@ -341,7 +341,7 @@ namespace FlaxEditor.GUI.Timeline var style = Style.Current; var bounds = new Rectangle(Vector2.Zero, Size); - var fillColor = style.Background * 1.5f; + var fillColor = BackgroundColor.A > 0.0f ? BackgroundColor : style.Background * 1.5f; Render2D.FillRectangle(bounds, fillColor); var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge; diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index 0067d8010..e20ae0a69 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -1898,16 +1898,16 @@ namespace FlaxEditor.GUI.Timeline media.OnTimelineShowContextMenu(menu, controlUnderMouse); if (media.PropertiesEditObject != null) { - menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, ref location, media.Track)); + menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, location, media.Track)); } } if (PropertiesEditObject != null) { - menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, ref location, this)); + menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, location, this)); } if (_tracks.Count > 1) { - menu.AddButton("Sort tracks", SortTracks).TooltipText = "Sorts sub tracks alphabetically"; + menu.AddButton("Sort tracks", SortTracks).TooltipText = "Sorts tracks alphabetically"; } menu.AddSeparator(); menu.AddButton("Reset zoom", () => Zoom = 1.0f); @@ -2089,7 +2089,7 @@ namespace FlaxEditor.GUI.Timeline /// The object. /// The show location (in timeline space). /// The undo context object. - protected virtual void ShowEditPopup(object obj, ref Vector2 location, object undoContext = null) + public virtual void ShowEditPopup(object obj, Vector2 location, object undoContext = null) { var popup = new PropertiesEditPopup(this, obj, undoContext); popup.Show(this, location); diff --git a/Source/Editor/Scripting/ScriptsBuilder.h b/Source/Editor/Scripting/ScriptsBuilder.h index d72f4bcb3..7597ffdef 100644 --- a/Source/Editor/Scripting/ScriptsBuilder.h +++ b/Source/Editor/Scripting/ScriptsBuilder.h @@ -4,7 +4,6 @@ #include "Engine/Core/Delegate.h" #include "Engine/Core/Types/String.h" -#include "Engine/Core/Collections/Array.h" #include "Engine/Scripting/ScriptingType.h" /// diff --git a/Source/Engine/Animations/AnimationUtils.h b/Source/Engine/Animations/AnimationUtils.h index c73753964..5afe6bd1b 100644 --- a/Source/Engine/Animations/AnimationUtils.h +++ b/Source/Engine/Animations/AnimationUtils.h @@ -14,7 +14,13 @@ namespace AnimationUtils template FORCE_INLINE static T GetZero() { - return 0.0f; + return T(); + } + + template<> + FORCE_INLINE int32 GetZero() + { + return 0; } template<> diff --git a/Source/Engine/Content/Assets/Animation.h b/Source/Engine/Content/Assets/Animation.h index 0bdb9bdd4..950b7a45a 100644 --- a/Source/Engine/Content/Assets/Animation.h +++ b/Source/Engine/Content/Assets/Animation.h @@ -18,7 +18,7 @@ DECLARE_BINARY_ASSET_HEADER(Animation, 1); /// /// Contains basic information about the animation asset contents. /// - API_STRUCT() struct InfoData + API_STRUCT() struct FLAXENGINE_API InfoData { DECLARE_SCRIPTING_TYPE_NO_SPAWN(InfoData); diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5c13fc402..89d43046d 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -605,9 +605,9 @@ public: ScriptingObjectReference ParentActor; SpawnActorAction(Actor* actor, Actor* parent) + : TargetActor(actor) + , ParentActor(parent) { - TargetActor = actor; - ParentActor = parent; } bool Do() const override @@ -623,8 +623,8 @@ public: ScriptingObjectReference TargetActor; DeleteActorAction(Actor* actor) + : TargetActor(actor) { - TargetActor = actor; } bool Do() const override diff --git a/Source/Engine/Scripting/Attributes/Editor/TypeReferenceAttribute.cs b/Source/Engine/Scripting/Attributes/Editor/TypeReferenceAttribute.cs index 94ba188d8..448db8fc6 100644 --- a/Source/Engine/Scripting/Attributes/Editor/TypeReferenceAttribute.cs +++ b/Source/Engine/Scripting/Attributes/Editor/TypeReferenceAttribute.cs @@ -5,7 +5,7 @@ using System; namespace FlaxEngine { /// - /// Specifies a options for an type reference picker in the editor. Allows to customize view or provide custom value assign policy (eg/ restrict types to inherit from a given type). + /// Specifies a options for an type reference picker in the editor. Allows to customize view or provide custom value assign policy (eg. restrict types to inherit from a given type). /// /// [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)] diff --git a/Source/Engine/Serialization/JsonSerializer.h b/Source/Engine/Serialization/JsonSerializer.h index fd831cfc2..de73026a5 100644 --- a/Source/Engine/Serialization/JsonSerializer.h +++ b/Source/Engine/Serialization/JsonSerializer.h @@ -20,6 +20,17 @@ API_CLASS(Static, Namespace="FlaxEngine.Json") class FLAXENGINE_API JsonSerializ /// The output data. API_FUNCTION() static Array SaveToBytes(ISerializable* obj); + /// + /// Performs object Json deserialization from the raw bytes. + /// + /// The object to deserialize (can be null). + /// The source data to read from. + /// The engine build number of the saved data. Used to resolve old object formats when loading deprecated data. + FORCE_INLINE static void LoadFromBytes(ISerializable* obj, const Array& data, int32 engineBuild) + { + LoadFromBytes(obj, Span(data.Get(), data.Count()), engineBuild); + } + /// /// Performs object Json deserialization from the raw bytes. /// From ff9b1165221c07ae47a7b3ca94750652a47f95e0 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Tue, 4 Jan 2022 23:58:39 +0100 Subject: [PATCH 12/25] Add `ScriptingObject::NewObject` utility for scripting objects spawning in C++ --- Source/Engine/Scripting/ScriptingObject.cpp | 11 +++++++++++ Source/Engine/Scripting/ScriptingObject.h | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index ddfb0fca5..b8775f941 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -82,6 +82,17 @@ MClass* ScriptingObject::GetClass() const return _type ? _type.GetType().ManagedClass : nullptr; } +ScriptingObject* ScriptingObject::NewObject(const ScriptingTypeHandle& typeHandle) +{ + if (!typeHandle) + return nullptr; + auto& type = typeHandle.GetType(); + if (type.Type != ScriptingTypes::Script) + return nullptr; + const ScriptingObjectSpawnParams params(Guid::New(), typeHandle); + return type.Script.Spawn(params); +} + ScriptingObject* ScriptingObject::FromInterface(void* interfaceObj, const ScriptingTypeHandle& interfaceType) { if (!interfaceObj || !interfaceType) diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index b29078d45..a0114b1a4 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -108,6 +108,13 @@ public: public: + static ScriptingObject* NewObject(const ScriptingTypeHandle& typeHandle); + template + static T* NewObject() + { + return (T*)NewObject(T::TypeInitializer); + } + // Tries to cast native interface object to scripting object instance. Returns null if fails. static ScriptingObject* FromInterface(void* interfaceObj, const ScriptingTypeHandle& interfaceType); static void* ToInterface(ScriptingObject* obj, const ScriptingTypeHandle& interfaceType); From f4bbf0e3487731ae727a8d7554171017b06d55c4 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Jan 2022 14:39:37 +0100 Subject: [PATCH 13/25] Add timeline media selection and editing to be global in sync with keyframes editors --- Source/Editor/GUI/Timeline/Media.cs | 61 ++++- Source/Editor/GUI/Timeline/Timeline.cs | 338 +++++++++++++++++-------- Source/Editor/GUI/Timeline/Track.cs | 6 +- 3 files changed, 289 insertions(+), 116 deletions(-) diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs index f902345fa..0f0025f76 100644 --- a/Source/Editor/GUI/Timeline/Media.cs +++ b/Source/Editor/GUI/Timeline/Media.cs @@ -215,7 +215,7 @@ namespace FlaxEditor.GUI.Timeline /// /// Gets a value indicating whether this media can be resized (duration changed). /// - public bool CanResize; + public bool CanResize = true; /// /// Initializes a new instance of the class. @@ -347,8 +347,9 @@ namespace FlaxEditor.GUI.Timeline var isMovingWholeMedia = _isMoving && !_startMoveRightEdge && !_startMoveLeftEdge; var borderHighlightColor = style.BorderHighlighted; var moveColor = style.ProgressNormal; + var selectedColor = style.BackgroundSelected; var moveThickness = 2.0f; - var borderColor = isMovingWholeMedia ? moveColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal); + var borderColor = isMovingWholeMedia ? moveColor : (Timeline.SelectedMedia.Contains(this) ? selectedColor : (IsMouseOver ? borderHighlightColor : style.BorderNormal)); Render2D.DrawRectangle(bounds, borderColor, isMovingWholeMedia ? moveThickness : 1.0f); if (_startMoveLeftEdge) { @@ -384,9 +385,25 @@ namespace FlaxEditor.GUI.Timeline _startMoveDuration = DurationFrames; _startMoveLeftEdge = MoveLeftEdgeRect.Contains(ref location) && CanResize; _startMoveRightEdge = MoveRightEdgeRect.Contains(ref location) && CanResize; - StartMouseCapture(true); + if (_startMoveLeftEdge || _startMoveRightEdge) + return true; + if (Root.GetKey(KeyboardKeys.Control)) + { + // Add/Remove selection + if (_timeline.SelectedMedia.Contains(this)) + _timeline.Deselect(this); + else + _timeline.Select(this, true); + } + else + { + // Select (additive for the move) + _timeline.Select(this, true); + } + + _timeline.OnKeyframesMove(null, this, location, true, false); return true; } @@ -417,7 +434,8 @@ namespace FlaxEditor.GUI.Timeline } else { - StartFrame = _startMoveStartFrame + moveDelta; + // Move with global timeline selection + _timeline.OnKeyframesMove(null, this, location, false, false); } if (StartFrame != startFrame || DurationFrames != durationFrames) @@ -436,6 +454,15 @@ namespace FlaxEditor.GUI.Timeline { if (button == MouseButton.Left && _isMoving) { + if (!_startMoveLeftEdge && !_startMoveRightEdge && !Root.GetKey(KeyboardKeys.Control)) + { + var moveLocationDelta = Root.MousePosition - _startMoveLocation; + if (moveLocationDelta.Length < 4.0f) + { + // No move so just select itself + _timeline.Select(this); + } + } EndMoving(); return true; } @@ -501,19 +528,27 @@ namespace FlaxEditor.GUI.Timeline _startMoveLeftEdge = false; _startMoveRightEdge = false; - // Re-assign the media start/duration inside the undo recording block - if (_startMoveStartFrame != _startFrame || _startMoveDuration != _durationFrames) + if (_startMoveLeftEdge || _startMoveRightEdge) { - var endMoveStartFrame = _startFrame; - var endMoveDuration = _durationFrames; - _startFrame = _startMoveStartFrame; - _durationFrames = _startMoveDuration; - using (new TrackUndoBlock(_tack)) + // Re-assign the media start/duration inside the undo recording block + if (_startMoveStartFrame != _startFrame || _startMoveDuration != _durationFrames) { - _startFrame = endMoveStartFrame; - _durationFrames = endMoveDuration; + var endMoveStartFrame = _startFrame; + var endMoveDuration = _durationFrames; + _startFrame = _startMoveStartFrame; + _durationFrames = _startMoveDuration; + using (new TrackUndoBlock(_tack)) + { + _startFrame = endMoveStartFrame; + _durationFrames = endMoveDuration; + } } } + else + { + // Global timeline selection moving end + _timeline.OnKeyframesMove(null, this, _mouseLocation, false, true); + } EndMouseCapture(); } diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index e20ae0a69..a89df1fc2 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -219,6 +219,10 @@ namespace FlaxEditor.GUI.Timeline private bool _isRightMouseButtonDown; private Vector2 _rightMouseButtonDownPos; private Vector2 _rightMouseButtonMovePos; + private Vector2 _mediaMoveStartPos; + private int[] _mediaMoveStartFrames; + private List _mediaMoveStartTracks; + private byte[][] _mediaMoveStartData; private float _zoom = 1.0f; private bool _isMovingPositionHandle; private bool _canPlayPause = true, _canStop = true; @@ -1375,7 +1379,7 @@ namespace FlaxEditor.GUI.Timeline if (!addToSelection) { SelectedTracks.Clear(); - SelectedMedia.Clear(); + OnKeyframesDeselect(null); } SelectedMedia.Add(media); OnSelectionChanged(); @@ -1425,47 +1429,40 @@ namespace FlaxEditor.GUI.Timeline } /// - /// Deletes the selected tracks/media events. + /// Deletes the selected tracks. /// /// True if use undo/redo action for track removing. - public void DeleteSelection(bool withUndo = true) + public void DeleteSelectedTracks(bool withUndo = true) { - if (SelectedMedia.Count > 0) + if (SelectedTracks.Count == 0) + return; + var tracks = new List(SelectedTracks.Count); + for (int i = 0; i < SelectedTracks.Count; i++) { - throw new NotImplementedException("TODO: removing selected media events"); + GetTracks(SelectedTracks[i], tracks); } - - if (SelectedTracks.Count > 0) + SelectedTracks.Clear(); + if (withUndo && Undo != null && Undo.Enabled) { - // Delete selected tracks - var tracks = new List(SelectedTracks.Count); - for (int i = 0; i < SelectedTracks.Count; i++) + if (tracks.Count == 1) { - GetTracks(SelectedTracks[i], tracks); + Undo.AddAction(new AddRemoveTrackAction(this, tracks[0], false)); } - SelectedTracks.Clear(); - if (withUndo && Undo != null && Undo.Enabled) + else { - if (tracks.Count == 1) - { - Undo.AddAction(new AddRemoveTrackAction(this, tracks[0], false)); - } - else - { - var actions = new List(); - for (int i = tracks.Count - 1; i >= 0; i--) - actions.Add(new AddRemoveTrackAction(this, tracks[i], false)); - Undo.AddAction(new MultiUndoAction(actions, "Remove tracks")); - } + var actions = new List(); + for (int i = tracks.Count - 1; i >= 0; i--) + actions.Add(new AddRemoveTrackAction(this, tracks[i], false)); + Undo.AddAction(new MultiUndoAction(actions, "Remove tracks")); } - for (int i = tracks.Count - 1; i >= 0; i--) - { - tracks[i].ParentTrack = null; - OnDeleteTrack(tracks[i]); - } - OnTracksChanged(); - MarkAsEdited(); } + for (int i = tracks.Count - 1; i >= 0; i--) + { + tracks[i].ParentTrack = null; + OnDeleteTrack(tracks[i]); + } + OnTracksChanged(); + MarkAsEdited(); } /// @@ -1539,6 +1536,32 @@ namespace FlaxEditor.GUI.Timeline MarkAsEdited(); } + /// + /// Adds the media. + /// + /// The track to add media to. + /// The media to add. + /// True if use undo/redo action for media adding. + public void AddMedia(Track track, Media media, bool withUndo = true) + { + if (track == null || media == null) + throw new ArgumentNullException(); + if (media.Track != null) + throw new InvalidOperationException(); + if (withUndo && Undo != null && Undo.Enabled) + { + var before = EditTrackAction.CaptureData(track); + track.AddMedia(media); + var after = EditTrackAction.CaptureData(track); + Undo.AddAction(new EditTrackAction(this, track, before, after)); + } + else + { + track.AddMedia(media); + } + MarkAsEdited(); + } + /// /// Called to delete media. /// @@ -1551,89 +1574,82 @@ namespace FlaxEditor.GUI.Timeline } /// - /// Duplicates the selected tracks/media events. + /// Duplicates the selected tracks. /// /// True if use undo/redo action for track duplication. - public void DuplicateSelection(bool withUndo = true) + public void DuplicateSelectedTracks(bool withUndo = true) { - if (SelectedMedia.Count > 0) + if (SelectedTracks.Count == 0) + return; + var tracks = new List(SelectedTracks.Count); + for (int i = 0; i < SelectedTracks.Count; i++) { - throw new NotImplementedException("TODO: duplicating selected media events"); + GetTracks(SelectedTracks[i], tracks); } - - if (SelectedTracks.Count > 0) + var clones = new Track[tracks.Count]; + for (int i = 0; i < tracks.Count; i++) { - // Duplicate selected tracks - var tracks = new List(SelectedTracks.Count); - for (int i = 0; i < SelectedTracks.Count; i++) + var track = tracks[i]; + var options = new TrackCreateOptions { - GetTracks(SelectedTracks[i], tracks); + Archetype = track.Archetype, + Flags = track.Flags, + }; + var clone = options.Archetype.Create(options); + clone.Name = track.CanRename ? GetValidTrackName(track.Name) : track.Name; + clone.Color = track.Color; + clone.IsExpanded = track.IsExpanded; + byte[] data; + using (var memory = new MemoryStream(512)) + using (var stream = new BinaryWriter(memory)) + { + // TODO: reuse memory stream to improve tracks duplication performance + options.Archetype.Save(track, stream); + data = memory.ToArray(); } - var clones = new Track[tracks.Count]; - for (int i = 0; i < tracks.Count; i++) + using (var memory = new MemoryStream(data)) + using (var stream = new BinaryReader(memory)) { - var track = tracks[i]; - var options = new TrackCreateOptions + track.Archetype.Load(Timeline.FormatVersion, clone, stream); + } + var trackParent = track.ParentTrack; + var trackIndex = track.TrackIndex + 1; + if (trackParent != null && tracks.Contains(trackParent)) + { + for (int j = 0; j < i; j++) { - Archetype = track.Archetype, - Flags = track.Flags, - }; - var clone = options.Archetype.Create(options); - clone.Name = track.CanRename ? GetValidTrackName(track.Name) : track.Name; - clone.Color = track.Color; - clone.IsExpanded = track.IsExpanded; - byte[] data; - using (var memory = new MemoryStream(512)) - using (var stream = new BinaryWriter(memory)) - { - // TODO: reuse memory stream to improve tracks duplication performance - options.Archetype.Save(track, stream); - data = memory.ToArray(); - } - using (var memory = new MemoryStream(data)) - using (var stream = new BinaryReader(memory)) - { - track.Archetype.Load(Timeline.FormatVersion, clone, stream); - } - var trackParent = track.ParentTrack; - var trackIndex = track.TrackIndex + 1; - if (trackParent != null && tracks.Contains(trackParent)) - { - for (int j = 0; j < i; j++) + if (tracks[j] == trackParent) { - if (tracks[j] == trackParent) - { - trackParent = clones[j]; - break; - } + trackParent = clones[j]; + break; } - trackIndex--; } - clone.ParentTrack = trackParent; - clone.TrackIndex = trackIndex; - track.OnDuplicated(clone); - AddTrack(clone, false); - clones[i] = clone; + trackIndex--; } - OnTracksOrderChanged(); - if (withUndo && Undo != null && Undo.Enabled) - { - if (clones.Length == 1) - { - Undo.AddAction(new AddRemoveTrackAction(this, clones[0], true)); - } - else - { - var actions = new List(); - for (int i = 0; i < clones.Length; i++) - actions.Add(new AddRemoveTrackAction(this, clones[i], true)); - Undo.AddAction(new MultiUndoAction(actions, "Remove tracks")); - } - } - OnTracksChanged(); - MarkAsEdited(); - SelectedTracks[0].Focus(); + clone.ParentTrack = trackParent; + clone.TrackIndex = trackIndex; + track.OnDuplicated(clone); + AddTrack(clone, false); + clones[i] = clone; } + OnTracksOrderChanged(); + if (withUndo && Undo != null && Undo.Enabled) + { + if (clones.Length == 1) + { + Undo.AddAction(new AddRemoveTrackAction(this, clones[0], true)); + } + else + { + var actions = new List(); + for (int i = 0; i < clones.Length; i++) + actions.Add(new AddRemoveTrackAction(this, clones[i], true)); + Undo.AddAction(new MultiUndoAction(actions, "Remove tracks")); + } + } + OnTracksChanged(); + MarkAsEdited(); + SelectedTracks[0].Focus(); } /// @@ -2067,6 +2083,9 @@ namespace FlaxEditor.GUI.Timeline case KeyboardKeys.S: Split(CurrentFrame); return true; + case KeyboardKeys.Delete: + OnKeyframesDelete(null); + return true; } return false; @@ -2145,6 +2164,11 @@ namespace FlaxEditor.GUI.Timeline if (_tracks[i] is IKeyframesEditorContext trackContext) trackContext.OnKeyframesDeselect(editor); } + if (SelectedMedia.Count != 0) + { + SelectedMedia.Clear(); + OnSelectionChanged(); + } } /// @@ -2152,17 +2176,38 @@ namespace FlaxEditor.GUI.Timeline { var globalControl = _backgroundArea; var globalRect = Rectangle.FromPoints(control.PointToParent(globalControl, selection.UpperLeft), control.PointToParent(globalControl, selection.BottomRight)); + var mediaControl = MediaPanel; + var mediaRect = Rectangle.FromPoints(mediaControl.PointFromParent(globalRect.UpperLeft), mediaControl.PointFromParent(globalRect.BottomRight)); + var selectionChanged = false; + if (SelectedMedia.Count != 0) + { + SelectedMedia.Clear(); + selectionChanged = true; + } for (int i = 0; i < _tracks.Count; i++) { if (_tracks[i] is IKeyframesEditorContext trackContext) trackContext.OnKeyframesSelection(editor, globalControl, globalRect); + + foreach (var media in _tracks[i].Media) + { + if (media.Bounds.Intersects(ref mediaRect)) + { + SelectedMedia.Add(media); + selectionChanged = true; + } + } + } + if (selectionChanged) + { + OnSelectionChanged(); } } /// public int OnKeyframesSelectionCount() { - int result = 0; + int result = SelectedMedia.Count; for (int i = 0; i < _tracks.Count; i++) { if (_tracks[i] is IKeyframesEditorContext trackContext) @@ -2179,6 +2224,45 @@ namespace FlaxEditor.GUI.Timeline if (_tracks[i] is IKeyframesEditorContext trackContext) trackContext.OnKeyframesDelete(editor); } + + // Delete selected media events + if (SelectedMedia.Count != 0) + { + if (Undo != null && Undo.Enabled) + { + // Undo per-track + if (_mediaMoveStartTracks == null) + _mediaMoveStartTracks = new List(); + else + _mediaMoveStartTracks.Clear(); + for (var i = 0; i < SelectedMedia.Count; i++) + { + var media = SelectedMedia[i]; + if (!_mediaMoveStartTracks.Contains(media.Track)) + _mediaMoveStartTracks.Add(media.Track); + } + _mediaMoveStartData = new byte[_mediaMoveStartTracks.Count][]; + for (int i = 0; i < _mediaMoveStartData.Length; i++) + _mediaMoveStartData[i] = EditTrackAction.CaptureData(_mediaMoveStartTracks[i]); + } + + foreach (var media in SelectedMedia.ToArray()) + OnDeleteMedia(media); + + if (Undo != null && Undo.Enabled) + { + for (int i = 0; i < _mediaMoveStartData.Length; i++) + { + var track = _mediaMoveStartTracks[i]; + var before = _mediaMoveStartData[i]; + var after = EditTrackAction.CaptureData(track); + if (!Utils.ArraysEqual(before, after)) + AddBatchedUndoAction(new EditTrackAction(this, track, before, after)); + } + } + + MarkAsEdited(); + } } /// @@ -2190,6 +2274,60 @@ namespace FlaxEditor.GUI.Timeline if (_tracks[i] is IKeyframesEditorContext trackContext) trackContext.OnKeyframesMove(editor, _backgroundArea, location, start, end); } + if (SelectedMedia.Count != 0) + { + location = MediaPanel.PointFromParent(location); + if (start) + { + // Start moving selected media events + _mediaMoveStartPos = location; + _mediaMoveStartFrames = new int[SelectedMedia.Count]; + if (_mediaMoveStartTracks == null) + _mediaMoveStartTracks = new List(); + else + _mediaMoveStartTracks.Clear(); + for (var i = 0; i < SelectedMedia.Count; i++) + { + var media = SelectedMedia[i]; + _mediaMoveStartFrames[i] = media.StartFrame; + if (!_mediaMoveStartTracks.Contains(media.Track)) + _mediaMoveStartTracks.Add(media.Track); + } + if (Undo != null && Undo.Enabled) + { + // Undo per-track + _mediaMoveStartData = new byte[_mediaMoveStartTracks.Count][]; + for (int i = 0; i < _mediaMoveStartData.Length; i++) + _mediaMoveStartData[i] = EditTrackAction.CaptureData(_mediaMoveStartTracks[i]); + } + } + else if (end) + { + // End moving selected media events + if (_mediaMoveStartData != null) + { + for (int i = 0; i < _mediaMoveStartData.Length; i++) + { + var track = _mediaMoveStartTracks[i]; + var before = _mediaMoveStartData[i]; + var after = EditTrackAction.CaptureData(track); + if (!Utils.ArraysEqual(before, after)) + AddBatchedUndoAction(new EditTrackAction(this, track, before, after)); + } + } + MarkAsEdited(); + _mediaMoveStartTracks.Clear(); + _mediaMoveStartFrames = null; + } + else + { + // Move selected media events + var moveLocationDelta = location - _mediaMoveStartPos; + var moveDelta = (int)(moveLocationDelta.X / (UnitsPerSecond * Zoom) * FramesPerSecond); + for (var i = 0; i < SelectedMedia.Count; i++) + SelectedMedia[i].StartFrame = _mediaMoveStartFrames[i] + moveDelta; + } + } } /// diff --git a/Source/Editor/GUI/Timeline/Track.cs b/Source/Editor/GUI/Timeline/Track.cs index 6541444b2..935ab863a 100644 --- a/Source/Editor/GUI/Timeline/Track.cs +++ b/Source/Editor/GUI/Timeline/Track.cs @@ -1051,7 +1051,7 @@ namespace FlaxEditor.GUI.Timeline if (CanRename) menu.AddButton("Rename", "F2", StartRenaming); if (CanCopyPaste) - menu.AddButton("Duplicate", "Ctrl+D", () => Timeline.DuplicateSelection()); + menu.AddButton("Duplicate", "Ctrl+D", () => Timeline.DuplicateSelectedTracks()); menu.AddButton("Delete", "Del", Delete); if (CanExpand) { @@ -1292,12 +1292,12 @@ namespace FlaxEditor.GUI.Timeline StartRenaming(); return true; case KeyboardKeys.Delete: - _timeline.DeleteSelection(); + _timeline.DeleteSelectedTracks(); return true; case KeyboardKeys.D: if (Root.GetKey(KeyboardKeys.Control) && CanCopyPaste) { - _timeline.DuplicateSelection(); + _timeline.DuplicateSelectedTracks(); return true; } break; From 751916151a2cce46663a6352bfcda94ce440efa2 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Jan 2022 14:40:27 +0100 Subject: [PATCH 14/25] Add Loading text info for particle system and animation timelines --- Source/Editor/Windows/Assets/AnimationWindow.cs | 2 ++ Source/Editor/Windows/Assets/ParticleSystemWindow.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Source/Editor/Windows/Assets/AnimationWindow.cs b/Source/Editor/Windows/Assets/AnimationWindow.cs index 12dd4a848..088aceadd 100644 --- a/Source/Editor/Windows/Assets/AnimationWindow.cs +++ b/Source/Editor/Windows/Assets/AnimationWindow.cs @@ -254,6 +254,7 @@ namespace FlaxEditor.Windows.Assets Enabled = false }; _timeline.Modified += MarkAsEdited; + _timeline.SetNoTracksText("Loading..."); // Asset properties _propertiesPresenter = new CustomEditorPresenter(null); @@ -361,6 +362,7 @@ namespace FlaxEditor.Windows.Assets _timeline.Load(_asset); _undo.Clear(); _timeline.Enabled = true; + _timeline.SetNoTracksText(null); ClearEditedFlag(); } } diff --git a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs index acc3c658a..189f737c6 100644 --- a/Source/Editor/Windows/Assets/ParticleSystemWindow.cs +++ b/Source/Editor/Windows/Assets/ParticleSystemWindow.cs @@ -347,6 +347,7 @@ namespace FlaxEditor.Windows.Assets }; _timeline.Modified += OnTimelineModified; _timeline.SelectionChanged += OnTimelineSelectionChanged; + _timeline.SetNoTracksText("Loading..."); // Properties editor var propertiesEditor = new CustomEditorPresenter(_undo, string.Empty); @@ -527,6 +528,7 @@ namespace FlaxEditor.Windows.Assets // Setup _undo.Clear(); _timeline.Enabled = true; + _timeline.SetNoTracksText(null); _propertiesEditor.Select(new GeneralProxy(this)); ClearEditedFlag(); } From 779e41a6861d5de76ee5935570682e68f30feecd Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Thu, 6 Jan 2022 14:40:55 +0100 Subject: [PATCH 15/25] Add static assert for curve keyframes data raw serialization --- Source/Engine/Animations/CurveSerialization.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Engine/Animations/CurveSerialization.h b/Source/Engine/Animations/CurveSerialization.h index 4af89ceca..91e53de0a 100644 --- a/Source/Engine/Animations/CurveSerialization.h +++ b/Source/Engine/Animations/CurveSerialization.h @@ -186,6 +186,7 @@ namespace Serialization // Raw keyframes data stream.WriteInt32(keyframes.Count()); + static_assert(TIsPODType::Value, "Raw bytes serialization only for raw POD types."); stream.WriteBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); } @@ -209,6 +210,7 @@ namespace Serialization int32 keyframesCount; stream.ReadInt32(&keyframesCount); keyframes.Resize(keyframesCount, false); + static_assert(TIsPODType::Value, "Raw bytes serialization only for raw POD types."); stream.ReadBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame)); return false; From bbfe0446f0e10858a599e3ca97fc5d3387910fbe Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Jan 2022 12:08:52 +0100 Subject: [PATCH 16/25] Tweaks and fixes --- Source/Editor/GUI/Timeline/Media.cs | 6 +++--- Source/Editor/Managed/ManagedEditor.cpp | 5 ----- Source/Editor/Managed/ManagedEditor.h | 1 - Source/Editor/Viewport/Previews/AnimationPreview.cs | 2 +- Source/Engine/Animations/Curve.h | 6 ++++++ Source/Engine/Animations/Graph/AnimGraph.h | 2 +- Source/Engine/Graphics/RenderBuffers.cpp | 5 ----- Source/Engine/Graphics/RenderBuffers.h | 5 ----- 8 files changed, 11 insertions(+), 21 deletions(-) diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs index 0f0025f76..70783b449 100644 --- a/Source/Editor/GUI/Timeline/Media.cs +++ b/Source/Editor/GUI/Timeline/Media.cs @@ -525,11 +525,11 @@ namespace FlaxEditor.GUI.Timeline private void EndMoving() { _isMoving = false; - _startMoveLeftEdge = false; - _startMoveRightEdge = false; - if (_startMoveLeftEdge || _startMoveRightEdge) { + _startMoveLeftEdge = false; + _startMoveRightEdge = false; + // Re-assign the media start/duration inside the undo recording block if (_startMoveStartFrame != _startFrame || _startMoveDuration != _durationFrames) { diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index 4ac4709a3..b37ebf132 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -476,11 +476,6 @@ void ManagedEditor::OnEditorAssemblyLoaded(MAssembly* assembly) CreateManaged(); } -String ManagedEditor::ToString() const -{ - return TEXT("ManagedEditor"); -} - void ManagedEditor::DestroyManaged() { // Ensure to cleanup managed stuff diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index df05e87bd..734dadc3c 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -145,6 +145,5 @@ private: public: // [PersistentScriptingObject] - String ToString() const override; void DestroyManaged() override; }; diff --git a/Source/Editor/Viewport/Previews/AnimationPreview.cs b/Source/Editor/Viewport/Previews/AnimationPreview.cs index 9ef6fedb9..92130b438 100644 --- a/Source/Editor/Viewport/Previews/AnimationPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimationPreview.cs @@ -44,7 +44,7 @@ namespace FlaxEditor.Viewport.Previews // Playback Speed { var playbackSpeed = ViewWidgetButtonMenu.AddButton("Playback Speed"); - var playbackSpeedValue = new FloatValueBox(-1, 90, 2, 70.0f, 0.0f, 10000.0f, 0.001f) + var playbackSpeedValue = new FloatValueBox(-1, 90, 2, 70.0f, -10000.0f, 10000.0f, 0.001f) { Parent = playbackSpeed }; diff --git a/Source/Engine/Animations/Curve.h b/Source/Engine/Animations/Curve.h index 8564f6912..2d3a9b50b 100644 --- a/Source/Engine/Animations/Curve.h +++ b/Source/Engine/Animations/Curve.h @@ -769,6 +769,12 @@ public: } }; +/// +/// An animation spline represented by a set of keyframes, each representing a value point. +/// +template +using StepCurve = Curve>; + /// /// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve. /// diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index 88c7a2eec..f7dd36b10 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -384,7 +384,7 @@ public: /// /// The slots animations. /// - Array Slots; + Array> Slots; public: diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index 82e5a3821..71fe5961e 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -183,8 +183,3 @@ void RenderBuffers::Release() UPDATE_LAZY_KEEP_RT(LuminanceMap); #undef UPDATE_LAZY_KEEP_RT } - -String RenderBuffers::ToString() const -{ - return TEXT("RenderBuffers"); -} diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index fd4bce1b1..75f7f3d50 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -175,9 +175,4 @@ public: /// Release the buffers data. /// API_FUNCTION() void Release(); - -public: - - // [PersistentScriptingObject] - String ToString() const override; }; From e144a6f69db618cd7ca680f8668cea30a97fb4f3 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Jan 2022 12:25:16 +0100 Subject: [PATCH 17/25] Fix crash when trying to read invalid Visual Script parameter from local scope during debugging --- Source/Engine/Content/Assets/VisualScript.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Source/Engine/Content/Assets/VisualScript.cpp b/Source/Engine/Content/Assets/VisualScript.cpp index ebacae017..2bfa4ed12 100644 --- a/Source/Engine/Content/Assets/VisualScript.cpp +++ b/Source/Engine/Content/Assets/VisualScript.cpp @@ -624,7 +624,9 @@ void VisualScriptExecutor::ProcessGroupFunction(Box* boxBase, Node* node, Value& { // Evaluate method parameter value from the current scope auto& scope = ThreadStacks.Get().Stack->Scope; - value = scope->Parameters[boxBase->ID - 1]; + int32 index = boxBase->ID - 1; + if (index < scope->Parameters.Length()) + value = scope->Parameters.Get()[index]; } break; } From 192af7ec14c37ceb3a7745c31f47bddfc482f137 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Jan 2022 13:16:36 +0100 Subject: [PATCH 18/25] Refactor native core objects to simplify usage for newcomers --- Source/Editor/Cooker/CookingData.h | 2 +- Source/Editor/Cooker/GameCooker.cpp | 2 +- Source/Editor/Managed/ManagedEditor.cpp | 4 +- Source/Editor/Managed/ManagedEditor.h | 4 +- Source/Engine/Audio/AudioDevice.h | 6 +- Source/Engine/Core/Object.cpp | 25 --- Source/Engine/Core/Object.h | 24 +-- Source/Engine/Core/ObjectsRemovalService.cpp | 31 +++- Source/Engine/Core/ObjectsRemovalService.h | 6 +- Source/Engine/Foliage/FoliageType.cpp | 2 +- Source/Engine/Foliage/FoliageType.h | 2 +- Source/Engine/Graphics/GPUAdapter.h | 4 +- Source/Engine/Graphics/GPUContext.cpp | 2 +- Source/Engine/Graphics/GPUContext.h | 2 +- Source/Engine/Graphics/GPUDevice.cpp | 8 +- Source/Engine/Graphics/GPUDevice.h | 2 +- Source/Engine/Graphics/GPUResource.h | 8 +- .../Graphics/Materials/MaterialParams.h | 4 +- Source/Engine/Graphics/Models/MaterialSlot.h | 4 +- Source/Engine/Graphics/Models/MeshBase.h | 4 +- Source/Engine/Graphics/Models/ModelLOD.h | 4 +- .../Engine/Graphics/Models/SkinnedModelLOD.h | 4 +- Source/Engine/Graphics/RenderBuffers.cpp | 2 +- Source/Engine/Graphics/RenderBuffers.h | 2 +- Source/Engine/Graphics/RenderTask.cpp | 2 +- Source/Engine/Graphics/RenderTask.h | 2 +- Source/Engine/Input/InputDevice.h | 4 +- Source/Engine/Level/Actor.h | 2 +- Source/Engine/Level/SceneObject.h | 4 +- .../Engine/Networking/Drivers/ENetDriver.cpp | 2 +- Source/Engine/Networking/Drivers/ENetDriver.h | 2 +- Source/Engine/Networking/NetworkPeer.h | 4 +- Source/Engine/Particles/ParticleEffect.h | 4 +- Source/Engine/Platform/Base/PlatformBase.cpp | 2 +- Source/Engine/Platform/Base/UserBase.h | 2 +- Source/Engine/Platform/Base/WindowBase.cpp | 4 +- Source/Engine/Platform/Base/WindowBase.h | 4 +- Source/Engine/Renderer/RenderList.cpp | 2 +- Source/Engine/Renderer/RenderList.h | 2 +- Source/Engine/Scripting/ManagedCLR/MUtils.h | 2 +- Source/Engine/Scripting/Script.h | 2 +- Source/Engine/Scripting/ScriptingObject.cpp | 164 ++++++++---------- Source/Engine/Scripting/ScriptingObject.h | 71 ++++---- Source/Engine/Threading/Task.h | 2 +- Source/Engine/Threading/TaskGraph.cpp | 4 +- Source/Engine/Threading/TaskGraph.h | 4 +- Source/Engine/Visject/GraphParameter.h | 4 +- 47 files changed, 204 insertions(+), 249 deletions(-) delete mode 100644 Source/Engine/Core/Object.cpp diff --git a/Source/Editor/Cooker/CookingData.h b/Source/Editor/Cooker/CookingData.h index 0d8236e83..d4923b60f 100644 --- a/Source/Editor/Cooker/CookingData.h +++ b/Source/Editor/Cooker/CookingData.h @@ -131,7 +131,7 @@ extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuratio /// /// Game cooking temporary data. /// -API_CLASS(Sealed, Namespace="FlaxEditor") class FLAXENGINE_API CookingData : public PersistentScriptingObject +API_CLASS(Sealed, Namespace="FlaxEditor") class FLAXENGINE_API CookingData : public ScriptingObject { DECLARE_SCRIPTING_TYPE(CookingData); public: diff --git a/Source/Editor/Cooker/GameCooker.cpp b/Source/Editor/Cooker/GameCooker.cpp index 452f4d230..c3696d6fe 100644 --- a/Source/Editor/Cooker/GameCooker.cpp +++ b/Source/Editor/Cooker/GameCooker.cpp @@ -164,7 +164,7 @@ CookingData::Statistics::Statistics() } CookingData::CookingData(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { } diff --git a/Source/Editor/Managed/ManagedEditor.cpp b/Source/Editor/Managed/ManagedEditor.cpp index b37ebf132..39bb9e05f 100644 --- a/Source/Editor/Managed/ManagedEditor.cpp +++ b/Source/Editor/Managed/ManagedEditor.cpp @@ -140,7 +140,7 @@ void OnVisualScriptingDebugFlow() void OnLogMessage(LogType type, const StringView& msg); ManagedEditor::ManagedEditor() - : PersistentScriptingObject(SpawnParams(ObjectID, ManagedEditor::TypeInitializer)) + : ScriptingObject(SpawnParams(ObjectID, ManagedEditor::TypeInitializer)) { // Link events auto editor = ((NativeBinaryModule*)GetBinaryModuleFlaxEngine())->Assembly; @@ -494,5 +494,5 @@ void ManagedEditor::DestroyManaged() Internal_OnVisualScriptingDebugFlow = nullptr; // Base - PersistentScriptingObject::DestroyManaged(); + ScriptingObject::DestroyManaged(); } diff --git a/Source/Editor/Managed/ManagedEditor.h b/Source/Editor/Managed/ManagedEditor.h index 734dadc3c..540eb46c0 100644 --- a/Source/Editor/Managed/ManagedEditor.h +++ b/Source/Editor/Managed/ManagedEditor.h @@ -14,7 +14,7 @@ namespace CSG /// /// Managed Editor root object /// -class ManagedEditor : public PersistentScriptingObject +class ManagedEditor : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor); @@ -144,6 +144,6 @@ private: public: - // [PersistentScriptingObject] + // [ScriptingObject] void DestroyManaged() override; }; diff --git a/Source/Engine/Audio/AudioDevice.h b/Source/Engine/Audio/AudioDevice.h index 4e7b5388b..e7bad7b56 100644 --- a/Source/Engine/Audio/AudioDevice.h +++ b/Source/Engine/Audio/AudioDevice.h @@ -7,17 +7,17 @@ /// /// Represents a single audio device. /// -API_CLASS(NoSpawn) class AudioDevice : public PersistentScriptingObject +API_CLASS(NoSpawn) class AudioDevice : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(AudioDevice); explicit AudioDevice() - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) { } AudioDevice(const AudioDevice& other) - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) { Name = other.Name; InternalName = other.InternalName; diff --git a/Source/Engine/Core/Object.cpp b/Source/Engine/Core/Object.cpp deleted file mode 100644 index d931741dc..000000000 --- a/Source/Engine/Core/Object.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. - -#include "Object.h" -#include "ObjectsRemovalService.h" - -RemovableObject::~RemovableObject() -{ -#if BUILD_DEBUG - // Prevent removing object that is still reverenced by the removal service - ASSERT(!ObjectsRemovalService::IsInPool(this)); -#endif -} - -void RemovableObject::DeleteObjectNow() -{ - ObjectsRemovalService::Dereference(this); - - OnDeleteObject(); -} - -void RemovableObject::DeleteObject(float timeToLive, bool useGameTime) -{ - // Add to deferred remove (or just update timeout but don't remove object here) - ObjectsRemovalService::Add(this, timeToLive, useGameTime); -} diff --git a/Source/Engine/Core/Object.h b/Source/Engine/Core/Object.h index ad9a37a51..5d1335d3c 100644 --- a/Source/Engine/Core/Object.h +++ b/Source/Engine/Core/Object.h @@ -37,30 +37,13 @@ public: /// /// Finalizes an instance of the class. /// - virtual ~Object() - { - } + virtual ~Object(); /// /// Gets the string representation of this object. /// /// The string. virtual String ToString() const = 0; -}; - -/// -/// Interface for removable Engine objects. -/// -/// -class FLAXENGINE_API RemovableObject : public Object -{ -public: - - /// - /// Virtual destructor but protected. Removable objects should be deleted using `DeleteObject` which supports deferred delete. - /// Note: it's unsafe to delete object using destructor it it has been marked for deferred delete. - /// - virtual ~RemovableObject(); /// /// Deletes the object without queueing it to the ObjectsRemovalService. @@ -68,7 +51,7 @@ public: void DeleteObjectNow(); /// - /// Deletes the object. + /// Deletes the object (deferred). /// /// The time to live (in seconds). Use zero to kill it now. /// True if unscaled game time for the object life timeout, otherwise false to use absolute time. @@ -82,3 +65,6 @@ public: Delete(this); } }; + +// [Deprecated on 5.01.2022, expires on 5.01.2024] +typedef Object RemovableObject; diff --git a/Source/Engine/Core/ObjectsRemovalService.cpp b/Source/Engine/Core/ObjectsRemovalService.cpp index 254a30f2a..28e001168 100644 --- a/Source/Engine/Core/ObjectsRemovalService.cpp +++ b/Source/Engine/Core/ObjectsRemovalService.cpp @@ -16,8 +16,8 @@ namespace ObjectsRemovalServiceImpl CriticalSection NewItemsLocker; DateTime LastUpdate; float LastUpdateGameTime; - Dictionary Pool(8192); - Dictionary NewItemsPool(2048); + Dictionary Pool(8192); + Dictionary NewItemsPool(2048); } using namespace ObjectsRemovalServiceImpl; @@ -38,7 +38,7 @@ public: ObjectsRemovalServiceService ObjectsRemovalServiceServiceInstance; -bool ObjectsRemovalService::IsInPool(RemovableObject* obj) +bool ObjectsRemovalService::IsInPool(Object* obj) { if (!IsReady) return false; @@ -67,7 +67,7 @@ bool ObjectsRemovalService::HasNewItemsForFlush() return result; } -void ObjectsRemovalService::Dereference(RemovableObject* obj) +void ObjectsRemovalService::Dereference(Object* obj) { if (!IsReady) return; @@ -81,7 +81,7 @@ void ObjectsRemovalService::Dereference(RemovableObject* obj) PoolLocker.Unlock(); } -void ObjectsRemovalService::Add(RemovableObject* obj, float timeToLive, bool useGameTime) +void ObjectsRemovalService::Add(Object* obj, float timeToLive, bool useGameTime) { ScopeLock lock(NewItemsLocker); @@ -213,3 +213,24 @@ void ObjectsRemovalServiceService::Dispose() IsReady = false; } + +Object::~Object() +{ +#if BUILD_DEBUG + // Prevent removing object that is still reverenced by the removal service + ASSERT(!ObjectsRemovalService::IsInPool(this)); +#endif +} + +void Object::DeleteObjectNow() +{ + ObjectsRemovalService::Dereference(this); + + OnDeleteObject(); +} + +void Object::DeleteObject(float timeToLive, bool useGameTime) +{ + // Add to deferred remove (or just update timeout but don't remove object here) + ObjectsRemovalService::Add(this, timeToLive, useGameTime); +} diff --git a/Source/Engine/Core/ObjectsRemovalService.h b/Source/Engine/Core/ObjectsRemovalService.h index 883dc19ae..c68652756 100644 --- a/Source/Engine/Core/ObjectsRemovalService.h +++ b/Source/Engine/Core/ObjectsRemovalService.h @@ -16,7 +16,7 @@ public: /// /// The object. /// True if object has been registered in the pool for the removing, otherwise false. - static bool IsInPool(RemovableObject* obj); + static bool IsInPool(Object* obj); /// /// Determines whether any object has been registered to be removed from pool (requests are flushed on Flush call). @@ -28,7 +28,7 @@ public: /// Removes the specified object from the dead pool (clears the reference to it). /// /// The object. - static void Dereference(RemovableObject* obj); + static void Dereference(Object* obj); /// /// Adds the specified object to the dead pool. @@ -36,7 +36,7 @@ public: /// The object. /// The time to live (in seconds). /// True if unscaled game time for the object life timeout, otherwise false to use absolute time. - static void Add(RemovableObject* obj, float timeToLive = 1.0f, bool useGameTime = false); + static void Add(Object* obj, float timeToLive = 1.0f, bool useGameTime = false); /// /// Flushes the objects pool removing objects marked to remove now (with negative or zero time to live). diff --git a/Source/Engine/Foliage/FoliageType.cpp b/Source/Engine/Foliage/FoliageType.cpp index ccf1b2f3e..22977716f 100644 --- a/Source/Engine/Foliage/FoliageType.cpp +++ b/Source/Engine/Foliage/FoliageType.cpp @@ -7,7 +7,7 @@ #include "Foliage.h" FoliageType::FoliageType() - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) , Foliage(nullptr) , Index(-1) { diff --git a/Source/Engine/Foliage/FoliageType.h b/Source/Engine/Foliage/FoliageType.h index 537c5be01..3dc161434 100644 --- a/Source/Engine/Foliage/FoliageType.h +++ b/Source/Engine/Foliage/FoliageType.h @@ -41,7 +41,7 @@ API_ENUM() enum class FoliageScalingModes /// /// Foliage mesh instances type descriptor. Defines the shared properties of the spawned mesh instances. /// -API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public PersistentScriptingObject, public ISerializable +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API FoliageType : public ScriptingObject, public ISerializable { DECLARE_SCRIPTING_TYPE_NO_SPAWN(FoliageType); friend Foliage; diff --git a/Source/Engine/Graphics/GPUAdapter.h b/Source/Engine/Graphics/GPUAdapter.h index efacae2ba..1aabd34b5 100644 --- a/Source/Engine/Graphics/GPUAdapter.h +++ b/Source/Engine/Graphics/GPUAdapter.h @@ -13,13 +13,13 @@ /// /// Interface for GPU device adapter. /// -API_CLASS(NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUAdapter : public PersistentScriptingObject +API_CLASS(NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUAdapter : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUDevice); public: GPUAdapter() - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) { } diff --git a/Source/Engine/Graphics/GPUContext.cpp b/Source/Engine/Graphics/GPUContext.cpp index a1955fb4c..c327e8c6e 100644 --- a/Source/Engine/Graphics/GPUContext.cpp +++ b/Source/Engine/Graphics/GPUContext.cpp @@ -6,7 +6,7 @@ #include "Textures/GPUTexture.h" GPUContext::GPUContext(GPUDevice* device) - : PersistentScriptingObject(ScriptingObjectSpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(ScriptingObjectSpawnParams(Guid::New(), TypeInitializer)) , _device(device) { } diff --git a/Source/Engine/Graphics/GPUContext.h b/Source/Engine/Graphics/GPUContext.h index c00f0852a..98e6b8069 100644 --- a/Source/Engine/Graphics/GPUContext.h +++ b/Source/Engine/Graphics/GPUContext.h @@ -113,7 +113,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUDrawIndexedIndirectArgs); /// /// Interface for GPU device context that can record and send graphics commands to the GPU in a sequence. /// -API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUContext : public PersistentScriptingObject +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUContext : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUContext); private: diff --git a/Source/Engine/Graphics/GPUDevice.cpp b/Source/Engine/Graphics/GPUDevice.cpp index f4ef475b4..5597bd067 100644 --- a/Source/Engine/Graphics/GPUDevice.cpp +++ b/Source/Engine/Graphics/GPUDevice.cpp @@ -173,12 +173,12 @@ GPUPipelineState::Description GPUPipelineState::Description::DefaultFullscreenTr }; GPUResource::GPUResource() - : PersistentScriptingObject(SpawnParams(Guid::New(), GPUResource::TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), GPUResource::TypeInitializer)) { } GPUResource::GPUResource(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { } @@ -241,7 +241,7 @@ void GPUResource::OnDeleteObject() { ReleaseGPU(); - PersistentScriptingObject::OnDeleteObject(); + ScriptingObject::OnDeleteObject(); } double GPUResourceView::DummyLastRenderTime = -1; @@ -262,7 +262,7 @@ struct GPUDevice::PrivateData GPUDevice* GPUDevice::Instance = nullptr; GPUDevice::GPUDevice(RendererType type, ShaderProfile profile) - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) , _state(DeviceState::Missing) , _isRendering(false) , _wasVSyncUsed(false) diff --git a/Source/Engine/Graphics/GPUDevice.h b/Source/Engine/Graphics/GPUDevice.h index 45dd77c06..e064248cc 100644 --- a/Source/Engine/Graphics/GPUDevice.h +++ b/Source/Engine/Graphics/GPUDevice.h @@ -29,7 +29,7 @@ class MaterialBase; /// /// Graphics device object for rendering on GPU. /// -API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUDevice : public PersistentScriptingObject +API_CLASS(Sealed, NoSpawn) class FLAXENGINE_API GPUDevice : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUDevice); public: diff --git a/Source/Engine/Graphics/GPUResource.h b/Source/Engine/Graphics/GPUResource.h index 94753205f..34142b4c4 100644 --- a/Source/Engine/Graphics/GPUResource.h +++ b/Source/Engine/Graphics/GPUResource.h @@ -15,7 +15,7 @@ /// /// The base class for all GPU resources. /// -API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API GPUResource : public PersistentScriptingObject +API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API GPUResource : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUResource); public: @@ -108,7 +108,7 @@ protected: public: - // [PersistentScriptingObject] + // [ScriptingObject] String ToString() const override; void OnDeleteObject() override; }; @@ -186,14 +186,14 @@ public: /// /// Interface for GPU resources views. Shared base class for texture and buffer views. /// -API_CLASS(Abstract, NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUResourceView : public PersistentScriptingObject +API_CLASS(Abstract, NoSpawn, Attributes="HideInEditor") class FLAXENGINE_API GPUResourceView : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUResourceView); protected: static double DummyLastRenderTime; explicit GPUResourceView(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) , LastRenderTime(&DummyLastRenderTime) { } diff --git a/Source/Engine/Graphics/Materials/MaterialParams.h b/Source/Engine/Graphics/Materials/MaterialParams.h index d5e182878..78e91fab1 100644 --- a/Source/Engine/Graphics/Materials/MaterialParams.h +++ b/Source/Engine/Graphics/Materials/MaterialParams.h @@ -167,9 +167,9 @@ struct SerializedMaterialParam /// /// Material variable object. Allows to modify material parameter value at runtime. /// -API_CLASS(NoSpawn) class FLAXENGINE_API MaterialParameter : public PersistentScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API MaterialParameter : public ScriptingObject { -DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialParameter, PersistentScriptingObject); +DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialParameter, ScriptingObject); friend MaterialParams; friend MaterialInstance; private: diff --git a/Source/Engine/Graphics/Models/MaterialSlot.h b/Source/Engine/Graphics/Models/MaterialSlot.h index bdd491d52..84a394dcb 100644 --- a/Source/Engine/Graphics/Models/MaterialSlot.h +++ b/Source/Engine/Graphics/Models/MaterialSlot.h @@ -10,9 +10,9 @@ /// /// The material slot descriptor that specifies how to render geometry using it. /// -API_CLASS(NoSpawn) class FLAXENGINE_API MaterialSlot : public PersistentScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API MaterialSlot : public ScriptingObject { -DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialSlot, PersistentScriptingObject); +DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(MaterialSlot, ScriptingObject); /// /// The material to use for rendering. diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 61f76fa59..466b52e04 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -14,7 +14,7 @@ class ModelBase; /// /// Base class for model resources meshes. /// -API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public PersistentScriptingObject +API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public ScriptingObject { DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase); protected: @@ -30,7 +30,7 @@ protected: bool _use16BitIndexBuffer; explicit MeshBase(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { } diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h index 94cb8d8ce..897f596d5 100644 --- a/Source/Engine/Graphics/Models/ModelLOD.h +++ b/Source/Engine/Graphics/Models/ModelLOD.h @@ -10,9 +10,9 @@ class MemoryReadStream; /// /// Represents single Level Of Detail for the model. Contains a collection of the meshes. /// -API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public PersistentScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject { -DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, PersistentScriptingObject); +DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(ModelLOD, ScriptingObject); friend Model; friend Mesh; private: diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h index 758db0a1b..9f46fb95e 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h @@ -10,9 +10,9 @@ class MemoryReadStream; /// /// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes. /// -API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public PersistentScriptingObject +API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModelLOD : public ScriptingObject { -DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, PersistentScriptingObject); +DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(SkinnedModelLOD, ScriptingObject); friend SkinnedModel; private: diff --git a/Source/Engine/Graphics/RenderBuffers.cpp b/Source/Engine/Graphics/RenderBuffers.cpp index 71fe5961e..ea2a46f7b 100644 --- a/Source/Engine/Graphics/RenderBuffers.cpp +++ b/Source/Engine/Graphics/RenderBuffers.cpp @@ -8,7 +8,7 @@ #include "Engine/Engine/Engine.h" RenderBuffers::RenderBuffers(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { #define CREATE_TEXTURE(name) name = GPUDevice::Instance->CreateTexture(TEXT(#name)); _resources.Add(name) CREATE_TEXTURE(DepthBuffer); diff --git a/Source/Engine/Graphics/RenderBuffers.h b/Source/Engine/Graphics/RenderBuffers.h index 75f7f3d50..04fea6eb5 100644 --- a/Source/Engine/Graphics/RenderBuffers.h +++ b/Source/Engine/Graphics/RenderBuffers.h @@ -19,7 +19,7 @@ /// /// The scene rendering buffers container. /// -API_CLASS() class FLAXENGINE_API RenderBuffers : public PersistentScriptingObject +API_CLASS() class FLAXENGINE_API RenderBuffers : public ScriptingObject { DECLARE_SCRIPTING_TYPE(RenderBuffers); protected: diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index 4ce50b94f..5ce178601 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -82,7 +82,7 @@ void RenderTask::DrawAll() } RenderTask::RenderTask(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { // Register TasksLocker.Lock(); diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index 63c5894c2..2928b148e 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -24,7 +24,7 @@ class Actor; /// /// Allows to perform custom rendering using graphics pipeline. /// -API_CLASS() class FLAXENGINE_API RenderTask : public PersistentScriptingObject +API_CLASS() class FLAXENGINE_API RenderTask : public ScriptingObject { DECLARE_SCRIPTING_TYPE(RenderTask); diff --git a/Source/Engine/Input/InputDevice.h b/Source/Engine/Input/InputDevice.h index 27f6125ba..445b0b20e 100644 --- a/Source/Engine/Input/InputDevice.h +++ b/Source/Engine/Input/InputDevice.h @@ -11,7 +11,7 @@ /// /// Base class for all input device objects. /// -API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API InputDevice : public PersistentScriptingObject +API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API InputDevice : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(InputDevice); public: @@ -86,7 +86,7 @@ protected: EventQueue _queue; explicit InputDevice(const SpawnParams& params, const StringView& name) - : PersistentScriptingObject(params) + : ScriptingObject(params) , _name(name) { } diff --git a/Source/Engine/Level/Actor.h b/Source/Engine/Level/Actor.h index 1d4296285..e3fcbc371 100644 --- a/Source/Engine/Level/Actor.h +++ b/Source/Engine/Level/Actor.h @@ -971,7 +971,7 @@ private: public: - // [PersistentScriptingObject] + // [ScriptingObject] String ToString() const override; void OnDeleteObject() override; diff --git a/Source/Engine/Level/SceneObject.h b/Source/Engine/Level/SceneObject.h index 387cbbc13..42d923403 100644 --- a/Source/Engine/Level/SceneObject.h +++ b/Source/Engine/Level/SceneObject.h @@ -55,7 +55,7 @@ typedef Dictionary ActorsLookup; /// /// Base class for objects that are parts of the scene (actors and scripts). /// -API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API SceneObject : public PersistentScriptingObject, public ISerializable +API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API SceneObject : public ScriptingObject, public ISerializable { DECLARE_SCRIPTING_TYPE_NO_SPAWN(SceneObject); friend PrefabInstanceData; @@ -64,7 +64,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(SceneObject); friend ScriptsFactory; friend SceneTicking; public: - typedef PersistentScriptingObject Base; + typedef ScriptingObject Base; // Scene Object lifetime flow: // - Create diff --git a/Source/Engine/Networking/Drivers/ENetDriver.cpp b/Source/Engine/Networking/Drivers/ENetDriver.cpp index 5079ef218..c1c26641e 100644 --- a/Source/Engine/Networking/Drivers/ENetDriver.cpp +++ b/Source/Engine/Networking/Drivers/ENetDriver.cpp @@ -51,7 +51,7 @@ void SendPacketToPeer(ENetPeer* peer, const NetworkChannelType channelType, cons } ENetDriver::ENetDriver(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { } diff --git a/Source/Engine/Networking/Drivers/ENetDriver.h b/Source/Engine/Networking/Drivers/ENetDriver.h index b9f4b5545..bc038e220 100644 --- a/Source/Engine/Networking/Drivers/ENetDriver.h +++ b/Source/Engine/Networking/Drivers/ENetDriver.h @@ -13,7 +13,7 @@ /// /// Low-level network transport interface implementation based on ENet library. /// -API_CLASS(Namespace="FlaxEngine.Networking", Sealed) class FLAXENGINE_API ENetDriver : public PersistentScriptingObject, public INetworkDriver +API_CLASS(Namespace="FlaxEngine.Networking", Sealed) class FLAXENGINE_API ENetDriver : public ScriptingObject, public INetworkDriver { DECLARE_SCRIPTING_TYPE(ENetDriver); public: diff --git a/Source/Engine/Networking/NetworkPeer.h b/Source/Engine/Networking/NetworkPeer.h index 865a8f3ec..5ba1435bc 100644 --- a/Source/Engine/Networking/NetworkPeer.h +++ b/Source/Engine/Networking/NetworkPeer.h @@ -11,7 +11,7 @@ /// /// Low-level network peer class. Provides server-client communication functions, message processing and sending. /// -API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkPeer final : public PersistentScriptingObject +API_CLASS(sealed, NoSpawn, Namespace = "FlaxEngine.Networking") class FLAXENGINE_API NetworkPeer final : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(NetworkPeer); friend class NetworkManager; @@ -30,7 +30,7 @@ public: /// Initializes a new instance of the class. /// NetworkPeer() - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) { } diff --git a/Source/Engine/Particles/ParticleEffect.h b/Source/Engine/Particles/ParticleEffect.h index ac9a43a91..e43e1df8d 100644 --- a/Source/Engine/Particles/ParticleEffect.h +++ b/Source/Engine/Particles/ParticleEffect.h @@ -12,7 +12,7 @@ /// /// Particle system parameter. /// -API_CLASS(NoSpawn) class ParticleEffectParameter : public PersistentScriptingObject +API_CLASS(NoSpawn) class ParticleEffectParameter : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(ParticleEffectParameter); friend ParticleEffect; @@ -30,7 +30,7 @@ public: /// Initializes a new instance of the class. /// ParticleEffectParameter() - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) { } diff --git a/Source/Engine/Platform/Base/PlatformBase.cpp b/Source/Engine/Platform/Base/PlatformBase.cpp index 382aff1b4..2e51c5e30 100644 --- a/Source/Engine/Platform/Base/PlatformBase.cpp +++ b/Source/Engine/Platform/Base/PlatformBase.cpp @@ -112,7 +112,7 @@ UserBase::UserBase(const String& name) } UserBase::UserBase(const SpawnParams& params, const String& name) - : PersistentScriptingObject(params) + : ScriptingObject(params) , _name(name) { } diff --git a/Source/Engine/Platform/Base/UserBase.h b/Source/Engine/Platform/Base/UserBase.h index c946c4ba1..c53e56169 100644 --- a/Source/Engine/Platform/Base/UserBase.h +++ b/Source/Engine/Platform/Base/UserBase.h @@ -10,7 +10,7 @@ API_INJECT_CPP_CODE("#include \"Engine/Platform/User.h\""); /// /// Native platform user object. /// -API_CLASS(NoSpawn, NoConstructor, Sealed, Name="User") class FLAXENGINE_API UserBase : public PersistentScriptingObject +API_CLASS(NoSpawn, NoConstructor, Sealed, Name="User") class FLAXENGINE_API UserBase : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(UserBase); protected: diff --git a/Source/Engine/Platform/Base/WindowBase.cpp b/Source/Engine/Platform/Base/WindowBase.cpp index 8df7ae6d1..7324003ed 100644 --- a/Source/Engine/Platform/Base/WindowBase.cpp +++ b/Source/Engine/Platform/Base/WindowBase.cpp @@ -87,7 +87,7 @@ #endif WindowBase::WindowBase(const CreateWindowSettings& settings) - : PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) + : ScriptingObject(SpawnParams(Guid::New(), TypeInitializer)) , _visible(false) , _minimized(false) , _maximized(false) @@ -190,7 +190,7 @@ void WindowBase::OnDeleteObject() SAFE_DELETE(_swapChain); // Base - PersistentScriptingObject::OnDeleteObject(); + ScriptingObject::OnDeleteObject(); } bool WindowBase::GetRenderingEnabled() const diff --git a/Source/Engine/Platform/Base/WindowBase.h b/Source/Engine/Platform/Base/WindowBase.h index 799a3866e..ab77d2c56 100644 --- a/Source/Engine/Platform/Base/WindowBase.h +++ b/Source/Engine/Platform/Base/WindowBase.h @@ -269,7 +269,7 @@ API_INJECT_CPP_CODE("#include \"Engine/Platform/Window.h\""); /// Native platform window object. /// API_CLASS(NoSpawn, NoConstructor, Sealed, Name="Window") -class FLAXENGINE_API WindowBase : public PersistentScriptingObject +class FLAXENGINE_API WindowBase : public ScriptingObject { DECLARE_SCRIPTING_TYPE_NO_SPAWN(WindowBase); friend GPUSwapChain; @@ -941,7 +941,7 @@ private: public: - // [PersistentScriptingObject] + // [ScriptingObject] String ToString() const override; void OnDeleteObject() override; }; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index be1f035fc..51001cbb5 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -369,7 +369,7 @@ bool DrawCallsList::IsEmpty() const } RenderList::RenderList(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) , DirectionalLights(4) , PointLights(32) , SpotLights(32) diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index 10b51448d..890ebb473 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -322,7 +322,7 @@ struct DrawCallsList /// /// Rendering cache container object for the draw calls collecting, sorting and executing. /// -API_CLASS(Sealed) class FLAXENGINE_API RenderList : public PersistentScriptingObject +API_CLASS(Sealed) class FLAXENGINE_API RenderList : public ScriptingObject { DECLARE_SCRIPTING_TYPE(RenderList); diff --git a/Source/Engine/Scripting/ManagedCLR/MUtils.h b/Source/Engine/Scripting/ManagedCLR/MUtils.h index 9bda26826..09db2bb88 100644 --- a/Source/Engine/Scripting/ManagedCLR/MUtils.h +++ b/Source/Engine/Scripting/ManagedCLR/MUtils.h @@ -197,7 +197,7 @@ struct MConverter::Va // Converter for Scripting Objects (collection of values). template -struct MConverter::Value>::Type> +struct MConverter::Value>::Type> { MonoObject* Box(const T& data, MonoClass* klass) { diff --git a/Source/Engine/Scripting/Script.h b/Source/Engine/Scripting/Script.h index 4f5079daf..32c2c03fd 100644 --- a/Source/Engine/Scripting/Script.h +++ b/Source/Engine/Scripting/Script.h @@ -134,7 +134,7 @@ private: public: - // [PersistentScriptingObject] + // [ScriptingObject] String ToString() const override; void OnDeleteObject() override; diff --git a/Source/Engine/Scripting/ScriptingObject.cpp b/Source/Engine/Scripting/ScriptingObject.cpp index b8775f941..43136929c 100644 --- a/Source/Engine/Scripting/ScriptingObject.cpp +++ b/Source/Engine/Scripting/ScriptingObject.cpp @@ -40,20 +40,32 @@ ScriptingObject::ScriptingObject(const SpawnParams& params) ScriptingObject::~ScriptingObject() { - // Ensure that GC handle is empty - ASSERT(_gcHandle == 0); - - // Ensure that object has been already unregistered - if (IsRegistered()) - UnregisterObject(); - Deleted(this); + // Get rid of managed object + ScriptingObject::DestroyManaged(); + ASSERT(_gcHandle == 0); + // Handle custom scripting objects removing if (Flags & ObjectFlags::IsCustomScriptingType) { _type.Module->OnObjectDeleted(this); } + + // Ensure that object has been already unregistered + if (IsRegistered()) + UnregisterObject(); +} + +ScriptingObject* ScriptingObject::NewObject(const ScriptingTypeHandle& typeHandle) +{ + if (!typeHandle) + return nullptr; + auto& type = typeHandle.GetType(); + if (type.Type != ScriptingTypes::Script) + return nullptr; + const ScriptingObjectSpawnParams params(Guid::New(), typeHandle); + return type.Script.Spawn(params); } MObject* ScriptingObject::GetManagedInstance() const @@ -82,17 +94,6 @@ MClass* ScriptingObject::GetClass() const return _type ? _type.GetType().ManagedClass : nullptr; } -ScriptingObject* ScriptingObject::NewObject(const ScriptingTypeHandle& typeHandle) -{ - if (!typeHandle) - return nullptr; - auto& type = typeHandle.GetType(); - if (type.Type != ScriptingTypes::Script) - return nullptr; - const ScriptingObjectSpawnParams params(Guid::New(), typeHandle); - return type.Script.Spawn(params); -} - ScriptingObject* ScriptingObject::FromInterface(void* interfaceObj, const ScriptingTypeHandle& interfaceType) { if (!interfaceObj || !interfaceType) @@ -220,18 +221,49 @@ void ScriptingObject::OnManagedInstanceDeleted() // Unregister object if (IsRegistered()) UnregisterObject(); - - // Self destruct - DeleteObject(); } void ScriptingObject::OnScriptingDispose() { // Delete C# object + if (IsRegistered()) + UnregisterObject(); DestroyManaged(); +} - // Delete C++ object - DeleteObject(); +bool ScriptingObject::CreateManaged() +{ +#if USE_MONO + MonoObject* managedInstance = CreateManagedInternal(); + if (!managedInstance) + return true; + + // Prevent form object GC destruction + auto handle = mono_gchandle_new(managedInstance, false); + auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0); + if (*(uint32*)&oldHandle != 0) + { + // Other thread already created the object before + if (const auto monoClass = GetClass()) + { + // Reset managed to unmanaged pointer + const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); + if (monoUnmanagedPtrField) + { + void* param = nullptr; + monoUnmanagedPtrField->SetValue(managedInstance, ¶m); + } + } + mono_gchandle_free(handle); + return true; + } +#endif + + // Ensure to be registered + if (!IsRegistered()) + RegisterObject(); + + return false; } #if USE_MONO @@ -379,7 +411,7 @@ void ScriptingObject::OnDeleteObject() UnregisterObject(); // Base - RemovableObject::OnDeleteObject(); + Object::OnDeleteObject(); } String ScriptingObject::ToString() const @@ -392,6 +424,24 @@ ManagedScriptingObject::ManagedScriptingObject(const SpawnParams& params) { } +void ManagedScriptingObject::OnManagedInstanceDeleted() +{ + // Base + ScriptingObject::OnManagedInstanceDeleted(); + + // Self destruct + DeleteObject(); +} + +void ManagedScriptingObject::OnScriptingDispose() +{ + // Base + ScriptingObject::OnScriptingDispose(); + + // Self destruct + DeleteObject(); +} + bool ManagedScriptingObject::CreateManaged() { #if USE_MONO @@ -432,70 +482,6 @@ PersistentScriptingObject::PersistentScriptingObject(const SpawnParams& params) { } -PersistentScriptingObject::~PersistentScriptingObject() -{ - PersistentScriptingObject::DestroyManaged(); -} - -void PersistentScriptingObject::OnManagedInstanceDeleted() -{ - // Cleanup - if (_gcHandle) - { -#if USE_MONO - mono_gchandle_free(_gcHandle); -#endif - _gcHandle = 0; - } - - // But do not delete itself -} - -void PersistentScriptingObject::OnScriptingDispose() -{ - // Delete C# object - if (IsRegistered()) - UnregisterObject(); - DestroyManaged(); - - // Don't delete C++ object -} - -bool PersistentScriptingObject::CreateManaged() -{ -#if USE_MONO - MonoObject* managedInstance = CreateManagedInternal(); - if (!managedInstance) - return true; - - // Prevent form object GC destruction - auto handle = mono_gchandle_new(managedInstance, false); - auto oldHandle = Platform::InterlockedCompareExchange((int32*)&_gcHandle, *(int32*)&handle, 0); - if (*(uint32*)&oldHandle != 0) - { - // Other thread already created the object before - if (const auto monoClass = GetClass()) - { - // Reset managed to unmanaged pointer - const MField* monoUnmanagedPtrField = monoClass->GetField(ScriptingObject_unmanagedPtr); - if (monoUnmanagedPtrField) - { - void* param = nullptr; - monoUnmanagedPtrField->SetValue(managedInstance, ¶m); - } - } - mono_gchandle_free(handle); - return true; - } -#endif - - // Ensure to be registered - if (!IsRegistered()) - RegisterObject(); - - return false; -} - class ScriptingObjectInternal { public: @@ -767,7 +753,7 @@ public: static ScriptingObject* Spawn(const ScriptingObjectSpawnParams& params) { - return New(params); + return New(params); } }; diff --git a/Source/Engine/Scripting/ScriptingObject.h b/Source/Engine/Scripting/ScriptingObject.h index a0114b1a4..04983b43e 100644 --- a/Source/Engine/Scripting/ScriptingObject.h +++ b/Source/Engine/Scripting/ScriptingObject.h @@ -8,9 +8,9 @@ #include "ManagedCLR/MTypes.h" /// -/// Represents object from unmanaged memory that can use accessed via C# scripting. +/// Represents object from unmanaged memory that can use accessed via scripting. /// -API_CLASS(InBuild) class FLAXENGINE_API ScriptingObject : public RemovableObject +API_CLASS(InBuild) class FLAXENGINE_API ScriptingObject : public Object { friend class Scripting; friend class BinaryModule; @@ -40,14 +40,23 @@ public: public: - /// - /// Spawns a new objects of the given type. - /// + // Spawns a new objects of the given type. + static ScriptingObject* NewObject(const ScriptingTypeHandle& typeHandle); template - static T* Spawn() + static T* NewObject() { - const SpawnParams params(Guid::New(), T::TypeInitializer); - return T::New(params); + return (T*)NewObject(T::TypeInitializer); + } + template + static T* NewObject(const ScriptingTypeHandle& typeHandle) + { + auto obj = NewObject(typeHandle); + if (obj && !obj->Is()) + { + Delete(obj); + obj = nullptr; + } + return (T*)obj; } public: @@ -108,13 +117,6 @@ public: public: - static ScriptingObject* NewObject(const ScriptingTypeHandle& typeHandle); - template - static T* NewObject() - { - return (T*)NewObject(T::TypeInitializer); - } - // Tries to cast native interface object to scripting object instance. Returns null if fails. static ScriptingObject* FromInterface(void* interfaceObj, const ScriptingTypeHandle& interfaceType); static void* ToInterface(ScriptingObject* obj, const ScriptingTypeHandle& interfaceType); @@ -189,7 +191,7 @@ public: virtual void OnManagedInstanceDeleted(); virtual void OnScriptingDispose(); - virtual bool CreateManaged() = 0; + virtual bool CreateManaged(); virtual void DestroyManaged(); public: @@ -223,7 +225,7 @@ protected: public: - // [RemovableObject] + // [Object] void OnDeleteObject() override; String ToString() const override; }; @@ -246,32 +248,17 @@ public: public: // [ScriptingObject] - bool CreateManaged() override; -}; - -/// -/// Managed object that uses pinned GC handle to prevent collecting and moving. -/// Used by the objects that lifetime is controlled by the C++ side. -/// -API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public ScriptingObject -{ -public: - - /// - /// Initializes a new instance of the class. - /// - /// The object initialization parameters. - explicit PersistentScriptingObject(const SpawnParams& params); - - /// - /// Finalizes an instance of the class. - /// - ~PersistentScriptingObject(); - -public: - - // [ManagedScriptingObject] void OnManagedInstanceDeleted() override; void OnScriptingDispose() override; bool CreateManaged() override; }; + +/// +/// Use ScriptingObject instead. +/// [Deprecated on 5.01.2022, expires on 5.01.2024] +/// +API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public ScriptingObject +{ +public: + PersistentScriptingObject(const SpawnParams& params); +}; diff --git a/Source/Engine/Threading/Task.h b/Source/Engine/Threading/Task.h index 86a34a992..10486288a 100644 --- a/Source/Engine/Threading/Task.h +++ b/Source/Engine/Threading/Task.h @@ -18,7 +18,7 @@ DECLARE_ENUM_EX_6(TaskState, int64, 0, Created, Failed, Canceled, Queued, Runnin /// /// Represents an asynchronous operation. /// -class FLAXENGINE_API Task : public RemovableObject, public NonCopyable +class FLAXENGINE_API Task : public Object, public NonCopyable { // // Tasks execution and states flow: diff --git a/Source/Engine/Threading/TaskGraph.cpp b/Source/Engine/Threading/TaskGraph.cpp index b36a85e31..5d32f7670 100644 --- a/Source/Engine/Threading/TaskGraph.cpp +++ b/Source/Engine/Threading/TaskGraph.cpp @@ -14,7 +14,7 @@ namespace } TaskGraphSystem::TaskGraphSystem(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { } @@ -36,7 +36,7 @@ void TaskGraphSystem::PostExecute(TaskGraph* graph) } TaskGraph::TaskGraph(const SpawnParams& params) - : PersistentScriptingObject(params) + : ScriptingObject(params) { } diff --git a/Source/Engine/Threading/TaskGraph.h b/Source/Engine/Threading/TaskGraph.h index 142b0d2a1..1664f6291 100644 --- a/Source/Engine/Threading/TaskGraph.h +++ b/Source/Engine/Threading/TaskGraph.h @@ -10,7 +10,7 @@ class TaskGraph; /// /// System that can generate work into Task Graph for asynchronous execution. /// -API_CLASS(Abstract) class FLAXENGINE_API TaskGraphSystem : public PersistentScriptingObject +API_CLASS(Abstract) class FLAXENGINE_API TaskGraphSystem : public ScriptingObject { DECLARE_SCRIPTING_TYPE(TaskGraphSystem); friend TaskGraph; @@ -52,7 +52,7 @@ public: /// /// Graph-based asynchronous tasks scheduler for high-performance computing and processing. /// -API_CLASS() class FLAXENGINE_API TaskGraph : public PersistentScriptingObject +API_CLASS() class FLAXENGINE_API TaskGraph : public ScriptingObject { DECLARE_SCRIPTING_TYPE(TaskGraph); private: diff --git a/Source/Engine/Visject/GraphParameter.h b/Source/Engine/Visject/GraphParameter.h index 1fb3fd2dd..8b1f7cc8c 100644 --- a/Source/Engine/Visject/GraphParameter.h +++ b/Source/Engine/Visject/GraphParameter.h @@ -39,9 +39,9 @@ API_ENUM() enum class ChannelMask /// /// Represents a parameter in the Graph. /// -API_CLASS() class FLAXENGINE_API GraphParameter : public PersistentScriptingObject +API_CLASS() class FLAXENGINE_API GraphParameter : public ScriptingObject { -DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(GraphParameter, PersistentScriptingObject); +DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(GraphParameter, ScriptingObject); public: /// From a2487c02d9854079bdb963ef0ce000b675a04472 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Fri, 7 Jan 2022 21:57:29 +0100 Subject: [PATCH 19/25] Add context menu for the timeline tracks and change signature for media context menu --- Source/Editor/GUI/Timeline/Media.cs | 3 ++- Source/Editor/GUI/Timeline/Timeline.cs | 16 +++++++++++++++- Source/Editor/GUI/Timeline/Track.cs | 9 +++++++++ .../Editor/GUI/Timeline/Tracks/CameraCutTrack.cs | 4 ++-- .../GUI/Timeline/Tracks/ScreenFadeTrack.cs | 4 ++-- 5 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs index 70783b449..6d6a4c5ea 100644 --- a/Source/Editor/GUI/Timeline/Media.cs +++ b/Source/Editor/GUI/Timeline/Media.cs @@ -229,8 +229,9 @@ namespace FlaxEditor.GUI.Timeline /// Called when showing timeline context menu to the user. Can be used to add custom buttons. /// /// The menu. + /// The time (in seconds) at which context menu is shown (user clicked on a timeline). /// The found control under the mouse cursor. - public virtual void OnTimelineShowContextMenu(ContextMenu.ContextMenu menu, Control controlUnderMouse) + public virtual void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time, Control controlUnderMouse) { if (CanDelete && Track.Media.Count > Track.MinMediaCount) menu.AddButton("Delete media", Delete); diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index a89df1fc2..e254c7456 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -1901,6 +1901,9 @@ namespace FlaxEditor.GUI.Timeline if (!ContainsFocus) Focus(); + var timelinePos = MediaPanel.PointFromParent(this, location); + var time = (timelinePos.X - StartOffset) / (UnitsPerSecond * Zoom); + var controlUnderMouse = GetChildAtRecursive(location); var mediaUnderMouse = controlUnderMouse; while (mediaUnderMouse != null && !(mediaUnderMouse is Media)) @@ -1911,12 +1914,23 @@ namespace FlaxEditor.GUI.Timeline var menu = new ContextMenu.ContextMenu(); if (mediaUnderMouse is Media media) { - media.OnTimelineShowContextMenu(menu, controlUnderMouse); + media.OnTimelineContextMenu(menu, time, controlUnderMouse); if (media.PropertiesEditObject != null) { menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, location, media.Track)); } } + else + { + foreach (var track in _tracks) + { + if (Mathf.IsInRange(timelinePos.Y, track.Top, track.Bottom)) + { + track.OnTimelineContextMenu(menu, time); + break; + } + } + } if (PropertiesEditObject != null) { menu.AddButton("Edit timeline", () => ShowEditPopup(PropertiesEditObject, location, this)); diff --git a/Source/Editor/GUI/Timeline/Track.cs b/Source/Editor/GUI/Timeline/Track.cs index 935ab863a..485603808 100644 --- a/Source/Editor/GUI/Timeline/Track.cs +++ b/Source/Editor/GUI/Timeline/Track.cs @@ -1028,6 +1028,15 @@ namespace FlaxEditor.GUI.Timeline return true; } + /// + /// Called when showing timeline context menu to the user. Can be used to add custom buttons. + /// + /// The menu. + /// The time (in seconds) at which context menu is shown (user clicked on a timeline). + public virtual void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time) + { + } + /// /// Called when context menu is being prepared to show. Can be used to add custom options. /// diff --git a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs index 3d731a321..3dd2a1fe2 100644 --- a/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/CameraCutTrack.cs @@ -324,12 +324,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks } /// - public override void OnTimelineShowContextMenu(ContextMenu.ContextMenu menu, Control controlUnderMouse) + public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time, Control controlUnderMouse) { if (((CameraCutTrack)Track).Camera) menu.AddButton("Refresh thumbnails", () => UpdateThumbnails()); - base.OnTimelineShowContextMenu(menu, controlUnderMouse); + base.OnTimelineContextMenu(menu, time, controlUnderMouse); } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs b/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs index 13b5c6fbc..e07b419da 100644 --- a/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs +++ b/Source/Editor/GUI/Timeline/Tracks/ScreenFadeTrack.cs @@ -101,9 +101,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks } /// - public override void OnTimelineShowContextMenu(ContextMenu.ContextMenu menu, Control controlUnderMouse) + public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time, Control controlUnderMouse) { - base.OnTimelineShowContextMenu(menu, controlUnderMouse); + base.OnTimelineContextMenu(menu, time, controlUnderMouse); if (controlUnderMouse is GradientEditor.StopControl stop) { From 79200a784bfff7bf0d3c5aadecefe944886cc598 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Jan 2022 14:45:56 +0100 Subject: [PATCH 20/25] Add `Level::ScriptsReloadRegisterObject` for easier scripting objects refreshing during hot-reload in code --- Source/Engine/Level/Level.cpp | 47 +++++++++++++++++++++++++++++++++++ Source/Engine/Level/Level.h | 10 ++++++++ 2 files changed, 57 insertions(+) diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 89d43046d..009404cbc 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -36,6 +36,7 @@ #include "Editor/Editor.h" #include "Engine/Platform/MessageBox.h" #include "Engine/Engine/CommandLine.h" +#include "Engine/Serialization/JsonSerializer.h" #endif bool LayersMask::HasLayer(const StringView& layerName) const @@ -74,11 +75,25 @@ public: } }; +#if USE_EDITOR + +struct ScriptsReloadObject +{ + StringAnsi TypeName; + ScriptingObject** Object; + Array Data; +}; + +#endif + namespace LevelImpl { Array _sceneActions; CriticalSection _sceneActionsLocker; DateTime _lastSceneLoadTime(0); +#if USE_EDITOR + Array ScriptsReloadObjects; +#endif void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId); @@ -559,6 +574,24 @@ public: Level::ScriptsReload(); Scripting::Reload(); + // Restore objects + for (auto& e : ScriptsReloadObjects) + { + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(e.TypeName); + *e.Object = ScriptingObject::NewObject(typeHandle); + if (!*e.Object) + { + LOG(Warning, "Failed to restore hot-reloaded object of type {0}.", String(e.TypeName)); + continue; + } + auto* serializable = ScriptingObject::ToInterface(*e.Object); + if (serializable && e.Data.HasItems()) + { + JsonSerializer::LoadFromBytes(serializable, e.Data, FLAXENGINE_VERSION_BUILD); + } + } + ScriptsReloadObjects.Clear(); + // Restore scenes (from memory) for (int32 i = 0; i < scenesCount; i++) { @@ -595,6 +628,20 @@ public: } }; +void Level::ScriptsReloadRegisterObject(ScriptingObject*& obj) +{ + if (!obj) + return; + auto& e = ScriptsReloadObjects.AddOne(); + e.Object = &obj; + e.TypeName = obj->GetType().Fullname; + if (auto* serializable = ScriptingObject::ToInterface(obj)) + e.Data = JsonSerializer::SaveToBytes(serializable); + ScriptingObject* o = obj; + obj = nullptr; + o->DeleteObjectNow(); +} + #endif class SpawnActorAction : public SceneAction diff --git a/Source/Engine/Level/Level.h b/Source/Engine/Level/Level.h index afd1553d1..241941872 100644 --- a/Source/Engine/Level/Level.h +++ b/Source/Engine/Level/Level.h @@ -215,6 +215,8 @@ public: /// API_EVENT() static Delegate SceneUnloaded; +#if USE_EDITOR + /// /// Fired when scene starts reloading scripts. /// @@ -230,6 +232,14 @@ public: /// API_EVENT() static Action ScriptsReloadEnd; + /// + /// Adds object to preserve during scripts reload. Called during ScriptsReloadStart event to serialize and destroy the object that should be restored when scripts reload ends. + /// + /// Reference to the object to preserve during the scripting reload. + static void ScriptsReloadRegisterObject(ScriptingObject*& obj); + +#endif + public: /// From 82a43dea28139752dfb042619a648e2460a4865e Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Jan 2022 15:06:02 +0100 Subject: [PATCH 21/25] Add **Animation Events** --- .../Editor/GUI/Timeline/AnimationTimeline.cs | 3 +- .../Timeline/Tracks/AnimationEventTrack.cs | 283 ++++++++++++++++++ .../Viewport/Previews/AnimatedModelPreview.cs | 8 + Source/Engine/Animations/AnimEvent.cpp | 70 +++++ Source/Engine/Animations/AnimEvent.h | 72 +++++ Source/Engine/Animations/Graph/AnimGraph.cpp | 22 ++ Source/Engine/Animations/Graph/AnimGraph.h | 14 + .../Animations/Graph/AnimGroup.Animation.cpp | 78 +++++ .../SceneAnimations/SceneAnimation.h | 1 + Source/Engine/Content/Assets/Animation.cpp | 147 ++++++++- Source/Engine/Content/Assets/Animation.h | 25 ++ 11 files changed, 720 insertions(+), 3 deletions(-) create mode 100644 Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs create mode 100644 Source/Engine/Animations/AnimEvent.cpp create mode 100644 Source/Engine/Animations/AnimEvent.h diff --git a/Source/Editor/GUI/Timeline/AnimationTimeline.cs b/Source/Editor/GUI/Timeline/AnimationTimeline.cs index 50e5ddfdb..19c1543e0 100644 --- a/Source/Editor/GUI/Timeline/AnimationTimeline.cs +++ b/Source/Editor/GUI/Timeline/AnimationTimeline.cs @@ -52,7 +52,7 @@ namespace FlaxEditor.GUI.Timeline /// /// The undo/redo to use for the history actions recording. Optional, can be null to disable undo support. public AnimationTimeline(FlaxEditor.Undo undo) - : base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, false) + : base(PlaybackButtons.Play | PlaybackButtons.Stop, undo, false, true) { PlaybackState = PlaybackStates.Seeking; ShowPreviewValues = false; @@ -61,6 +61,7 @@ namespace FlaxEditor.GUI.Timeline // Setup track types TrackArchetypes.Add(AnimationChannelTrack.GetArchetype()); TrackArchetypes.Add(AnimationChannelDataTrack.GetArchetype()); + TrackArchetypes.Add(AnimationEventTrack.GetArchetype()); } /// diff --git a/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs new file mode 100644 index 000000000..cce09a84f --- /dev/null +++ b/Source/Editor/GUI/Timeline/Tracks/AnimationEventTrack.cs @@ -0,0 +1,283 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using FlaxEditor.CustomEditors; +using FlaxEditor.GUI.ContextMenu; +using FlaxEditor.Scripting; +using FlaxEditor.Utilities; +using FlaxEngine; +using FlaxEngine.GUI; +using Object = FlaxEngine.Object; + +namespace FlaxEditor.GUI.Timeline.Tracks +{ + /// + /// The timeline media for and . + /// + public sealed class AnimationEventMedia : Media + { + private sealed class ProxyEditor : SyncPointEditor + { + /// + public override IEnumerable UndoObjects => Values; + + /// + public override void Initialize(LayoutElementsContainer layout) + { + base.Initialize(layout); + + var instance = (AnimEvent)Values[0]; + var scriptType = TypeUtils.GetObjectType(instance); + var editor = CustomEditorsUtil.CreateEditor(scriptType, false); + layout.Object(Values, editor); + } + } + + private sealed class Proxy : ProxyBase + { + [EditorDisplay("General", EditorDisplayAttribute.InlineStyle), CustomEditor(typeof(ProxyEditor))] + public AnimEvent Event + { + get => Media.Instance; + set => Media.Instance = value; + } + + public Proxy(AnimationEventTrack track, AnimationEventMedia media) + : base(track, media) + { + } + } + + private bool _isRegisteredForScriptsReload; + private string _instanceTypeName; + private byte[] _instanceData; + + /// + /// The event type. + /// + public ScriptType Type; + + /// + /// The event instance. + /// + public AnimEvent Instance; + + /// + /// True if event is continuous (with duration), not a single frame. + /// + public bool IsContinuous; + + /// + /// Initializes a new instance of the class. + /// + public AnimationEventMedia() + { + PropertiesEditObject = new Proxy(null, this); + } + + private void OnScriptsReloadBegin() + { + if (Instance) + { + _instanceTypeName = Type.TypeName; + Type = ScriptType.Null; + _instanceData = FlaxEngine.Json.JsonSerializer.SaveToBytes(Instance); + Object.Destroy(ref Instance); + ScriptsBuilder.ScriptsReloadEnd += OnScriptsReloadEnd; + } + } + + private void OnScriptsReloadEnd() + { + ScriptsBuilder.ScriptsReloadEnd -= OnScriptsReloadEnd; + Type = TypeUtils.GetType(_instanceTypeName); + if (Type == ScriptType.Null) + { + Editor.LogError("Missing anim event type " + _instanceTypeName); + return; + } + Instance = (AnimEvent)Type.CreateInstance(); + FlaxEngine.Json.JsonSerializer.LoadFromBytes(Instance, _instanceData, Globals.EngineBuildNumber); + _instanceData = null; + } + + /// + /// Initializes track for the specified type. + /// + /// The type. + public void Init(ScriptType type) + { + Type = type; + IsContinuous = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type); + CanDelete = true; + CanSplit = IsContinuous; + CanResize = IsContinuous; + TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(type); + Instance = (AnimEvent)type.CreateInstance(); + BackgroundColor = Instance.Color; + if (!_isRegisteredForScriptsReload) + { + _isRegisteredForScriptsReload = true; + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; + } + } + + /// + protected override void OnDurationFramesChanged() + { + if (Type != ScriptType.Null) + DurationFrames = IsContinuous ? Mathf.Max(DurationFrames, 2) : 1; + + base.OnDurationFramesChanged(); + } + + /// + public override void OnDestroy() + { + Type = ScriptType.Null; + Object.Destroy(ref Instance); + if (_isRegisteredForScriptsReload) + { + _isRegisteredForScriptsReload = false; + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; + } + + base.OnDestroy(); + } + } + + /// + /// The timeline track for and . + /// + public sealed class AnimationEventTrack : Track + { + /// + /// Gets the archetype. + /// + /// The archetype. + public static TrackArchetype GetArchetype() + { + return new TrackArchetype + { + TypeId = 19, + Name = "Animation Event", + Create = options => new AnimationEventTrack(ref options), + Load = LoadTrack, + Save = SaveTrack, + }; + } + + private static void LoadTrack(int version, Track track, BinaryReader stream) + { + var e = (AnimationEventTrack)track; + var count = stream.ReadInt32(); + while (e.Media.Count > count) + e.RemoveMedia(e.Media.Last()); + while (e.Media.Count < count) + e.AddMedia(new AnimationEventMedia()); + for (int i = 0; i < count; i++) + { + var m = (AnimationEventMedia)e.Media[i]; + m.StartFrame = (int)stream.ReadSingle(); + m.DurationFrames = (int)stream.ReadSingle(); + var typeName = stream.ReadStrAnsi(13); + var type = TypeUtils.GetType(typeName); + if (type == ScriptType.Null) + throw new Exception($"Unknown type {typeName}."); + m.Init(type); + stream.ReadJson(m.Instance); + } + } + + private static void SaveTrack(Track track, BinaryWriter stream) + { + var e = (AnimationEventTrack)track; + var count = e.Media.Count; + stream.Write(count); + for (int i = 0; i < count; i++) + { + var m = (AnimationEventMedia)e.Media[i]; + stream.Write((float)m.StartFrame); + stream.Write((float)m.DurationFrames); + stream.WriteStrAnsi(m.Type.TypeName, 13); + stream.WriteJson(m.Instance); + } + } + + /// + public AnimationEventTrack(ref TrackCreateOptions options) + : base(ref options) + { + // Add button + const float buttonSize = 14; + var addButton = new Button + { + Text = "+", + TooltipText = "Add events", + AutoFocus = true, + AnchorPreset = AnchorPresets.MiddleRight, + IsScrollable = false, + Offsets = new Margin(-buttonSize - 2 + _muteCheckbox.Offsets.Left, buttonSize, buttonSize * -0.5f, buttonSize), + Parent = this, + }; + addButton.ButtonClicked += OnAddButtonClicked; + } + + private void OnAddButtonClicked(Button button) + { + var cm = new ContextMenu.ContextMenu(); + OnContextMenu(cm); + cm.Show(button.Parent, button.BottomLeft); + } + + /// + public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time) + { + base.OnTimelineContextMenu(menu, time); + + AddContextMenu(menu, time); + } + + /// + protected override void OnContextMenu(ContextMenu.ContextMenu menu) + { + base.OnContextMenu(menu); + + menu.AddSeparator(); + AddContextMenu(menu, 0.0f); + } + + private void AddContextMenu(ContextMenu.ContextMenu menu, float time) + { + var addEvent = menu.AddChildMenu("Add Anim Event"); + var addContinuousEvent = menu.AddChildMenu("Add Anim Continuous Event"); + var animEventTypes = Editor.Instance.CodeEditing.All.Get().Where(x => new ScriptType(typeof(AnimEvent)).IsAssignableFrom(x)); + foreach (var type in animEventTypes) + { + if (type.IsAbstract || !type.CanCreateInstance) + continue; + var add = new ScriptType(typeof(AnimContinuousEvent)).IsAssignableFrom(type) ? addContinuousEvent : addEvent; + var b = add.ContextMenu.AddButton(type.Name); + b.TooltipText = Surface.SurfaceUtils.GetVisualScriptTypeDescription(type); + b.Tag = type; + b.Parent.Tag = time; + b.ButtonClicked += OnAddAnimEvent; + } + } + + + private void OnAddAnimEvent(ContextMenuButton button) + { + var type = (ScriptType)button.Tag; + var time = (float)button.Parent.Tag; + var media = new AnimationEventMedia(); + media.Init(type); + media.StartFrame = (int)(time * Timeline.FramesPerSecond); + media.DurationFrames = media.IsContinuous ? Mathf.Max(Timeline.DurationFrames / 10, 10) : 1; + Timeline.AddMedia(this, media); + } + } +} diff --git a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs index 1eabd1977..da1083d6e 100644 --- a/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs +++ b/Source/Editor/Viewport/Previews/AnimatedModelPreview.cs @@ -163,6 +163,7 @@ namespace FlaxEditor.Viewport.Previews : base(useWidgets) { Task.Begin += OnBegin; + ScriptsBuilder.ScriptsReloadBegin += OnScriptsReloadBegin; // Setup preview scene _previewModel = new AnimatedModel @@ -275,6 +276,12 @@ namespace FlaxEditor.Viewport.Previews } } + private void OnScriptsReloadBegin() + { + // Prevent any crashes due to dangling references to anim events + _previewModel.ResetAnimation(); + } + private int ComputeLODIndex(SkinnedModel model) { if (PreviewActor.ForcedLOD != -1) @@ -428,6 +435,7 @@ namespace FlaxEditor.Viewport.Previews /// public override void OnDestroy() { + ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin; Object.Destroy(ref _floorModel); Object.Destroy(ref _previewModel); NodesMask = null; diff --git a/Source/Engine/Animations/AnimEvent.cpp b/Source/Engine/Animations/AnimEvent.cpp new file mode 100644 index 000000000..87c2e9b3b --- /dev/null +++ b/Source/Engine/Animations/AnimEvent.cpp @@ -0,0 +1,70 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#include "AnimEvent.h" +#include "Engine/Scripting/BinaryModule.h" +#include "Engine/Scripting/ManagedSerialization.h" +#include "Engine/Serialization/SerializationFwd.h" +#include "Engine/Serialization/Serialization.h" + +AnimEvent::AnimEvent(const SpawnParams& params) + : ScriptingObject(params) +{ +} + +void AnimEvent::Serialize(SerializeStream& stream, const void* otherObj) +{ + SERIALIZE_GET_OTHER_OBJ(AnimEvent); + +#if !COMPILE_WITHOUT_CSHARP + // Handle C# objects data serialization + if (Flags & ObjectFlags::IsManagedType) + { + stream.JKEY("V"); + if (other) + { + ManagedSerialization::SerializeDiff(stream, GetOrCreateManagedInstance(), other->GetOrCreateManagedInstance()); + } + else + { + ManagedSerialization::Serialize(stream, GetOrCreateManagedInstance()); + } + } +#endif + + // Handle custom scripting objects data serialization + if (Flags & ObjectFlags::IsCustomScriptingType) + { + stream.JKEY("D"); + _type.Module->SerializeObject(stream, this, other); + } +} + +void AnimEvent::Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) +{ +#if !COMPILE_WITHOUT_CSHARP + // Handle C# objects data serialization + if (Flags & ObjectFlags::IsManagedType) + { + auto* const v = SERIALIZE_FIND_MEMBER(stream, "V"); + if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) + { + ManagedSerialization::Deserialize(v->value, GetOrCreateManagedInstance()); + } + } +#endif + + // Handle custom scripting objects data serialization + if (Flags & ObjectFlags::IsCustomScriptingType) + { + auto* const v = SERIALIZE_FIND_MEMBER(stream, "D"); + if (v != stream.MemberEnd() && v->value.IsObject() && v->value.MemberCount() != 0) + { + _type.Module->DeserializeObject(v->value, this, modifier); + } + } +} + +AnimContinuousEvent::AnimContinuousEvent(const SpawnParams& params) + : AnimEvent(params) +{ +} diff --git a/Source/Engine/Animations/AnimEvent.h b/Source/Engine/Animations/AnimEvent.h new file mode 100644 index 000000000..f5afc275f --- /dev/null +++ b/Source/Engine/Animations/AnimEvent.h @@ -0,0 +1,72 @@ +// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Scripting/ScriptingObject.h" +#include "Engine/Core/ISerializable.h" +#if USE_EDITOR +#include "Engine/Core/Math/Color.h" +#endif + +class AnimatedModel; +class Animation; + +/// +/// The animation notification event triggered during animation playback. +/// +API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public ScriptingObject, public ISerializable +{ + DECLARE_SCRIPTING_TYPE(AnimEvent); + +#if USE_EDITOR + /// + /// Event display color in the Editor. + /// + API_FIELD(Attributes="HideInEditor, NoSerialize") Color Color = Color::White; +#endif + + /// + /// Animation event notification. + /// + /// The animated model actor instance. + /// The source animation. + /// The current animation time (in seconds). + /// The current animation tick delta time (in seconds). + API_FUNCTION() virtual void OnEvent(AnimatedModel* actor, Animation* anim, float time, float deltaTime) + { + } + + // [ISerializable] + void Serialize(SerializeStream& stream, const void* otherObj) override; + void Deserialize(DeserializeStream& stream, ISerializeModifier* modifier) override; +}; + +/// +/// The animation notification event (with duration) triggered during animation playback that contains begin and end (event notification is received as a tick). +/// +API_CLASS(Abstract) class FLAXENGINE_API AnimContinuousEvent : public AnimEvent +{ + DECLARE_SCRIPTING_TYPE(AnimContinuousEvent); + + /// + /// Animation notification called before the first event. + /// + /// The animated model actor instance. + /// The source animation. + /// The current animation time (in seconds). + /// The current animation tick delta time (in seconds). + API_FUNCTION() virtual void OnBegin(AnimatedModel* actor, Animation* anim, float time, float deltaTime) + { + } + + /// + /// Animation notification called after the last event (guaranteed to be always called). + /// + /// The animated model actor instance. + /// The source animation. + /// The current animation time (in seconds). + /// The current animation tick delta time (in seconds). + API_FUNCTION() virtual void OnEnd(AnimatedModel* actor, Animation* anim, float time, float deltaTime) + { + } +}; diff --git a/Source/Engine/Animations/Graph/AnimGraph.cpp b/Source/Engine/Animations/Graph/AnimGraph.cpp index 6d702350d..118430c54 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.cpp +++ b/Source/Engine/Animations/Graph/AnimGraph.cpp @@ -2,6 +2,7 @@ #include "AnimGraph.h" #include "Engine/Animations/Animations.h" +#include "Engine/Animations/AnimEvent.h" #include "Engine/Content/Assets/SkinnedModel.h" #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Scripting/Scripting.h" @@ -91,6 +92,9 @@ void AnimGraphInstanceData::Clear() State.Resize(0); NodesPose.Resize(0); Slots.Resize(0); + for (const auto& e : Events) + ((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f); + Events.Resize(0); } void AnimGraphInstanceData::ClearState() @@ -103,6 +107,9 @@ void AnimGraphInstanceData::ClearState() State.Resize(0); NodesPose.Resize(0); Slots.Clear(); + for (const auto& e : Events) + ((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)Object, e.Anim, 0.0f, 0.0f); + Events.Clear(); } void AnimGraphInstanceData::Invalidate() @@ -246,6 +253,8 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) // Initialize buckets ResetBuckets(context, &_graph); } + for (auto& e : data.Events) + e.Hit = false; // Init empty nodes data context.EmptyNodes.RootMotion = RootMotionData::Identity; @@ -279,6 +288,19 @@ void AnimGraphExecutor::Update(AnimGraphInstanceData& data, float dt) if (animResult == nullptr) animResult = GetEmptyNodes(); } + if (data.Events.Count() != 0) + { + ANIM_GRAPH_PROFILE_EVENT("Events"); + for (int32 i = data.Events.Count() - 1; i >= 0; i--) + { + const auto& e = data.Events[i]; + if (!e.Hit) + { + ((AnimContinuousEvent*)e.Instance)->OnEnd((AnimatedModel*)context.Data->Object, e.Anim, 0.0f, 0.0f); + data.Events.RemoveAt(i); + } + } + } // Allow for external override of the local pose (eg. by the ragdoll) data.LocalPoseOverride(animResult); diff --git a/Source/Engine/Animations/Graph/AnimGraph.h b/Source/Engine/Animations/Graph/AnimGraph.h index f7dd36b10..c38eacee9 100644 --- a/Source/Engine/Animations/Graph/AnimGraph.h +++ b/Source/Engine/Animations/Graph/AnimGraph.h @@ -16,6 +16,7 @@ #define ANIM_GRAPH_MULTI_BLEND_2D_MAX_TRIS 32 #define ANIM_GRAPH_MAX_STATE_TRANSITIONS 64 #define ANIM_GRAPH_MAX_CALL_STACK 100 +#define ANIM_GRAPH_MAX_EVENTS 64 class AnimGraph; class AnimSubGraph; @@ -267,6 +268,7 @@ struct FLAXENGINE_API AnimGraphSlot /// class FLAXENGINE_API AnimGraphInstanceData { + friend AnimGraphExecutor; public: // ---- Quick documentation ---- @@ -402,6 +404,18 @@ public: /// Invalidates the update timer. /// void Invalidate(); + +private: + + struct Event + { + AnimEvent* Instance; + Animation* Anim; + AnimGraphNode* Node; + bool Hit; + }; + + Array> Events; }; /// diff --git a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp index 080109a12..ff7ce0058 100644 --- a/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp +++ b/Source/Engine/Animations/Graph/AnimGroup.Animation.cpp @@ -5,7 +5,9 @@ #include "Engine/Content/Assets/SkeletonMask.h" #include "Engine/Content/Assets/AnimationGraphFunction.h" #include "Engine/Animations/AlphaBlend.h" +#include "Engine/Animations/AnimEvent.h" #include "Engine/Animations/InverseKinematics.h" +#include "Engine/Level/Actors/AnimatedModel.h" namespace { @@ -146,6 +148,7 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float if (anim == nullptr || !anim->IsLoaded()) return Value::Null; PROFILE_CPU_ASSET(anim); + const float oldTimePos = prevTimePos; // Calculate actual time position within the animation node (defined by length and loop mode) const float pos = GetAnimPos(newTimePos, startTimePos, loop, length); @@ -180,6 +183,81 @@ Variant AnimGraphExecutor::SampleAnimation(AnimGraphNode* node, bool loop, float ExtractRootMotion(mapping, rootNodeIndex, anim, animPos, animPrevPos, nodes->Nodes[rootNodeIndex], nodes->RootMotion); } + // Collect events + if (anim->Events.Count() != 0) + { + ANIM_GRAPH_PROFILE_EVENT("Events"); + auto& context = Context.Get(); + float eventTimeMin = animPrevPos; + float eventTimeMax = animPos; + if (loop) + { + // Check if animation looped + const float posNotLooped = startTimePos + oldTimePos; + if (posNotLooped < 0.0f || posNotLooped > length) + { + if (context.DeltaTime * speed < 0) + { + // Playback backwards + Swap(eventTimeMin, eventTimeMax); + } + } + } + const float eventTime = animPos / static_cast(anim->Data.FramesPerSecond); + const float eventDeltaTime = (animPos - animPrevPos) / static_cast(anim->Data.FramesPerSecond); + for (const auto& track : anim->Events) + { + for (const auto& k : track.Second.GetKeyframes()) + { + if (!k.Value.Instance) + continue; + const float duration = k.Value.Duration > 1 ? k.Value.Duration : 0.0f; + if (k.Time <= eventTimeMax && eventTimeMin <= k.Time + duration) + { + int32 stateIndex = -1; + if (duration > 1) + { + // Begin for continuous event + for (stateIndex = 0; stateIndex < context.Data->Events.Count(); stateIndex++) + { + const auto& e = context.Data->Events[stateIndex]; + if (e.Instance == k.Value.Instance && e.Node == node) + break; + } + if (stateIndex == context.Data->Events.Count()) + { + auto& e = context.Data->Events.AddOne(); + e.Instance = k.Value.Instance; + e.Anim = anim; + e.Node = node; + ASSERT(k.Value.Instance->Is()); + ((AnimContinuousEvent*)k.Value.Instance)->OnBegin((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime); + } + } + + // Event + k.Value.Instance->OnEvent((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime); + if (stateIndex != -1) + context.Data->Events[stateIndex].Hit = true; + } + else if (duration > 1) + { + // End for continuous event + for (int32 i = 0; i < context.Data->Events.Count(); i++) + { + const auto& e = context.Data->Events[i]; + if (e.Instance == k.Value.Instance && e.Node == node) + { + ((AnimContinuousEvent*)k.Value.Instance)->OnEnd((AnimatedModel*)context.Data->Object, anim, eventTime, eventDeltaTime); + context.Data->Events.RemoveAt(i); + break; + } + } + } + } + } + } + return nodes; } diff --git a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h index 83dd85550..98bccc0cf 100644 --- a/Source/Engine/Animations/SceneAnimations/SceneAnimation.h +++ b/Source/Engine/Animations/SceneAnimations/SceneAnimation.h @@ -42,6 +42,7 @@ public: CameraCut = 16, //AnimationChannel = 17, //AnimationChannelData = 18, + //AnimationEvent = 19, }; enum class Flags diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 1ef3add14..789153c21 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -6,10 +6,13 @@ #include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Content/Factories/BinaryAssetFactory.h" #include "Engine/Animations/CurveSerialization.h" +#include "Engine/Animations/AnimEvent.h" +#include "Engine/Scripting/Scripting.h" #include "Engine/Threading/Threading.h" #include "Engine/Serialization/MemoryReadStream.h" #if USE_EDITOR #include "Engine/Serialization/MemoryWriteStream.h" +#include "Engine/Level/Level.h" #endif REGISTER_BINARY_ASSET(Animation, "FlaxEngine.Animation", false); @@ -19,6 +22,21 @@ Animation::Animation(const SpawnParams& params, const AssetInfo* info) { } +#if USE_EDITOR + +void Animation::OnScriptsReloadStart() +{ + for (auto& e : Events) + { + for (auto& k : e.Second.GetKeyframes()) + { + Level::ScriptsReloadRegisterObject((ScriptingObject*&)k.Value.Instance); + } + } +} + +#endif + Animation::InfoData Animation::GetInfo() const { ScopeLock lock(Locker); @@ -127,7 +145,7 @@ void Animation::LoadTimeline(BytesContainer& result) const const float fpsInv = 1.0f / fps; stream.WriteFloat(fps); stream.WriteInt32((int32)Data.Duration); - int32 tracksCount = Data.Channels.Count(); + int32 tracksCount = Data.Channels.Count() + Events.Count(); for (auto& channel : Data.Channels) tracksCount += (channel.Position.GetKeyframes().HasItems() ? 1 : 0) + @@ -214,6 +232,24 @@ void Animation::LoadTimeline(BytesContainer& result) const trackIndex++; } } + for (auto& e : Events) + { + // Animation Event track + stream.WriteByte(19); // Track Type + stream.WriteByte(0); // Track Flags + stream.WriteInt32(-1); // Parent Index + stream.WriteInt32(0); // Children Count + stream.WriteString(e.First, -13); // Name + stream.Write(&Color32::White); // Color + stream.WriteInt32(e.Second.GetKeyframes().Count()); // Events Count + for (const auto& k : e.Second.GetKeyframes()) + { + stream.WriteFloat(k.Time); + stream.WriteFloat(k.Value.Duration); + stream.WriteStringAnsi(k.Value.TypeName, 13); + stream.WriteJson(k.Value.Instance); + } + } result.Copy(stream.GetHandle(), stream.GetPosition()); } @@ -253,6 +289,7 @@ bool Animation::SaveTimeline(BytesContainer& data) // Tracks Data.Channels.Clear(); + Events.Clear(); Dictionary animationChannelTrackIndexToChannelIndex; animationChannelTrackIndexToChannelIndex.EnsureCapacity(tracksCount * 3); for (int32 trackIndex = 0; trackIndex < tracksCount; trackIndex++) @@ -325,6 +362,36 @@ bool Animation::SaveTimeline(BytesContainer& data) } break; } + case 19: + { + // Animation Event + int32 count; + stream.ReadInt32(&count); + auto& eventTrack = Events.AddOne(); + eventTrack.First = name; + eventTrack.Second.Resize(count); + for (int32 i = 0; i < count; i++) + { + auto& k = eventTrack.Second.GetKeyframes()[i]; + stream.ReadFloat(&k.Time); + stream.ReadFloat(&k.Value.Duration); + stream.ReadStringAnsi(&k.Value.TypeName, 13); + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(k.Value.TypeName); + k.Value.Instance = NewObject(typeHandle); + stream.ReadJson(k.Value.Instance); + if (!k.Value.Instance) + { + LOG(Error, "Failed to spawn object of type {0}.", String(k.Value.TypeName)); + continue; + } + if (!_registeredForScriptingReload) + { + _registeredForScriptingReload = true; + Level::ScriptsReloadStart.Bind(this); + } + } + break; + } default: LOG(Error, "Unsupported track type {0} for animation.", trackType); return true; @@ -364,7 +431,7 @@ bool Animation::Save(const StringView& path) MemoryWriteStream stream(4096); // Info - stream.WriteInt32(100); + stream.WriteInt32(101); stream.WriteDouble(Data.Duration); stream.WriteDouble(Data.FramesPerSecond); stream.WriteBool(Data.EnableRootMotion); @@ -381,6 +448,22 @@ bool Animation::Save(const StringView& path) Serialization::Serialize(stream, anim.Scale); } + // Animation events + stream.WriteInt32(Events.Count()); + for (int32 i = 0; i < Events.Count(); i++) + { + auto& e = Events[i]; + stream.WriteString(e.First, 172); + stream.WriteInt32(e.Second.GetKeyframes().Count()); + for (const auto& k : e.Second.GetKeyframes()) + { + stream.WriteFloat(k.Time); + stream.WriteFloat(k.Value.Duration); + stream.WriteStringAnsi(k.Value.TypeName, 17); + stream.WriteJson(k.Value.Instance); + } + } + // Set data to the chunk asset auto chunk0 = GetOrCreateChunk(0); ASSERT(chunk0 != nullptr); @@ -432,6 +515,7 @@ Asset::LoadResult Animation::load() switch (headerVersion) { case 100: + case 101: { stream.ReadInt32(&headerVersion); stream.ReadDouble(&Data.Duration); @@ -471,13 +555,72 @@ Asset::LoadResult Animation::load() } } + // Animation events + if (headerVersion >= 101) + { + int32 eventTracksCount; + stream.ReadInt32(&eventTracksCount); + Events.Resize(eventTracksCount, false); +#if !USE_EDITOR + StringAnsi typeName; +#endif + for (int32 i = 0; i < eventTracksCount; i++) + { + auto& e = Events[i]; + stream.ReadString(&e.First, 172); + int32 eventsCount; + stream.ReadInt32(&eventsCount); + e.Second.GetKeyframes().Resize(eventsCount); + for (auto& k : e.Second.GetKeyframes()) + { + stream.ReadFloat(&k.Time); + stream.ReadFloat(&k.Value.Duration); +#if USE_EDITOR + StringAnsi& typeName = k.Value.TypeName; +#endif + stream.ReadStringAnsi(&typeName, 17); + const ScriptingTypeHandle typeHandle = Scripting::FindScriptingType(typeName); + k.Value.Instance = NewObject(typeHandle); + stream.ReadJson(k.Value.Instance); + if (!k.Value.Instance) + { + LOG(Error, "Failed to spawn object of type {0}.", String(typeName)); + continue; + } +#if USE_EDITOR + if (!_registeredForScriptingReload) + { + _registeredForScriptingReload = true; + Level::ScriptsReloadStart.Bind(this); + } +#endif + } + } + } + return LoadResult::Ok; } void Animation::unload(bool isReloading) { +#if USE_EDITOR + if (_registeredForScriptingReload) + { + _registeredForScriptingReload = false; + Level::ScriptsReloadStart.Unbind(this); + } +#endif ClearCache(); Data.Dispose(); + for (const auto& e : Events) + { + for (const auto& k : e.Second.GetKeyframes()) + { + if (k.Value.Instance) + Delete(k.Value.Instance); + } + } + Events.Clear(); } AssetChunksFlag Animation::getChunksToPreload() const diff --git a/Source/Engine/Content/Assets/Animation.h b/Source/Engine/Content/Assets/Animation.h index 950b7a45a..8c9b9c095 100644 --- a/Source/Engine/Content/Assets/Animation.h +++ b/Source/Engine/Content/Assets/Animation.h @@ -7,6 +7,7 @@ #include "Engine/Animations/AnimationData.h" class SkinnedModel; +class AnimEvent; /// /// Asset that contains an animation spline represented by a set of keyframes, each representing an endpoint of a linear curve. @@ -48,6 +49,25 @@ DECLARE_BINARY_ASSET_HEADER(Animation, 1); API_FIELD() int32 MemoryUsage; }; + /// + /// Contains instance. + /// + struct FLAXENGINE_API AnimEventData + { + float Duration = 0.0f; + AnimEvent* Instance = nullptr; +#if USE_EDITOR + StringAnsi TypeName; +#endif + }; + +private: + +#if USE_EDITOR + bool _registeredForScriptingReload = false; + void OnScriptsReloadStart(); +#endif + public: /// @@ -55,6 +75,11 @@ public: /// AnimationData Data; + /// + /// The animation events (keyframes per named track). + /// + Array>> Events; + /// /// Contains the mapping for every skeleton node to the animation data channels. /// Can be used for a simple lookup or to check if a given node is animated (unused nodes are using -1 index). From 844fad2cedb7a9bebd906bbab9822f56a5f6bd09 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Jan 2022 18:29:13 +0100 Subject: [PATCH 22/25] Fix crash when animation has anim events and scripting is disposed before content --- Source/Engine/Content/Assets/Animation.cpp | 18 ++++++++++++++++++ Source/Engine/Content/Assets/Animation.h | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/Source/Engine/Content/Assets/Animation.cpp b/Source/Engine/Content/Assets/Animation.cpp index 789153c21..8788f1466 100644 --- a/Source/Engine/Content/Assets/Animation.cpp +++ b/Source/Engine/Content/Assets/Animation.cpp @@ -502,6 +502,24 @@ void Animation::OnSkinnedModelUnloaded(Asset* obj) MappingCache.Remove(i); } +void Animation::OnScriptingDispose() +{ + // Dispose any events to prevent crashes (scripting is released before content) + for (auto& e : Events) + { + for (auto& k : e.Second.GetKeyframes()) + { + if (k.Value.Instance) + { + Delete(k.Value.Instance); + k.Value.Instance = nullptr; + } + } + } + + BinaryAsset::OnScriptingDispose(); +} + Asset::LoadResult Animation::load() { // Get stream with animations data diff --git a/Source/Engine/Content/Assets/Animation.h b/Source/Engine/Content/Assets/Animation.h index 8c9b9c095..80085e392 100644 --- a/Source/Engine/Content/Assets/Animation.h +++ b/Source/Engine/Content/Assets/Animation.h @@ -163,6 +163,11 @@ private: void OnSkinnedModelUnloaded(Asset* obj); +public: + + // [BinaryAsset] + void OnScriptingDispose() override; + protected: // [BinaryAsset] From 73f7688c58dad876d3a2edc91c3f1fd36b67002f Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sat, 8 Jan 2022 20:01:27 +0100 Subject: [PATCH 23/25] Tweaks --- Source/Editor/GUI/Timeline/Media.cs | 5 +++-- Source/Editor/GUI/Timeline/Timeline.cs | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Source/Editor/GUI/Timeline/Media.cs b/Source/Editor/GUI/Timeline/Media.cs index 6d6a4c5ea..b97717ec8 100644 --- a/Source/Editor/GUI/Timeline/Media.cs +++ b/Source/Editor/GUI/Timeline/Media.cs @@ -400,8 +400,9 @@ namespace FlaxEditor.GUI.Timeline } else { - // Select (additive for the move) - _timeline.Select(this, true); + // Select + if (!_timeline.SelectedMedia.Contains(this)) + _timeline.Select(this); } _timeline.OnKeyframesMove(null, this, location, true, false); diff --git a/Source/Editor/GUI/Timeline/Timeline.cs b/Source/Editor/GUI/Timeline/Timeline.cs index e254c7456..d2ade10eb 100644 --- a/Source/Editor/GUI/Timeline/Timeline.cs +++ b/Source/Editor/GUI/Timeline/Timeline.cs @@ -1560,6 +1560,7 @@ namespace FlaxEditor.GUI.Timeline track.AddMedia(media); } MarkAsEdited(); + Select(media); } /// @@ -1922,6 +1923,7 @@ namespace FlaxEditor.GUI.Timeline } else { + OnKeyframesDeselect(null); foreach (var track in _tracks) { if (Mathf.IsInRange(timelinePos.Y, track.Top, track.Bottom)) From 892723c5015dd3e60b34bdaa86808980de02b7a8 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 9 Jan 2022 17:46:37 +0100 Subject: [PATCH 24/25] Fix build --- Source/Engine/Content/Storage/FlaxStorage.h | 1 + Source/Engine/Level/Level.cpp | 2 ++ Source/Engine/Serialization/WriteStream.h | 18 ------------------ 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/Source/Engine/Content/Storage/FlaxStorage.h b/Source/Engine/Content/Storage/FlaxStorage.h index 85a88fee0..798a9d53a 100644 --- a/Source/Engine/Content/Storage/FlaxStorage.h +++ b/Source/Engine/Content/Storage/FlaxStorage.h @@ -6,6 +6,7 @@ #include "Engine/Core/Delegate.h" #include "Engine/Core/Types/String.h" #include "Engine/Core/Types/DateTime.h" +#include "Engine/Core/Collections/Array.h" #include "Engine/Platform/CriticalSection.h" #include "Engine/Serialization/FileReadStream.h" #include "Engine/Threading/ThreadLocal.h" diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 009404cbc..496dc2519 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -143,9 +143,11 @@ Delegate Level::SceneLoaded; Delegate Level::SceneLoadError; Delegate Level::SceneUnloading; Delegate Level::SceneUnloaded; +#if USE_EDITOR Action Level::ScriptsReloadStart; Action Level::ScriptsReload; Action Level::ScriptsReloadEnd; +#endif Array Level::Tags; String Level::Layers[32]; diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 43bda26e4..1bc4ee692 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -154,24 +154,6 @@ public: WriteBytes((const void*)text, sizeof(Char) * length); } - template - void WriteTextFormatted(const char* format, const Args& ... args) - { - fmt_flax::allocator_ansi allocator; - fmt_flax::memory_buffer_ansi buffer(allocator); - fmt_flax::format(buffer, format, args...); - WriteText(buffer.data(), (int32)buffer.size()); - } - - template - void WriteTextFormatted(const Char* format, const Args& ... args) - { - fmt_flax::allocator allocator; - fmt_flax::memory_buffer buffer(allocator); - fmt_flax::format(buffer, format, args...); - WriteText(buffer.data(), (int32)buffer.size()); - } - // Write UTF BOM character sequence void WriteBOM() { From 729bfcb941e78295113ac7ec984014215f28dfdf Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 9 Jan 2022 18:05:52 +0100 Subject: [PATCH 25/25] Fix build --- .../Cooker/Platform/UWP/UWPPlatformTools.cpp | 24 ++++++++-------- .../Engine/ContentExporters/ExportModel.cpp | 28 +++++++++---------- Source/Engine/Serialization/Stream.cpp | 6 ++++ Source/Engine/Serialization/WriteStream.h | 1 + 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp index d399c7f63..c74368348 100644 --- a/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp +++ b/Source/Editor/Cooker/Platform/UWP/UWPPlatformTools.cpp @@ -179,12 +179,12 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) if (file) { auto now = DateTime::Now(); - file->WriteTextFormatted( + file->WriteText(StringAnsi::Format( fileTemplate.Get() , gameSettings->ProductName.ToStringAnsi() , gameSettings->CompanyName.ToStringAnsi() , now.GetYear() - ); + )); hasError = file->HasError(); Delete(file); } @@ -210,10 +210,10 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool hasError = true; if (file) { - file->WriteTextFormatted( + file->WriteText(StringAnsi::Format( fileTemplate.Get() , defaultNamespace.ToStringAnsi() // {0} Default Namespace - ); + )); hasError = file->HasError(); Delete(file); } @@ -264,11 +264,11 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool hasError = true; if (file) { - file->WriteTextFormatted( + file->WriteText(StringAnsi::Format( fileTemplate.Get() , autoRotationPreferences.Get() , preferredLaunchWindowingMode.Get() - ); + )); hasError = file->HasError(); Delete(file); } @@ -296,12 +296,12 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool hasError = true; if (file) { - file->WriteTextFormatted( + file->WriteText(StringAnsi::Format( fileTemplate.Get() , projectName.ToStringAnsi() // {0} Project Name , mode // {1} Platform Mode , projectGuid.ToStringAnsi() // {2} Project ID - ); + )); hasError = file->HasError(); Delete(file); } @@ -344,14 +344,14 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool hasError = true; if (file) { - file->WriteTextFormatted( + file->WriteText(StringAnsi::Format( fileTemplate.Get() , projectName.ToStringAnsi() // {0} Project Name , mode // {1} Platform Mode , projectGuid.Get() // {2} Project ID , filesInclude.ToString().ToStringAnsi() // {3} Files to include , defaultNamespace.ToStringAnsi() // {4} Default Namespace - ); + )); hasError = file->HasError(); Delete(file); } @@ -394,13 +394,13 @@ bool UWPPlatformTools::OnDeployBinaries(CookingData& data) bool hasError = true; if (file) { - file->WriteTextFormatted( + file->WriteText(StringAnsi::Format( fileTemplate.Get() , projectName.ToStringAnsi() // {0} Display Name , gameSettings->CompanyName.ToStringAnsi() // {1} Company Name , productId.ToStringAnsi() // {2} Product ID , defaultNamespace.ToStringAnsi() // {3} Default Namespace - ); + )); hasError = file->HasError(); Delete(file); } diff --git a/Source/Engine/ContentExporters/ExportModel.cpp b/Source/Engine/ContentExporters/ExportModel.cpp index 7fdd011f9..9dd3a8124 100644 --- a/Source/Engine/ContentExporters/ExportModel.cpp +++ b/Source/Engine/ContentExporters/ExportModel.cpp @@ -34,7 +34,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) DeleteMe outputDeleteMe(output); const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi(); - output->WriteTextFormatted("# Exported model {0}\n", name.Get()); + output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get())); // Extract all meshes const auto& lod = asset->LODs[lodIndex]; @@ -63,12 +63,12 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) } auto ib = stream.Read(indicesCount * ibStride); - output->WriteTextFormatted("# Mesh {0}\n", meshIndex); + output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex)); for (uint32 i = 0; i < vertices; i++) { auto v = vb0[i].Position; - output->WriteTextFormatted("v {0} {1} {2}\n", v.X, v.Y, v.Z); + output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z)); } output->WriteChar('\n'); @@ -76,7 +76,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) for (uint32 i = 0; i < vertices; i++) { auto v = vb1[i].TexCoord; - output->WriteTextFormatted("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y)); + output->WriteText(StringAnsi::Format("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y))); } output->WriteChar('\n'); @@ -84,7 +84,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) for (uint32 i = 0; i < vertices; i++) { auto v = vb1[i].Normal.ToVector3() * 2.0f - 1.0f; - output->WriteTextFormatted("vn {0} {1} {2}\n", v.X, v.Y, v.Z); + output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z)); } output->WriteChar('\n'); @@ -97,7 +97,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) auto i0 = vertexStart + *t++; auto i1 = vertexStart + *t++; auto i2 = vertexStart + *t++; - output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2); + output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2)); } } else @@ -108,7 +108,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context) auto i0 = vertexStart + *t++; auto i1 = vertexStart + *t++; auto i2 = vertexStart + *t++; - output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2); + output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2)); } } @@ -146,7 +146,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context DeleteMe outputDeleteMe(output); const auto name = StringUtils::GetFileNameWithoutExtension(asset->GetPath()).ToStringAnsi(); - output->WriteTextFormatted("# Exported model {0}\n", name.Get()); + output->WriteText(StringAnsi::Format("# Exported model {0}\n", name.Get())); // Extract all meshes const auto& lod = asset->LODs[lodIndex]; @@ -168,12 +168,12 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context auto vb0 = stream.Read(vertices); auto ib = stream.Read(indicesCount * ibStride); - output->WriteTextFormatted("# Mesh {0}\n", meshIndex); + output->WriteText(StringAnsi::Format("# Mesh {0}\n", meshIndex)); for (uint32 i = 0; i < vertices; i++) { auto v = vb0[i].Position; - output->WriteTextFormatted("v {0} {1} {2}\n", v.X, v.Y, v.Z); + output->WriteText(StringAnsi::Format("v {0} {1} {2}\n", v.X, v.Y, v.Z)); } output->WriteChar('\n'); @@ -181,7 +181,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context for (uint32 i = 0; i < vertices; i++) { auto v = vb0[i].TexCoord; - output->WriteTextFormatted("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y)); + output->WriteText(StringAnsi::Format("vt {0} {1}\n", Float16Compressor::Decompress(v.X), Float16Compressor::Decompress(v.Y))); } output->WriteChar('\n'); @@ -189,7 +189,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context for (uint32 i = 0; i < vertices; i++) { auto v = vb0[i].Normal.ToVector3() * 2.0f - 1.0f; - output->WriteTextFormatted("vn {0} {1} {2}\n", v.X, v.Y, v.Z); + output->WriteText(StringAnsi::Format("vn {0} {1} {2}\n", v.X, v.Y, v.Z)); } output->WriteChar('\n'); @@ -202,7 +202,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context auto i0 = vertexStart + *t++; auto i1 = vertexStart + *t++; auto i2 = vertexStart + *t++; - output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2); + output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2)); } } else @@ -213,7 +213,7 @@ ExportAssetResult AssetExporters::ExportSkinnedModel(ExportAssetContext& context auto i0 = vertexStart + *t++; auto i1 = vertexStart + *t++; auto i2 = vertexStart + *t++; - output->WriteTextFormatted("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2); + output->WriteText(StringAnsi::Format("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n", i0, i1, i2)); } } diff --git a/Source/Engine/Serialization/Stream.cpp b/Source/Engine/Serialization/Stream.cpp index 067c5e269..28874f976 100644 --- a/Source/Engine/Serialization/Stream.cpp +++ b/Source/Engine/Serialization/Stream.cpp @@ -523,6 +523,12 @@ void WriteStream::WriteText(const StringView& text) WriteChar(text[i]); } +void WriteStream::WriteText(const StringAnsiView& text) +{ + for (int32 i = 0; i < text.Length(); i++) + WriteChar(text[i]); +} + void WriteStream::WriteString(const StringView& data) { const int32 length = data.Length(); diff --git a/Source/Engine/Serialization/WriteStream.h b/Source/Engine/Serialization/WriteStream.h index 1bc4ee692..97bb4c66d 100644 --- a/Source/Engine/Serialization/WriteStream.h +++ b/Source/Engine/Serialization/WriteStream.h @@ -165,6 +165,7 @@ public: // Writes text to the stream // @param data Text to write void WriteText(const StringView& text); + void WriteText(const StringAnsiView& text); // Writes String to the stream // @param data Data to write