Merge branch 'master' into mac

This commit is contained in:
Wojtek Figat
2022-01-09 19:15:15 +01:00
94 changed files with 1726 additions and 499 deletions

View File

@@ -137,7 +137,7 @@ extern FLAXENGINE_API const Char* ToString(const BuildConfiguration configuratio
/// <summary>
/// Game cooking temporary data.
/// </summary>
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:

View File

@@ -171,7 +171,7 @@ CookingData::Statistics::Statistics()
}
CookingData::CookingData(const SpawnParams& params)
: PersistentScriptingObject(params)
: ScriptingObject(params)
{
}

View File

@@ -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);
}

View File

@@ -102,6 +102,16 @@ namespace FlaxEditor.CustomEditors
/// </summary>
protected virtual void OnModified()
{
var parent = ParentEditor;
while (parent != null)
{
if (parent is SyncPointEditor syncPointEditor)
{
syncPointEditor.OnModified();
break;
}
parent = parent.ParentEditor;
}
}
/// <inheritdoc />

View File

@@ -52,7 +52,7 @@ namespace FlaxEditor.GUI.Timeline
/// </summary>
/// <param name="undo">The undo/redo to use for the history actions recording. Optional, can be null to disable undo support.</param>
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());
}
/// <summary>

View File

@@ -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;
/// <summary>
@@ -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
/// <inheritdoc />
public override void OnLostFocus()
{
_leftMouseDown = false;
if (_isSelecting)
{
_isSelecting = false;
EndMouseCapture();
}
base.OnLostFocus();
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
_isSelecting = false;
EndMouseCapture();
base.OnEndMouseCapture();
}
/// <inheritdoc />
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);
}

View File

@@ -91,7 +91,7 @@ namespace FlaxEditor.GUI.Timeline
/// <param name="media">The media.</param>
protected ProxyBase(TTrack track, TMedia media)
{
Track = track ?? throw new ArgumentNullException(nameof(track));
Track = track;
Media = media ?? throw new ArgumentNullException(nameof(media));
}
}
@@ -215,7 +215,7 @@ namespace FlaxEditor.GUI.Timeline
/// <summary>
/// Gets a value indicating whether this media can be resized (duration changed).
/// </summary>
public bool CanResize;
public bool CanResize = true;
/// <summary>
/// Initializes a new instance of the <see cref="Media"/> class.
@@ -229,8 +229,9 @@ namespace FlaxEditor.GUI.Timeline
/// Called when showing timeline context menu to the user. Can be used to add custom buttons.
/// </summary>
/// <param name="menu">The menu.</param>
/// <param name="time">The time (in seconds) at which context menu is shown (user clicked on a timeline).</param>
/// <param name="controlUnderMouse">The found control under the mouse cursor.</param>
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);
@@ -341,14 +342,15 @@ 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;
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 +386,26 @@ 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
if (!_timeline.SelectedMedia.Contains(this))
_timeline.Select(this);
}
_timeline.OnKeyframesMove(null, this, location, true, false);
return true;
}
@@ -417,7 +436,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 +456,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;
}
@@ -443,6 +472,20 @@ namespace FlaxEditor.GUI.Timeline
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
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;
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
@@ -484,22 +527,30 @@ namespace FlaxEditor.GUI.Timeline
private void EndMoving()
{
_isMoving = false;
_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))
_startMoveLeftEdge = false;
_startMoveRightEdge = false;
// 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();
}

View File

@@ -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<Track> _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
}
/// <summary>
/// Deletes the selected tracks/media events.
/// Deletes the selected tracks.
/// </summary>
/// <param name="withUndo">True if use undo/redo action for track removing.</param>
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<Track>(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<Track>(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<IUndoAction>();
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<IUndoAction>();
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();
}
/// <summary>
@@ -1539,6 +1536,33 @@ namespace FlaxEditor.GUI.Timeline
MarkAsEdited();
}
/// <summary>
/// Adds the media.
/// </summary>
/// <param name="track">The track to add media to.</param>
/// <param name="media">The media to add.</param>
/// <param name="withUndo">True if use undo/redo action for media adding.</param>
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();
Select(media);
}
/// <summary>
/// Called to delete media.
/// </summary>
@@ -1551,89 +1575,82 @@ namespace FlaxEditor.GUI.Timeline
}
/// <summary>
/// Duplicates the selected tracks/media events.
/// Duplicates the selected tracks.
/// </summary>
/// <param name="withUndo">True if use undo/redo action for track duplication.</param>
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<Track>(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<Track>(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<IUndoAction>();
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<IUndoAction>();
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();
}
/// <summary>
@@ -1885,6 +1902,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))
@@ -1895,19 +1915,31 @@ 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, ref location, media.Track));
menu.AddButton("Edit media", () => ShowEditPopup(media.PropertiesEditObject, location, media.Track));
}
}
else
{
OnKeyframesDeselect(null);
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, 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);
@@ -2067,6 +2099,9 @@ namespace FlaxEditor.GUI.Timeline
case KeyboardKeys.S:
Split(CurrentFrame);
return true;
case KeyboardKeys.Delete:
OnKeyframesDelete(null);
return true;
}
return false;
@@ -2089,7 +2124,7 @@ namespace FlaxEditor.GUI.Timeline
/// <param name="obj">The object.</param>
/// <param name="location">The show location (in timeline space).</param>
/// <param name="undoContext">The undo context object.</param>
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);
@@ -2145,6 +2180,11 @@ namespace FlaxEditor.GUI.Timeline
if (_tracks[i] is IKeyframesEditorContext trackContext)
trackContext.OnKeyframesDeselect(editor);
}
if (SelectedMedia.Count != 0)
{
SelectedMedia.Clear();
OnSelectionChanged();
}
}
/// <inheritdoc />
@@ -2152,17 +2192,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();
}
}
/// <inheritdoc />
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 +2240,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<Track>();
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();
}
}
/// <inheritdoc />
@@ -2190,6 +2290,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<Track>();
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;
}
}
}
/// <inheritdoc />

View File

@@ -1028,6 +1028,15 @@ namespace FlaxEditor.GUI.Timeline
return true;
}
/// <summary>
/// Called when showing timeline context menu to the user. Can be used to add custom buttons.
/// </summary>
/// <param name="menu">The menu.</param>
/// <param name="time">The time (in seconds) at which context menu is shown (user clicked on a timeline).</param>
public virtual void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time)
{
}
/// <summary>
/// Called when context menu is being prepared to show. Can be used to add custom options.
/// </summary>
@@ -1051,7 +1060,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 +1301,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;

View File

@@ -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
{
/// <summary>
/// The timeline media for <see cref="AnimEvent"/> and <see cref="AnimContinuousEvent"/>.
/// </summary>
public sealed class AnimationEventMedia : Media
{
private sealed class ProxyEditor : SyncPointEditor
{
/// <inheritdoc />
public override IEnumerable<object> UndoObjects => Values;
/// <inheritdoc />
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<AnimationEventTrack, AnimationEventMedia>
{
[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;
/// <summary>
/// The event type.
/// </summary>
public ScriptType Type;
/// <summary>
/// The event instance.
/// </summary>
public AnimEvent Instance;
/// <summary>
/// True if event is continuous (with duration), not a single frame.
/// </summary>
public bool IsContinuous;
/// <summary>
/// Initializes a new instance of the <see cref="AnimationEventMedia"/> class.
/// </summary>
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;
}
/// <summary>
/// Initializes track for the specified type.
/// </summary>
/// <param name="type">The type.</param>
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;
}
}
/// <inheritdoc />
protected override void OnDurationFramesChanged()
{
if (Type != ScriptType.Null)
DurationFrames = IsContinuous ? Mathf.Max(DurationFrames, 2) : 1;
base.OnDurationFramesChanged();
}
/// <inheritdoc />
public override void OnDestroy()
{
Type = ScriptType.Null;
Object.Destroy(ref Instance);
if (_isRegisteredForScriptsReload)
{
_isRegisteredForScriptsReload = false;
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
}
base.OnDestroy();
}
}
/// <summary>
/// The timeline track for <see cref="AnimEvent"/> and <see cref="AnimContinuousEvent"/>.
/// </summary>
public sealed class AnimationEventTrack : Track
{
/// <summary>
/// Gets the archetype.
/// </summary>
/// <returns>The archetype.</returns>
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);
}
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public override void OnTimelineContextMenu(ContextMenu.ContextMenu menu, float time)
{
base.OnTimelineContextMenu(menu, time);
AddContextMenu(menu, time);
}
/// <inheritdoc />
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);
}
}
}

View File

@@ -324,12 +324,12 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
/// <inheritdoc />
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);
}
/// <inheritdoc />

View File

@@ -101,9 +101,9 @@ namespace FlaxEditor.GUI.Timeline.Tracks
}
/// <inheritdoc />
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)
{

View File

@@ -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;
@@ -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
@@ -499,5 +494,5 @@ void ManagedEditor::DestroyManaged()
Internal_OnVisualScriptingDebugFlow = nullptr;
// Base
PersistentScriptingObject::DestroyManaged();
ScriptingObject::DestroyManaged();
}

View File

@@ -14,7 +14,7 @@ namespace CSG
/// <summary>
/// Managed Editor root object
/// </summary>
class ManagedEditor : public PersistentScriptingObject
class ManagedEditor : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(ManagedEditor);
@@ -144,7 +144,6 @@ private:
public:
// [PersistentScriptingObject]
String ToString() const override;
// [ScriptingObject]
void DestroyManaged() override;
};

View File

@@ -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"
/// <summary>

View File

@@ -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
/// <inheritdoc />
public override void OnDestroy()
{
ScriptsBuilder.ScriptsReloadBegin -= OnScriptsReloadBegin;
Object.Destroy(ref _floorModel);
Object.Destroy(ref _previewModel);
NodesMask = null;

View File

@@ -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
};

View File

@@ -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();
}
}

View File

@@ -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();
}

View File

@@ -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)
{
}

View File

@@ -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;
/// <summary>
/// The animation notification event triggered during animation playback.
/// </summary>
API_CLASS(Abstract) class FLAXENGINE_API AnimEvent : public ScriptingObject, public ISerializable
{
DECLARE_SCRIPTING_TYPE(AnimEvent);
#if USE_EDITOR
/// <summary>
/// Event display color in the Editor.
/// </summary>
API_FIELD(Attributes="HideInEditor, NoSerialize") Color Color = Color::White;
#endif
/// <summary>
/// Animation event notification.
/// </summary>
/// <param name="actor">The animated model actor instance.</param>
/// <param name="anim">The source animation.</param>
/// <param name="time">The current animation time (in seconds).</param>
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
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;
};
/// <summary>
/// The animation notification event (with duration) triggered during animation playback that contains begin and end (event notification is received as a tick).
/// </summary>
API_CLASS(Abstract) class FLAXENGINE_API AnimContinuousEvent : public AnimEvent
{
DECLARE_SCRIPTING_TYPE(AnimContinuousEvent);
/// <summary>
/// Animation notification called before the first event.
/// </summary>
/// <param name="actor">The animated model actor instance.</param>
/// <param name="anim">The source animation.</param>
/// <param name="time">The current animation time (in seconds).</param>
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
API_FUNCTION() virtual void OnBegin(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
{
}
/// <summary>
/// Animation notification called after the last event (guaranteed to be always called).
/// </summary>
/// <param name="actor">The animated model actor instance.</param>
/// <param name="anim">The source animation.</param>
/// <param name="time">The current animation time (in seconds).</param>
/// <param name="deltaTime">The current animation tick delta time (in seconds).</param>
API_FUNCTION() virtual void OnEnd(AnimatedModel* actor, Animation* anim, float time, float deltaTime)
{
}
};

View File

@@ -14,7 +14,13 @@ namespace AnimationUtils
template<class T>
FORCE_INLINE static T GetZero()
{
return 0.0f;
return T();
}
template<>
FORCE_INLINE int32 GetZero<int32>()
{
return 0;
}
template<>

View File

@@ -769,6 +769,12 @@ public:
}
};
/// <summary>
/// An animation spline represented by a set of keyframes, each representing a value point.
/// </summary>
template<typename T>
using StepCurve = Curve<T, StepCurveKeyframe<T>>;
/// <summary>
/// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
/// </summary>

View File

@@ -186,6 +186,7 @@ namespace Serialization
// Raw keyframes data
stream.WriteInt32(keyframes.Count());
static_assert(TIsPODType<T>::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<T>::Value, "Raw bytes serialization only for raw POD types.");
stream.ReadBytes(keyframes.Get(), keyframes.Count() * sizeof(KeyFrame));
return false;

View File

@@ -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);

View File

@@ -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
/// </summary>
class FLAXENGINE_API AnimGraphInstanceData
{
friend AnimGraphExecutor;
public:
// ---- Quick documentation ----
@@ -384,7 +386,7 @@ public:
/// <summary>
/// The slots animations.
/// </summary>
Array<AnimGraphSlot> Slots;
Array<AnimGraphSlot, InlinedAllocation<4>> Slots;
public:
@@ -402,6 +404,18 @@ public:
/// Invalidates the update timer.
/// </summary>
void Invalidate();
private:
struct Event
{
AnimEvent* Instance;
Animation* Anim;
AnimGraphNode* Node;
bool Hit;
};
Array<Event, InlinedAllocation<8>> Events;
};
/// <summary>

View File

@@ -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<float>(anim->Data.FramesPerSecond);
const float eventDeltaTime = (animPos - animPrevPos) / static_cast<float>(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>());
((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;
}

View File

@@ -42,6 +42,7 @@ public:
CameraCut = 16,
//AnimationChannel = 17,
//AnimationChannelData = 18,
//AnimationEvent = 19,
};
enum class Flags

View File

@@ -7,17 +7,17 @@
/// <summary>
/// Represents a single audio device.
/// </summary>
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;

View File

@@ -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<int32, int32> 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<AnimEvent>(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<Animation, &Animation::OnScriptsReloadStart>(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);
@@ -419,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
@@ -432,6 +533,7 @@ Asset::LoadResult Animation::load()
switch (headerVersion)
{
case 100:
case 101:
{
stream.ReadInt32(&headerVersion);
stream.ReadDouble(&Data.Duration);
@@ -471,13 +573,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<AnimEvent>(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<Animation, &Animation::OnScriptsReloadStart>(this);
}
#endif
}
}
}
return LoadResult::Ok;
}
void Animation::unload(bool isReloading)
{
#if USE_EDITOR
if (_registeredForScriptingReload)
{
_registeredForScriptingReload = false;
Level::ScriptsReloadStart.Unbind<Animation, &Animation::OnScriptsReloadStart>(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

View File

@@ -7,6 +7,7 @@
#include "Engine/Animations/AnimationData.h"
class SkinnedModel;
class AnimEvent;
/// <summary>
/// Asset that contains an animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
@@ -18,7 +19,7 @@ DECLARE_BINARY_ASSET_HEADER(Animation, 1);
/// <summary>
/// Contains basic information about the animation asset contents.
/// </summary>
API_STRUCT() struct InfoData
API_STRUCT() struct FLAXENGINE_API InfoData
{
DECLARE_SCRIPTING_TYPE_NO_SPAWN(InfoData);
@@ -48,6 +49,25 @@ DECLARE_BINARY_ASSET_HEADER(Animation, 1);
API_FIELD() int32 MemoryUsage;
};
/// <summary>
/// Contains <see cref="AnimEvent"/> instance.
/// </summary>
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:
/// <summary>
@@ -55,6 +75,11 @@ public:
/// </summary>
AnimationData Data;
/// <summary>
/// The animation events (keyframes per named track).
/// </summary>
Array<Pair<String, StepCurve<AnimEventData>>> Events;
/// <summary>
/// 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).
@@ -138,6 +163,11 @@ private:
void OnSkinnedModelUnloaded(Asset* obj);
public:
// [BinaryAsset]
void OnScriptingDispose() override;
protected:
// [BinaryAsset]

View File

@@ -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;
}

View File

@@ -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"

View File

@@ -34,7 +34,7 @@ ExportAssetResult AssetExporters::ExportModel(ExportAssetContext& context)
DeleteMe<FileWriteStream> 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<byte>(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<FileWriteStream> 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<VB0SkinnedElementType>(vertices);
auto ib = stream.Read<byte>(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));
}
}

View File

@@ -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 <iostream>
#define LOG_ENABLE_FILE (!PLATFORM_SWITCH)

View File

@@ -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);
}

View File

@@ -37,30 +37,13 @@ public:
/// <summary>
/// Finalizes an instance of the <see cref="Object"/> class.
/// </summary>
virtual ~Object()
{
}
virtual ~Object();
/// <summary>
/// Gets the string representation of this object.
/// </summary>
/// <returns>The string.</returns>
virtual String ToString() const = 0;
};
/// <summary>
/// Interface for removable Engine objects.
/// </summary>
/// <seealso cref="Object" />
class FLAXENGINE_API RemovableObject : public Object
{
public:
/// <summary>
/// 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.
/// </summary>
virtual ~RemovableObject();
/// <summary>
/// Deletes the object without queueing it to the ObjectsRemovalService.
@@ -68,7 +51,7 @@ public:
void DeleteObjectNow();
/// <summary>
/// Deletes the object.
/// Deletes the object (deferred).
/// </summary>
/// <param name="timeToLive">The time to live (in seconds). Use zero to kill it now.</param>
/// <param name="useGameTime">True if unscaled game time for the object life timeout, otherwise false to use absolute time.</param>
@@ -82,3 +65,6 @@ public:
Delete(this);
}
};
// [Deprecated on 5.01.2022, expires on 5.01.2024]
typedef Object RemovableObject;

View File

@@ -16,8 +16,8 @@ namespace ObjectsRemovalServiceImpl
CriticalSection NewItemsLocker;
DateTime LastUpdate;
float LastUpdateGameTime;
Dictionary<RemovableObject*, float> Pool(8192);
Dictionary<RemovableObject*, float> NewItemsPool(2048);
Dictionary<Object*, float> Pool(8192);
Dictionary<Object*, float> 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);
}

View File

@@ -16,7 +16,7 @@ public:
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>True if object has been registered in the pool for the removing, otherwise false.</returns>
static bool IsInPool(RemovableObject* obj);
static bool IsInPool(Object* obj);
/// <summary>
/// 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).
/// </summary>
/// <param name="obj">The object.</param>
static void Dereference(RemovableObject* obj);
static void Dereference(Object* obj);
/// <summary>
/// Adds the specified object to the dead pool.
@@ -36,7 +36,7 @@ public:
/// <param name="obj">The object.</param>
/// <param name="timeToLive">The time to live (in seconds).</param>
/// <param name="useGameTime">True if unscaled game time for the object life timeout, otherwise false to use absolute time.</param>
static void Add(RemovableObject* obj, float timeToLive = 1.0f, bool useGameTime = false);
static void Add(Object* obj, float timeToLive = 1.0f, bool useGameTime = false);
/// <summary>
/// Flushes the objects pool removing objects marked to remove now (with negative or zero time to live).

View File

@@ -337,6 +337,8 @@ public:
void ReserveSpace(int32 length)
{
ASSERT(length >= 0);
if (length == _length)
return;
Platform::Free(_data);
if (length != 0)
{

View File

@@ -7,7 +7,7 @@
#include "Foliage.h"
FoliageType::FoliageType()
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
, Foliage(nullptr)
, Index(-1)
{

View File

@@ -41,7 +41,7 @@ API_ENUM() enum class FoliageScalingModes
/// <summary>
/// Foliage mesh instances type descriptor. Defines the shared properties of the spawned mesh instances.
/// </summary>
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;

View File

@@ -14,13 +14,13 @@
/// <summary>
/// Interface for GPU device adapter.
/// </summary>
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))
{
}

View File

@@ -6,7 +6,7 @@
#include "Textures/GPUTexture.h"
GPUContext::GPUContext(GPUDevice* device)
: PersistentScriptingObject(ScriptingObjectSpawnParams(Guid::New(), TypeInitializer))
: ScriptingObject(ScriptingObjectSpawnParams(Guid::New(), TypeInitializer))
, _device(device)
{
}

View File

@@ -113,7 +113,7 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(GPUDrawIndexedIndirectArgs);
/// <summary>
/// Interface for GPU device context that can record and send graphics commands to the GPU in a sequence.
/// </summary>
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:

View File

@@ -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)

View File

@@ -29,7 +29,7 @@ class MaterialBase;
/// <summary>
/// Graphics device object for rendering on GPU.
/// </summary>
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:

View File

@@ -15,7 +15,7 @@
/// <summary>
/// The base class for all GPU resources.
/// </summary>
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:
/// <summary>
/// Interface for GPU resources views. Shared base class for texture and buffer views.
/// </summary>
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)
{
}

View File

@@ -167,9 +167,9 @@ struct SerializedMaterialParam
/// <summary>
/// Material variable object. Allows to modify material parameter value at runtime.
/// </summary>
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:

View File

@@ -10,9 +10,9 @@
/// <summary>
/// The material slot descriptor that specifies how to render geometry using it.
/// </summary>
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);
/// <summary>
/// The material to use for rendering.

View File

@@ -14,7 +14,7 @@ class ModelBase;
/// <summary>
/// Base class for model resources meshes.
/// </summary>
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)
{
}

View File

@@ -10,9 +10,9 @@ class MemoryReadStream;
/// <summary>
/// Represents single Level Of Detail for the model. Contains a collection of the meshes.
/// </summary>
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:

View File

@@ -10,9 +10,9 @@ class MemoryReadStream;
/// <summary>
/// Represents single Level Of Detail for the skinned model. Contains a collection of the meshes.
/// </summary>
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:

View File

@@ -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);
@@ -183,8 +183,3 @@ void RenderBuffers::Release()
UPDATE_LAZY_KEEP_RT(LuminanceMap);
#undef UPDATE_LAZY_KEEP_RT
}
String RenderBuffers::ToString() const
{
return TEXT("RenderBuffers");
}

View File

@@ -19,7 +19,7 @@
/// <summary>
/// The scene rendering buffers container.
/// </summary>
API_CLASS() class FLAXENGINE_API RenderBuffers : public PersistentScriptingObject
API_CLASS() class FLAXENGINE_API RenderBuffers : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE(RenderBuffers);
protected:
@@ -175,9 +175,4 @@ public:
/// Release the buffers data.
/// </summary>
API_FUNCTION() void Release();
public:
// [PersistentScriptingObject]
String ToString() const override;
};

View File

@@ -82,7 +82,7 @@ void RenderTask::DrawAll()
}
RenderTask::RenderTask(const SpawnParams& params)
: PersistentScriptingObject(params)
: ScriptingObject(params)
{
// Register
TasksLocker.Lock();

View File

@@ -24,7 +24,7 @@ class Actor;
/// <summary>
/// Allows to perform custom rendering using graphics pipeline.
/// </summary>
API_CLASS() class FLAXENGINE_API RenderTask : public PersistentScriptingObject
API_CLASS() class FLAXENGINE_API RenderTask : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE(RenderTask);

View File

@@ -11,7 +11,7 @@
/// <summary>
/// Base class for all input device objects.
/// </summary>
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)
{
}

View File

@@ -971,7 +971,7 @@ private:
public:
// [PersistentScriptingObject]
// [ScriptingObject]
String ToString() const override;
void OnDeleteObject() override;

View File

@@ -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<byte> Data;
};
#endif
namespace LevelImpl
{
Array<SceneAction*> _sceneActions;
CriticalSection _sceneActionsLocker;
DateTime _lastSceneLoadTime(0);
#if USE_EDITOR
Array<ScriptsReloadObject> ScriptsReloadObjects;
#endif
void CallSceneEvent(SceneEventType eventType, Scene* scene, Guid sceneId);
@@ -128,9 +143,11 @@ Delegate<Scene*, const Guid&> Level::SceneLoaded;
Delegate<Scene*, const Guid&> Level::SceneLoadError;
Delegate<Scene*, const Guid&> Level::SceneUnloading;
Delegate<Scene*, const Guid&> Level::SceneUnloaded;
#if USE_EDITOR
Action Level::ScriptsReloadStart;
Action Level::ScriptsReload;
Action Level::ScriptsReloadEnd;
#endif
Array<String> Level::Tags;
String Level::Layers[32];
@@ -559,6 +576,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<ISerializable>(*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 +630,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<ISerializable>(obj))
e.Data = JsonSerializer::SaveToBytes(serializable);
ScriptingObject* o = obj;
obj = nullptr;
o->DeleteObjectNow();
}
#endif
class SpawnActorAction : public SceneAction
@@ -605,9 +654,9 @@ public:
ScriptingObjectReference<Actor> ParentActor;
SpawnActorAction(Actor* actor, Actor* parent)
: TargetActor(actor)
, ParentActor(parent)
{
TargetActor = actor;
ParentActor = parent;
}
bool Do() const override
@@ -623,8 +672,8 @@ public:
ScriptingObjectReference<Actor> TargetActor;
DeleteActorAction(Actor* actor)
: TargetActor(actor)
{
TargetActor = actor;
}
bool Do() const override

View File

@@ -215,6 +215,8 @@ public:
/// </summary>
API_EVENT() static Delegate<Scene*, const Guid&> SceneUnloaded;
#if USE_EDITOR
/// <summary>
/// Fired when scene starts reloading scripts.
/// </summary>
@@ -230,6 +232,14 @@ public:
/// </summary>
API_EVENT() static Action ScriptsReloadEnd;
/// <summary>
/// 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.
/// </summary>
/// <param name="obj">Reference to the object to preserve during the scripting reload.</param>
static void ScriptsReloadRegisterObject(ScriptingObject*& obj);
#endif
public:
/// <summary>

View File

@@ -55,7 +55,7 @@ typedef Dictionary<Guid, Actor*, HeapAllocation> ActorsLookup;
/// <summary>
/// Base class for objects that are parts of the scene (actors and scripts).
/// </summary>
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

View File

@@ -51,7 +51,7 @@ void SendPacketToPeer(ENetPeer* peer, const NetworkChannelType channelType, cons
}
ENetDriver::ENetDriver(const SpawnParams& params)
: PersistentScriptingObject(params)
: ScriptingObject(params)
{
}

View File

@@ -13,7 +13,7 @@
/// <summary>
/// Low-level network transport interface implementation based on ENet library.
/// </summary>
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:

View File

@@ -11,7 +11,7 @@
/// <summary>
/// Low-level network peer class. Provides server-client communication functions, message processing and sending.
/// </summary>
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 <see cref="NetworkPeer"/> class.
/// </summary>
NetworkPeer()
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
{
}

View File

@@ -12,7 +12,7 @@
/// <summary>
/// Particle system parameter.
/// </summary>
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 <see cref="ParticleEffectParameter"/> class.
/// </summary>
ParticleEffectParameter()
: PersistentScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
: ScriptingObject(SpawnParams(Guid::New(), TypeInitializer))
{
}

View File

@@ -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;

View File

@@ -6,6 +6,8 @@
#include "Actors/PhysicsColliderActor.h"
#include <ThirdParty/PhysX/PxScene.h>
#include <ThirdParty/PhysX/PxQueryFiltering.h>
#include <ThirdParty/PhysX/PxRigidDynamic.h>
#include <ThirdParty/PhysX/characterkinematic/PxController.h>
// 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

View File

@@ -112,6 +112,11 @@ DECLARE_SCRIPTING_TYPE_NO_SPAWN(Physics);
/// </summary>
static PxQueryFilterCallback* GetCharacterQueryFilterCallback();
/// <summary>
/// Gets the default controller filter callback used for the character controller collisions detection.
/// </summary>
static PxControllerFilterCallback* GetCharacterControllerFilterCallback();
/// <summary>
/// Gets the default physical material.
/// </summary>

View File

@@ -39,6 +39,7 @@ namespace physx
class PxController;
class PxCapsuleController;
class PxQueryFilterCallback;
class PxControllerFilterCallback;
class PxHeightField;
struct PxFilterData;
struct PxRaycastHit;

View File

@@ -112,7 +112,7 @@ UserBase::UserBase(const String& name)
}
UserBase::UserBase(const SpawnParams& params, const String& name)
: PersistentScriptingObject(params)
: ScriptingObject(params)
, _name(name)
{
}

View File

@@ -10,7 +10,7 @@ API_INJECT_CPP_CODE("#include \"Engine/Platform/User.h\"");
/// <summary>
/// Native platform user object.
/// </summary>
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:

View File

@@ -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

View File

@@ -269,7 +269,7 @@ API_INJECT_CPP_CODE("#include \"Engine/Platform/Window.h\"");
/// Native platform window object.
/// </summary>
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;
@@ -949,7 +949,7 @@ private:
public:
// [PersistentScriptingObject]
// [ScriptingObject]
String ToString() const override;
void OnDeleteObject() override;
};

View File

@@ -369,7 +369,7 @@ bool DrawCallsList::IsEmpty() const
}
RenderList::RenderList(const SpawnParams& params)
: PersistentScriptingObject(params)
: ScriptingObject(params)
, DirectionalLights(4)
, PointLights(32)
, SpotLights(32)

View File

@@ -322,7 +322,7 @@ struct DrawCallsList
/// <summary>
/// Rendering cache container object for the draw calls collecting, sorting and executing.
/// </summary>
API_CLASS(Sealed) class FLAXENGINE_API RenderList : public PersistentScriptingObject
API_CLASS(Sealed) class FLAXENGINE_API RenderList : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE(RenderList);

View File

@@ -5,7 +5,7 @@ using System;
namespace FlaxEngine
{
/// <summary>
/// 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).
/// </summary>
/// <seealso cref="System.Attribute" />
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property | AttributeTargets.Parameter)]

View File

@@ -197,7 +197,7 @@ struct MConverter<T*, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Va
// Converter for Scripting Objects (collection of values).
template<typename T>
struct MConverter<T, typename TEnableIf<TIsBaseOf<class PersistentScriptingObject, T>::Value>::Type>
struct MConverter<T, typename TEnableIf<TIsBaseOf<class ScriptingObject, T>::Value>::Type>
{
MonoObject* Box(const T& data, MonoClass* klass)
{

View File

@@ -134,7 +134,7 @@ private:
public:
// [PersistentScriptingObject]
// [ScriptingObject]
String ToString() const override;
void OnDeleteObject() override;

View File

@@ -38,20 +38,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
@@ -207,18 +219,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, &param);
}
}
mono_gchandle_free(handle);
return true;
}
#endif
// Ensure to be registered
if (!IsRegistered())
RegisterObject();
return false;
}
#if USE_MONO
@@ -233,8 +276,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()));
@@ -358,7 +409,7 @@ void ScriptingObject::OnDeleteObject()
UnregisterObject();
// Base
RemovableObject::OnDeleteObject();
Object::OnDeleteObject();
}
String ScriptingObject::ToString() const
@@ -371,6 +422,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
@@ -411,70 +480,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, &param);
}
}
mono_gchandle_free(handle);
return true;
}
#endif
// Ensure to be registered
if (!IsRegistered())
RegisterObject();
return false;
}
class ScriptingObjectInternal
{
public:
@@ -746,7 +751,7 @@ public:
static ScriptingObject* Spawn(const ScriptingObjectSpawnParams& params)
{
return New<PersistentScriptingObject>(params);
return New<ScriptingObject>(params);
}
};

View File

@@ -8,9 +8,9 @@
#include "ManagedCLR/MTypes.h"
/// <summary>
/// Represents object from unmanaged memory that can use accessed via C# scripting.
/// Represents object from unmanaged memory that can use accessed via scripting.
/// </summary>
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:
/// <summary>
/// Spawns a new objects of the given type.
/// </summary>
// Spawns a new objects of the given type.
static ScriptingObject* NewObject(const ScriptingTypeHandle& typeHandle);
template<typename T>
static T* Spawn()
static T* NewObject()
{
const SpawnParams params(Guid::New(), T::TypeInitializer);
return T::New(params);
return (T*)NewObject(T::TypeInitializer);
}
template<typename T>
static T* NewObject(const ScriptingTypeHandle& typeHandle)
{
auto obj = NewObject(typeHandle);
if (obj && !obj->Is<T>())
{
Delete(obj);
obj = nullptr;
}
return (T*)obj;
}
public:
@@ -182,7 +191,7 @@ public:
virtual void OnManagedInstanceDeleted();
virtual void OnScriptingDispose();
virtual bool CreateManaged() = 0;
virtual bool CreateManaged();
virtual void DestroyManaged();
public:
@@ -216,7 +225,7 @@ protected:
public:
// [RemovableObject]
// [Object]
void OnDeleteObject() override;
String ToString() const override;
};
@@ -239,32 +248,17 @@ public:
public:
// [ScriptingObject]
bool CreateManaged() override;
};
/// <summary>
/// Managed object that uses pinned GC handle to prevent collecting and moving.
/// Used by the objects that lifetime is controlled by the C++ side.
/// </summary>
API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public ScriptingObject
{
public:
/// <summary>
/// Initializes a new instance of the <see cref="PersistentScriptingObject"/> class.
/// </summary>
/// <param name="params">The object initialization parameters.</param>
explicit PersistentScriptingObject(const SpawnParams& params);
/// <summary>
/// Finalizes an instance of the <see cref="PersistentScriptingObject"/> class.
/// </summary>
~PersistentScriptingObject();
public:
// [ManagedScriptingObject]
void OnManagedInstanceDeleted() override;
void OnScriptingDispose() override;
bool CreateManaged() override;
};
/// <summary>
/// Use ScriptingObject instead.
/// [Deprecated on 5.01.2022, expires on 5.01.2024]
/// </summary>
API_CLASS(InBuild) class FLAXENGINE_API PersistentScriptingObject : public ScriptingObject
{
public:
PersistentScriptingObject(const SpawnParams& params);
};

View File

@@ -15,10 +15,7 @@ using Newtonsoft.Json.Serialization;
namespace FlaxEngine.Json
{
/// <summary>
/// Objects serialization tool (json format).
/// </summary>
public static class JsonSerializer
partial class JsonSerializer
{
internal class SerializerCache
{

View File

@@ -0,0 +1,41 @@
// 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"
/// <summary>
/// Objects serialization tool (json format).
/// </summary>
API_CLASS(Static, Namespace="FlaxEngine.Json") class FLAXENGINE_API JsonSerializer
{
DECLARE_SCRIPTING_TYPE_MINIMAL(JsonSerializer);
/// <summary>
/// Performs object Json serialization to the raw bytes.
/// </summary>
/// <param name="obj">The object to serialize (can be null).</param>
/// <returns>The output data.</returns>
API_FUNCTION() static Array<byte> SaveToBytes(ISerializable* obj);
/// <summary>
/// Performs object Json deserialization from the raw bytes.
/// </summary>
/// <param name="obj">The object to deserialize (can be null).</param>
/// <param name="data">The source data to read from.</param>
/// <param name="engineBuild">The engine build number of the saved data. Used to resolve old object formats when loading deprecated data.</param>
FORCE_INLINE static void LoadFromBytes(ISerializable* obj, const Array<byte>& data, int32 engineBuild)
{
LoadFromBytes(obj, Span<byte>(data.Get(), data.Count()), engineBuild);
}
/// <summary>
/// Performs object Json deserialization from the raw bytes.
/// </summary>
/// <param name="obj">The object to deserialize (can be null).</param>
/// <param name="data">The source data to read from.</param>
/// <param name="engineBuild">The engine build number of the saved data. Used to resolve old object formats when loading deprecated data.</param>
API_FUNCTION() static void LoadFromBytes(ISerializable* obj, const Span<byte>& data, int32 engineBuild);
};

View File

@@ -3,6 +3,7 @@
#pragma once
#include "ReadStream.h"
#include "Engine/Platform/Platform.h"
/// <summary>
/// Super fast advanced data reading from raw bytes without any overhead at all
@@ -33,8 +34,8 @@ public:
/// Init
/// </summary>
/// <param name="data">Array with data to read from</param>
template<typename T>
MemoryReadStream(const Array<T>& data)
template<typename T, typename AllocationType = HeapAllocation>
MemoryReadStream(const Array<T, AllocationType>& data)
: MemoryReadStream(data.Get(), data.Count() * sizeof(T))
{
}
@@ -52,8 +53,8 @@ public:
/// Init stream to the custom buffer location
/// </summary>
/// <param name="data">Array with data to read from</param>
template<typename T>
FORCE_INLINE void Init(const Array<T>& data)
template<typename T, typename AllocationType = HeapAllocation>
FORCE_INLINE void Init(const Array<T, AllocationType>& data)
{
Init(data.Get(), data.Count() * sizeof(T));
}

View File

@@ -2,27 +2,19 @@
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "Stream.h"
#include "Engine/Core/Templates.h"
struct CommonValue;
struct Variant;
struct VariantType;
class ISerializable;
/// <summary>
/// Base class for all data read streams
/// </summary>
class FLAXENGINE_API ReadStream : public Stream
{
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~ReadStream()
{
}
public:
/// <summary>
@@ -32,8 +24,6 @@ public:
/// <param name="bytes">Amount of bytes to read</param>
virtual void ReadBytes(void* data, uint32 bytes) = 0;
public:
template<typename T>
FORCE_INLINE void Read(T* data)
{
@@ -193,9 +183,10 @@ public:
/// Read data array
/// </summary>
/// <param name="data">Array to read</param>
template<typename T>
void ReadArray(Array<T>* data)
template<typename T, typename AllocationType = HeapAllocation>
void ReadArray(Array<T, AllocationType>* data)
{
static_assert(TIsPODType<T>::Value, "Only POD types are valid for ReadArray.");
int32 size;
ReadInt32(&size);
data->Resize(size, false);
@@ -203,6 +194,13 @@ public:
ReadBytes(data->Get(), size * sizeof(T));
}
/// <summary>
/// Deserializes object from Json by reading it as a raw data (ver+length+bytes).
/// </summary>
/// <remarks>Reads version number, data length and actual data bytes from the stream.</remarks>
/// <param name="obj">The object to deserialize.</param>
void ReadJson(ISerializable* obj);
public:
// [Stream]

View File

@@ -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,12 +494,41 @@ 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<MemoryReadStream*>(this))
{
JsonSerializer::LoadFromBytes(obj, Span<byte>((byte*)memoryStream->Read(size), size), engineBuild);
}
else
{
void* data = Allocator::Allocate(size);
ReadBytes(data, size);
JsonSerializer::LoadFromBytes(obj, Span<byte>((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++)
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();
@@ -519,7 +553,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);
@@ -734,3 +768,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<byte> JsonSerializer::SaveToBytes(ISerializable* obj)
{
Array<byte> 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<byte>& 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);
}

View File

@@ -2,6 +2,7 @@
#pragma once
#include "Engine/Core/Core.h"
#include "Engine/Core/Types/BaseTypes.h"
#define FILESTREAM_BUFFER_SIZE 4096

View File

@@ -2,28 +2,19 @@
#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;
struct VariantType;
class ISerializable;
/// <summary>
/// Base class for all data write streams
/// </summary>
class FLAXENGINE_API WriteStream : public Stream
{
public:
/// <summary>
/// Virtual destructor
/// </summary>
virtual ~WriteStream()
{
}
public:
/// <summary>
@@ -33,8 +24,6 @@ public:
/// <param name="bytes">Amount of bytes to write</param>
virtual void WriteBytes(const void* data, uint32 bytes) = 0;
public:
template<typename T>
FORCE_INLINE void Write(const T* data)
{
@@ -165,24 +154,6 @@ public:
WriteBytes((const void*)text, sizeof(Char) * length);
}
template<typename... Args>
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<typename... Args>
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()
{
@@ -194,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
@@ -211,7 +183,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 +203,8 @@ public:
/// Write data array
/// </summary>
/// <param name="data">Array to write</param>
template<typename T>
void WriteArray(const Array<T>& data)
template<typename T, typename AllocationType = HeapAllocation>
void WriteArray(const Array<T, AllocationType>& data)
{
static_assert(TIsPODType<T>::Value, "Only POD types are valid for WriteArray.");
const int32 size = data.Count();
@@ -241,6 +213,14 @@ public:
WriteBytes(data.Get(), size * sizeof(T));
}
/// <summary>
/// Serializes object to Json and writes it as a raw data (ver+length+bytes).
/// </summary>
/// <remarks>Writes version number, data length and actual data bytes to the stream.</remarks>
/// <param name="obj">The object to serialize.</param>
/// <param name="otherObj">The instance of the object to compare with and serialize only the modified properties. If null, then serialize all properties.</param>
void WriteJson(ISerializable* obj, const void* otherObj = nullptr);
public:
// [Stream]

View File

@@ -18,7 +18,7 @@ DECLARE_ENUM_EX_6(TaskState, int64, 0, Created, Failed, Canceled, Queued, Runnin
/// <summary>
/// Represents an asynchronous operation.
/// </summary>
class FLAXENGINE_API Task : public RemovableObject, public NonCopyable
class FLAXENGINE_API Task : public Object, public NonCopyable
{
//
// Tasks execution and states flow:

View File

@@ -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)
{
}

View File

@@ -10,7 +10,7 @@ class TaskGraph;
/// <summary>
/// System that can generate work into Task Graph for asynchronous execution.
/// </summary>
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:
/// <summary>
/// Graph-based asynchronous tasks scheduler for high-performance computing and processing.
/// </summary>
API_CLASS() class FLAXENGINE_API TaskGraph : public PersistentScriptingObject
API_CLASS() class FLAXENGINE_API TaskGraph : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE(TaskGraph);
private:

View File

@@ -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;*/

View File

@@ -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());
}
/// <summary>
/// Deserializes object from Json by reading it as a raw data (ver+length+bytes).
/// </summary>
/// <remarks>Reads version number, data length and actual data bytes from the stream.</remarks>
/// <param name="stream">The stream.</param>
/// <param name="obj">The object to deserialize.</param>
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);
}
/// <summary>
/// Writes the color to the binary stream.
/// </summary>
@@ -548,5 +568,25 @@ namespace FlaxEngine
stream.Write(value.M43);
stream.Write(value.M44);
}
/// <summary>
/// Serializes object to Json and writes it as a raw data (ver+length+bytes).
/// </summary>
/// <param name="stream">The stream.</param>
/// <remarks>Writes version number, data length and actual data bytes to the stream.</remarks>
/// <param name="obj">The object to serialize.</param>
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);
}
}
}

View File

@@ -39,9 +39,9 @@ API_ENUM() enum class ChannelMask
/// <summary>
/// Represents a parameter in the Graph.
/// </summary>
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:
/// <summary>