merging latest development

Signed-off-by: kberg-amzn <karlberg@amazon.com>
This commit is contained in:
kberg-amzn
2021-10-21 13:45:35 -07:00
151 changed files with 4227 additions and 2078 deletions
-923
View File
@@ -1,923 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ColorGradientCtrl.h"
// Qt
#include <QPainter>
#include <QToolTip>
// AzQtComponents
#include <AzQtComponents/Components/Widgets/ColorPicker.h>
#define MIN_TIME_EPSILON 0.01f
//////////////////////////////////////////////////////////////////////////
CColorGradientCtrl::CColorGradientCtrl(QWidget* parent)
: QWidget(parent)
{
m_nActiveKey = -1;
m_nHitKeyIndex = -1;
m_nKeyDrawRadius = 3;
m_bTracking = false;
m_pSpline = nullptr;
m_fMinTime = -1;
m_fMaxTime = 1;
m_fMinValue = -1;
m_fMaxValue = 1;
m_fTooltipScaleX = 1;
m_fTooltipScaleY = 1;
m_bNoTimeMarker = true;
m_bLockFirstLastKey = false;
m_bNoZoom = true;
ClearSelection();
m_bSelectedKeys.reserve(0);
m_fTimeMarker = -10;
m_grid.zoom.x = 100;
setMouseTracking(true);
}
CColorGradientCtrl::~CColorGradientCtrl()
{
}
/////////////////////////////////////////////////////////////////////////////
// QColorGradientCtrl message handlers
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
QRect rc(QPoint(0, 0), event->size());
m_rcGradient = rc;
m_rcGradient.setHeight(m_rcGradient.height() - 11);
//m_rcGradient.DeflateRect(4,4);
m_grid.rect = m_rcGradient;
if (m_bNoZoom)
{
m_grid.zoom.x = static_cast<f32>(m_grid.rect.width());
}
m_rcKeys = rc;
m_rcKeys.setTop(m_rcKeys.bottom() - 10);
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetZoom(float fZoom)
{
m_grid.zoom.x = fZoom;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetOrigin(float fOffset)
{
m_grid.origin.x = fOffset;
}
//////////////////////////////////////////////////////////////////////////
QPoint CColorGradientCtrl::KeyToPoint(int nKey)
{
if (nKey >= 0)
{
return TimeToPoint(m_pSpline->GetKeyTime(nKey));
}
return QPoint(0, 0);
}
//////////////////////////////////////////////////////////////////////////
QPoint CColorGradientCtrl::TimeToPoint(float time)
{
return QPoint(m_grid.WorldToClient(Vec2(time, 0)).x(), m_rcGradient.height() / 2);
}
//////////////////////////////////////////////////////////////////////////
AZ::Color CColorGradientCtrl::TimeToColor(float time)
{
ISplineInterpolator::ValueType val;
m_pSpline->Interpolate(time, val);
const AZ::Color col = ValueToColor(val);
return col;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val)
{
time = XOfsToTime(point.x());
ColorToValue(TimeToColor(time), val);
}
//////////////////////////////////////////////////////////////////////////
float CColorGradientCtrl::XOfsToTime(int x)
{
return m_grid.ClientToWorld(QPoint(x, 0)).x;
}
//////////////////////////////////////////////////////////////////////////
QPoint CColorGradientCtrl::XOfsToPoint(int x)
{
return TimeToPoint(XOfsToTime(x));
}
//////////////////////////////////////////////////////////////////////////
AZ::Color CColorGradientCtrl::XOfsToColor(int x)
{
return TimeToColor(XOfsToTime(x));
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::paintEvent(QPaintEvent* e)
{
QPainter painter(this);
QRect rcClient = rect();
if (m_pSpline)
{
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
}
{
if (!isEnabled())
{
painter.setBrush(palette().button());
painter.drawRect(rcClient);
return;
}
//////////////////////////////////////////////////////////////////////////
// Fill keys backgound.
//////////////////////////////////////////////////////////////////////////
QRect rcKeys = m_rcKeys.intersected(e->rect());
painter.setBrush(palette().button());
painter.drawRect(rcKeys);
//////////////////////////////////////////////////////////////////////////
//Draw Keys and Curve
if (m_pSpline)
{
DrawGradient(e, &painter);
DrawKeys(e, &painter);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::DrawGradient(QPaintEvent* e, QPainter* painter)
{
//Draw Curve
// create and select a thick, white pen
painter->setPen(QPen(QColor(128, 255, 128), 1, Qt::SolidLine));
const QRect rcClip = e->rect().intersected(m_rcGradient);
const int right = rcClip.left() + rcClip.width();
for (int x = rcClip.left(); x < right; x++)
{
const AZ::Color col = XOfsToColor(x);
QPen pen(QColor(col.GetR8(), col.GetG8(), col.GetR8(), col.GetA8()), 1, Qt::SolidLine);
painter->setPen(pen);
painter->drawLine(x, m_rcGradient.top(), x, m_rcGradient.top() + m_rcGradient.height());
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::DrawKeys(QPaintEvent* e, QPainter* painter)
{
if (!m_pSpline)
{
return;
}
// create and select a white pen
painter->setPen(QPen(QColor(0, 0, 0), 1, Qt::SolidLine));
QRect rcClip = e->rect();
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
for (int i = 0; i < m_pSpline->GetKeyCount(); i++)
{
float time = m_pSpline->GetKeyTime(i);
QPoint pt = TimeToPoint(time);
if (pt.x() < rcClip.left() - 8 || pt.x() > rcClip.left() + rcClip.width() + 8)
{
continue;
}
const AZ::Color clr = TimeToColor(time);
QBrush brush(QColor(clr.GetR8(), clr.GetG8(), clr.GetB8(), clr.GetA8()));
painter->setBrush(brush);
// Find the midpoints of the top, right, left, and bottom
// of the client area. They will be the vertices of our polygon.
QPoint pts[3];
pts[0].rx() = pt.x();
pts[0].ry() = m_rcKeys.top() + 1;
pts[1].rx() = pt.x() - 5;
pts[1].ry() = m_rcKeys.top() + 8;
pts[2].rx() = pt.x() + 5;
pts[2].ry() = m_rcKeys.top() + 8;
painter->drawPolygon(pts, 3);
if (m_bSelectedKeys[i])
{
QPen pen(QColor(200, 0, 0), 1, Qt::SolidLine);
QPen oldPen = painter->pen();
painter->setPen(pen);
painter->drawPolygon(pts, 3);
painter->setPen(oldPen);
}
}
if (!m_bNoTimeMarker)
{
QPen timePen(QColor(255, 0, 255), 1, Qt::SolidLine);
painter->setPen(timePen);
QPoint pt = TimeToPoint(m_fTimeMarker);
painter->drawLine(pt.x(), m_rcGradient.top() + 1, pt.x(), m_rcGradient.bottom() - 1);
}
}
void CColorGradientCtrl::UpdateTooltip(QPoint pos)
{
if (m_nHitKeyIndex >= 0)
{
float time = m_pSpline->GetKeyTime(m_nHitKeyIndex);
ISplineInterpolator::ValueType val;
m_pSpline->GetKeyValue(m_nHitKeyIndex, val);
AZ::Color col = TimeToColor(time);
int cont_s = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_IN_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2;
int cont_d = (m_pSpline->GetKeyFlags(m_nHitKeyIndex) >> SPLINE_KEY_TANGENT_OUT_SHIFT) & SPLINE_KEY_TANGENT_LINEAR ? 1 : 2;
QString tipText(tr("%1 : %2,%3,%4 [%5,%6]").arg(time * m_fTooltipScaleX, 0, 'f', 2).arg(col.GetR8()).arg(col.GetG8()).arg(col.GetB8()).arg(cont_s).arg(cont_d));
const QPoint globalPos = mapToGlobal(pos);
QToolTip::showText(mapToGlobal(pos), tipText, this, QRect(globalPos, QSize(1, 1)));
}
}
/////////////////////////////////////////////////////////////////////////////
//Mouse Message Handlers
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
OnLButtonDown(event);
}
else if (event->button() == Qt::RightButton)
{
OnRButtonDown(event);
}
}
void CColorGradientCtrl::OnLButtonDown([[maybe_unused]] QMouseEvent* event)
{
if (m_bTracking)
{
return;
}
if (!m_pSpline)
{
return;
}
setFocus();
switch (m_hitCode)
{
case HIT_KEY:
StartTracking();
SetActiveKey(m_nHitKeyIndex);
break;
/*
case HIT_SPLINE:
{
// Cycle the spline slope of the nearest key.
int flags = m_pSpline->GetKeyFlags(m_nHitKeyIndex);
if (m_nHitKeyDist < 0)
// Toggle left side.
flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_IN_SHIFT;
if (m_nHitKeyDist > 0)
// Toggle right side.
flags ^= SPLINE_KEY_TANGENT_LINEAR << SPLINE_KEY_TANGENT_OUT_SHIFT;
m_pSpline->SetKeyFlags(m_nHitKeyIndex, flags);
m_pSpline->Update();
SetActiveKey(-1);
SendNotifyEvent( CLRGRDN_CHANGE );
if (m_updateCallback)
m_updateCallback(this);
break;
}
*/
case HIT_NOTHING:
SetActiveKey(-1);
break;
}
update();
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnRButtonDown([[maybe_unused]] QMouseEvent* event)
{
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::mouseDoubleClickEvent(QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
if (event->button() != Qt::LeftButton)
{
return;
}
switch (m_hitCode)
{
case HIT_SPLINE:
{
int iIndex = InsertKey(event->pos());
SetActiveKey(iIndex);
EditKey(iIndex);
update();
}
break;
case HIT_KEY:
{
EditKey(m_nHitKeyIndex);
}
break;
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::mouseMoveEvent(QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
if (!m_bTracking)
{
switch (HitTest(event->pos()))
{
case HIT_SPLINE:
{
setCursor(CMFCUtils::LoadCursor(IDC_ARRWHITE));
} break;
case HIT_KEY:
{
setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCK));
} break;
default:
break;
}
}
if (m_bTracking)
{
TrackKey(event->pos());
}
if (m_bTracking || m_nHitKeyIndex >= 0)
{
UpdateTooltip(event->pos());
}
else
{
QToolTip::hideText();
}
}
void CColorGradientCtrl::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
OnLButtonUp(event);
}
else if (event->button() == Qt::RightButton)
{
OnRButtonUp(event);
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnLButtonUp(QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
if (m_bTracking)
{
StopTracking(event->pos());
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnRButtonUp([[maybe_unused]] QMouseEvent* event)
{
if (!m_pSpline)
{
return;
}
}
/////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetActiveKey(int nIndex)
{
ClearSelection();
//Activate New Key
if (nIndex >= 0)
{
m_bSelectedKeys[nIndex] = true;
}
m_nActiveKey = nIndex;
update();
SendNotifyEvent(CLRGRDN_ACTIVE_KEY_CHANGE);
}
/////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetSpline(ISplineInterpolator* pSpline, bool bRedraw)
{
if (pSpline != m_pSpline)
{
//if (pSpline && pSpline->GetNumDimensions() != 3)
//return;
m_pSpline = pSpline;
m_nActiveKey = -1;
}
ClearSelection();
if (bRedraw)
{
update();
}
}
//////////////////////////////////////////////////////////////////////////
ISplineInterpolator* CColorGradientCtrl::GetSpline()
{
return m_pSpline;
}
/////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::keyPressEvent(QKeyEvent* event)
{
bool bProcessed = false;
if (m_nActiveKey != -1 && m_pSpline)
{
switch (event->key())
{
case Qt::Key_Delete:
{
RemoveKey(m_nActiveKey);
bProcessed = true;
} break;
case Qt::Key_Up:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() -= 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
case Qt::Key_Down:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() += 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
case Qt::Key_Left:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() -= 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
case Qt::Key_Right:
{
CUndo undo("Move Spline Key");
QPoint point = KeyToPoint(m_nActiveKey);
point.rx() += 1;
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
TrackKey(point);
bProcessed = true;
} break;
default:
break; //do nothing
}
update();
}
event->setAccepted(bProcessed);
}
//////////////////////////////////////////////////////////////////////////////
CColorGradientCtrl::EHitCode CColorGradientCtrl::HitTest(QPoint point)
{
if (!m_pSpline)
{
return HIT_NOTHING;
}
ISplineInterpolator::ValueType val;
float time;
PointToTimeValue(point, time, val);
QRect rc = rect();
m_nHitKeyIndex = -1;
if (rc.contains(point))
{
m_nHitKeyDist = 0xFFFF;
m_hitCode = HIT_SPLINE;
for (int i = 0; i < m_pSpline->GetKeyCount(); i++)
{
QPoint splinePt = TimeToPoint(m_pSpline->GetKeyTime(i));
if (abs(point.x() - splinePt.x()) < abs(m_nHitKeyDist))
{
m_nHitKeyIndex = i;
m_nHitKeyDist = point.x() - splinePt.x();
}
}
if (abs(m_nHitKeyDist) < 4)
{
m_hitCode = HIT_KEY;
}
}
else
{
m_hitCode = HIT_NOTHING;
}
return m_hitCode;
}
///////////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::StartTracking()
{
m_bTracking = true;
GetIEditor()->BeginUndo();
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
setCursor(CMFCUtils::LoadCursor(IDC_ARRBLCKCROSS));
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::TrackKey(QPoint point)
{
if (point.x() < m_rcGradient.left() || point.y() > m_rcGradient.right())
{
return;
}
int nKey = m_nHitKeyIndex;
if (nKey >= 0)
{
ISplineInterpolator::ValueType val;
float time;
PointToTimeValue(point, time, val);
// Clamp to min/max time.
if (time < m_fMinTime || time > m_fMaxTime)
{
return;
}
int i;
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
{
// Switch to next key.
if ((m_pSpline->GetKeyTime(i) < time && i > nKey) ||
(m_pSpline->GetKeyTime(i) > time && i < nKey))
{
m_pSpline->SetKeyTime(nKey, time);
m_pSpline->Update();
SetActiveKey(i);
m_nHitKeyIndex = i;
return;
}
}
if (!m_bLockFirstLastKey || (nKey != 0 && nKey != m_pSpline->GetKeyCount() - 1))
{
m_pSpline->SetKeyTime(nKey, time);
m_pSpline->Update();
}
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
update();
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::StopTracking(QPoint point)
{
if (!m_bTracking)
{
return;
}
GetIEditor()->AcceptUndo("Spline Move");
if (m_nHitKeyIndex >= 0)
{
QRect rc = rect();
rc = rc.marginsAdded(QMargins(100, 100, 100, 100));
if (!rc.contains(point))
{
RemoveKey(m_nHitKeyIndex);
}
}
m_bTracking = false;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::EditKey(int nKey)
{
if (!m_pSpline)
{
return;
}
if (nKey < 0 || nKey >= m_pSpline->GetKeyCount())
{
return;
}
SetActiveKey(nKey);
ISplineInterpolator::ValueType val;
m_pSpline->GetKeyValue(nKey, val);
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
AzQtComponents::ColorPicker dlg(AzQtComponents::ColorPicker::Configuration::RGB);
dlg.setCurrentColor(ValueToColor(val));
dlg.setSelectedColor(ValueToColor(val));
connect(&dlg, &AzQtComponents::ColorPicker::currentColorChanged, this, &CColorGradientCtrl::OnKeyColorChanged);
if (dlg.exec() == QDialog::Accepted)
{
CUndo undo("Modify Gradient Color");
OnKeyColorChanged(dlg.selectedColor());
}
else
{
OnKeyColorChanged(ValueToColor(val));
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::OnKeyColorChanged(const AZ::Color& color)
{
int nKey = m_nActiveKey;
if (!m_pSpline)
{
return;
}
if (nKey < 0 || nKey >= m_pSpline->GetKeyCount())
{
return;
}
ISplineInterpolator::ValueType val;
ColorToValue(color, val);
m_pSpline->SetKeyValue(nKey, val);
update();
if (m_bLockFirstLastKey)
{
if (nKey == 0)
{
m_pSpline->SetKeyValue(m_pSpline->GetKeyCount() - 1, val);
}
else if (nKey == m_pSpline->GetKeyCount() - 1)
{
m_pSpline->SetKeyValue(0, val);
}
}
m_pSpline->Update();
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
GetIEditor()->UpdateViews(eRedrawViewports);
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::RemoveKey(int nKey)
{
if (!m_pSpline)
{
return;
}
if (m_bLockFirstLastKey)
{
if (nKey == 0 || nKey == m_pSpline->GetKeyCount() - 1)
{
return;
}
}
CUndo undo("Remove Spline Key");
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
m_nActiveKey = -1;
m_nHitKeyIndex = -1;
if (m_pSpline)
{
m_pSpline->RemoveKey(nKey);
m_pSpline->Update();
}
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
update();
}
//////////////////////////////////////////////////////////////////////////
int CColorGradientCtrl::InsertKey(QPoint point)
{
CUndo undo("Spline Insert Key");
ISplineInterpolator::ValueType val;
float time;
PointToTimeValue(point, time, val);
if (time < m_fMinTime || time > m_fMaxTime)
{
return -1;
}
int i;
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
{
// Skip if any key already have time that is very close.
if (fabs(m_pSpline->GetKeyTime(i) - time) < MIN_TIME_EPSILON)
{
return i;
}
}
SendNotifyEvent(CLRGRDN_BEFORE_CHANGE);
m_pSpline->InsertKey(time, val);
m_pSpline->Interpolate(time, val);
ClearSelection();
update();
SendNotifyEvent(CLRGRDN_CHANGE);
if (m_updateCallback)
{
m_updateCallback(this);
}
for (i = 0; i < m_pSpline->GetKeyCount(); i++)
{
// Find key with added time.
if (m_pSpline->GetKeyTime(i) == time)
{
return i;
}
}
return -1;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::ClearSelection()
{
m_nActiveKey = -1;
if (m_pSpline)
{
m_bSelectedKeys.resize(m_pSpline->GetKeyCount());
}
for (int i = 0; i < (int)m_bSelectedKeys.size(); i++)
{
m_bSelectedKeys[i] = false;
}
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SetTimeMarker(float fTime)
{
if (!m_pSpline)
{
return;
}
{
QPoint pt = TimeToPoint(m_fTimeMarker);
QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized();
rc += QMargins(1, 0, 1, 0);
update(rc);
}
{
QPoint pt = TimeToPoint(fTime);
QRect rc = QRect(pt.x(), m_rcGradient.top(), 0, m_rcGradient.bottom() - m_rcGradient.top()).normalized();
rc += QMargins(1, 0, 1, 0);
update(rc);
}
m_fTimeMarker = fTime;
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::SendNotifyEvent(int nEvent)
{
switch (nEvent)
{
case CLRGRDN_BEFORE_CHANGE:
emit beforeChange();
break;
case CLRGRDN_CHANGE:
emit change();
break;
case CLRGRDN_ACTIVE_KEY_CHANGE:
emit activeKeyChange();
break;
}
}
//////////////////////////////////////////////////////////////////////////
AZ::Color CColorGradientCtrl::ValueToColor(ISplineInterpolator::ValueType val)
{
const AZ::Color color(val[0], val[1], val[2], 1.0);
return color.LinearToGamma();
}
//////////////////////////////////////////////////////////////////////////
void CColorGradientCtrl::ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val)
{
const AZ::Color colLin = col.GammaToLinear();
val[0] = colLin.GetR();
val[1] = colLin.GetG();
val[2] = colLin.GetB();
val[3] = 0;
}
void CColorGradientCtrl::SetNoTimeMarker(bool noTimeMarker)
{
m_bNoTimeMarker = noTimeMarker;
update();
}
#include <Controls/moc_ColorGradientCtrl.cpp>
-167
View File
@@ -1,167 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QWidget>
#include <ISplines.h>
#include "Controls/WndGridHelper.h"
#endif
namespace AZ
{
class Color;
}
// Notify event sent when spline is being modified.
#define CLRGRDN_CHANGE (0x0001)
// Notify event sent just before when spline is modified.
#define CLRGRDN_BEFORE_CHANGE (0x0002)
// Notify event sent when the active key changes
#define CLRGRDN_ACTIVE_KEY_CHANGE (0x0003)
//////////////////////////////////////////////////////////////////////////
// Spline control.
//////////////////////////////////////////////////////////////////////////
class CColorGradientCtrl
: public QWidget
{
Q_OBJECT
public:
CColorGradientCtrl(QWidget* parent = nullptr);
virtual ~CColorGradientCtrl();
//Key functions
int GetActiveKey() { return m_nActiveKey; };
void SetActiveKey(int nIndex);
int InsertKey(QPoint point);
// Turns on/off zooming and scroll support.
void SetNoZoom([[maybe_unused]] bool bNoZoom) { m_bNoZoom = false; };
void SetTimeRange(float tmin, float tmax) { m_fMinTime = tmin; m_fMaxTime = tmax; }
void SetValueRange(float tmin, float tmax) { m_fMinValue = tmin; m_fMaxValue = tmax; }
void SetTooltipValueScale(float x, float y) { m_fTooltipScaleX = x; m_fTooltipScaleY = y; };
// Lock value of first and last key to be the same.
void LockFirstAndLastKeys(bool bLock) { m_bLockFirstLastKey = bLock; }
void SetSpline(ISplineInterpolator* pSpline, bool bRedraw = false);
ISplineInterpolator* GetSpline();
void SetTimeMarker(float fTime);
// Zoom in pixels per time unit.
void SetZoom(float fZoom);
void SetOrigin(float fOffset);
typedef AZStd::function<void(CColorGradientCtrl*)> UpdateCallback;
void SetUpdateCallback(const UpdateCallback& cb) { m_updateCallback = cb; };
void SetNoTimeMarker(bool noTimeMarker);
signals:
void change();
void beforeChange();
void activeKeyChange();
protected:
enum EHitCode
{
HIT_NOTHING,
HIT_KEY,
HIT_SPLINE,
};
void paintEvent(QPaintEvent* e) override;
void resizeEvent(QResizeEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
void OnLButtonDown(QMouseEvent* event);
void mouseMoveEvent(QMouseEvent* event) override;
void OnLButtonUp(QMouseEvent* event);
void OnRButtonUp(QMouseEvent* event);
void mouseDoubleClickEvent(QMouseEvent* event) override;
void OnRButtonDown(QMouseEvent* event);
void keyPressEvent(QKeyEvent* event) override;
// Drawing functions
void DrawGradient(QPaintEvent* e, QPainter* painter);
void DrawKeys(QPaintEvent* e, QPainter* painter);
void UpdateTooltip(QPoint pos);
EHitCode HitTest(QPoint point);
//Tracking support helper functions
void StartTracking();
void TrackKey(QPoint point);
void StopTracking(QPoint point);
void RemoveKey(int nKey);
void EditKey(int nKey);
QPoint KeyToPoint(int nKey);
QPoint TimeToPoint(float time);
void PointToTimeValue(QPoint point, float& time, ISplineInterpolator::ValueType& val);
float XOfsToTime(int x);
QPoint XOfsToPoint(int x);
AZ::Color XOfsToColor(int x);
AZ::Color TimeToColor(float time);
void ClearSelection();
void SendNotifyEvent(int nEvent);
AZ::Color ValueToColor(ISplineInterpolator::ValueType val);
void ColorToValue(const AZ::Color& col, ISplineInterpolator::ValueType& val);
private:
void OnKeyColorChanged(const AZ::Color& color);
private:
ISplineInterpolator* m_pSpline;
bool m_bNoZoom;
QRect m_rcClipRect;
QRect m_rcGradient;
QRect m_rcKeys;
QPoint m_hitPoint;
EHitCode m_hitCode;
int m_nHitKeyIndex;
int m_nHitKeyDist;
QPoint m_curvePoint;
float m_fTimeMarker;
int m_nActiveKey;
int m_nKeyDrawRadius;
bool m_bTracking;
float m_fMinTime, m_fMaxTime;
float m_fMinValue, m_fMaxValue;
float m_fTooltipScaleX, m_fTooltipScaleY;
bool m_bLockFirstLastKey;
bool m_bNoTimeMarker;
std::vector<int> m_bSelectedKeys;
UpdateCallback m_updateCallback;
CWndGridHelper m_grid;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_COLORGRADIENTCTRL_H
@@ -27,7 +27,6 @@ void RegisterReflectedVarHandlers()
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LocalStringPropertyHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew LightAnimationPropertyHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew UserPopupWidgetHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew ColorCurveHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew FloatCurveHandler());
EBUS_EVENT(AzToolsFramework::PropertyTypeRegistrationMessages::Bus, RegisterPropertyType, aznew MotionPropertyWidgetHandler());
}
@@ -170,30 +170,3 @@ bool FloatCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CSpline
GUI->SetSpline(reinterpret_cast<ISplineInterpolator*>(instance.m_spline));
return false;
}
QWidget* ColorCurveHandler::CreateGUI(QWidget *pParent)
{
CColorGradientCtrl* gradientCtrl = new CColorGradientCtrl(pParent);
//connect(gradientCtrl, &CColorGradientCtrl::change, [gradientCtrl]()
//{
// EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, gradientCtrl);
//});
gradientCtrl->SetTimeRange(0, 1);
gradientCtrl->setFixedHeight(36);
return gradientCtrl;
}
void ColorCurveHandler::ConsumeAttribute(CColorGradientCtrl*, AZ::u32, AzToolsFramework::PropertyAttributeReader*, const char*)
{}
void ColorCurveHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, [[maybe_unused]] CColorGradientCtrl* GUI, [[maybe_unused]] property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
{}
bool ColorCurveHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, CColorGradientCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
{
GUI->SetSpline(reinterpret_cast<ISplineInterpolator*>(instance.m_spline));
return false;
}
@@ -16,7 +16,6 @@
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include "ReflectedVar.h"
#include "Util/VariablePropertyType.h"
#include "Controls/ColorGradientCtrl.h"
#include "Controls/SplineCtrl.h"
#include <QWidget>
#endif
@@ -82,17 +81,4 @@ public:
void OnSplineChange(CSplineCtrl*);
};
class ColorCurveHandler : public QObject, public AzToolsFramework::PropertyHandler < CReflectedVarSpline, CColorGradientCtrl>
{
public:
AZ_CLASS_ALLOCATOR(ColorCurveHandler, AZ::SystemAllocator, 0);
bool IsDefaultHandler() const override { return false; }
QWidget* CreateGUI(QWidget *pParent) override;
AZ::u32 GetHandlerName(void) const override { return AZ_CRC("ePropertyColorCurve", 0xa30da4ec); }
void ConsumeAttribute(CColorGradientCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
void WriteGUIValuesIntoProperty(size_t index, CColorGradientCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
bool ReadValuesIntoGUI(size_t index, CColorGradientCtrl* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
};
#endif // CRYINCLUDE_EDITOR_UTILS_PROPERTYMISCCTRL_H
@@ -76,6 +76,7 @@ namespace AZ
else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderIsDown)
{
message = "Failed to put entries/dependencies into source control as the provider is not available.\n";
reportAsWarning = true;
}
else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_CertificateInvalid)
{
-2
View File
@@ -330,8 +330,6 @@ set(FILES
Commands/CommandManager.h
Controls/BitmapToolTip.cpp
Controls/BitmapToolTip.h
Controls/ColorGradientCtrl.cpp
Controls/ColorGradientCtrl.h
Controls/ConsoleSCB.cpp
Controls/ConsoleSCB.h
Controls/ConsoleSCB.ui
@@ -0,0 +1,254 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/Json/JsonImporter.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
namespace AZ
{
JsonSerializationResult::ResultCode JsonImportResolver::ResolveNestedImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element)
{
using namespace JsonSerializationResult;
for (auto& path : importPathStack)
{
if (importPath == path)
{
return settings.m_reporting(
AZStd::string::format("'%s' was already imported in this chain. This indicates a cyclic dependency.", importPath.c_str()),
ResultCode(Tasks::Import, Outcomes::Catastrophic), element);
}
}
importPathStack.push_back(importPath);
AZ::StackedString importElement(AZ::StackedString::Format::JsonPointer);
JsonImportSettings nestedImportSettings;
nestedImportSettings.m_importer = settings.m_importer;
nestedImportSettings.m_reporting = settings.m_reporting;
nestedImportSettings.m_resolveFlags = ImportTracking::Dependencies;
ResultCode result = ResolveImports(jsonDoc, allocator, importPathStack, nestedImportSettings, importElement);
importPathStack.pop_back();
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode JsonImportResolver::ResolveImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, StackedString& element)
{
using namespace JsonSerializationResult;
if (jsonDoc.IsObject())
{
for (auto& field : jsonDoc.GetObject())
{
if(strncmp(field.name.GetString(), JsonSerialization::ImportDirectiveIdentifier, field.name.GetStringLength()) == 0)
{
const rapidjson::Value& importDirective = field.value;
AZ::IO::FixedMaxPath importAbsPath = importPathStack.back();
importAbsPath.RemoveFilename();
AZStd::string importName;
if (importDirective.IsObject())
{
auto filenameField = importDirective.FindMember("filename");
if (filenameField != importDirective.MemberEnd())
{
importName = AZStd::string(filenameField->value.GetString(), filenameField->value.GetStringLength());
}
}
else
{
importName = AZStd::string(importDirective.GetString(), importDirective.GetStringLength());
}
importAbsPath.Append(importName);
rapidjson::Value patch;
ResultCode resolveResult = settings.m_importer->ResolveImport(&jsonDoc, patch, importDirective, importAbsPath, allocator);
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
{
return resolveResult;
}
if ((settings.m_resolveFlags & ImportTracking::Imports) == ImportTracking::Imports)
{
rapidjson::Pointer path(element.Get().data(), element.Get().size());
settings.m_importer->AddImportDirective(path, importName);
}
if ((settings.m_resolveFlags & ImportTracking::Dependencies) == ImportTracking::Dependencies)
{
settings.m_importer->AddImportedFile(importAbsPath.String());
}
ResultCode result = ResolveNestedImports(jsonDoc, allocator, importPathStack, settings, importAbsPath, element);
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
settings.m_importer->ApplyPatch(jsonDoc, patch, allocator);
}
else if (field.value.IsObject() || field.value.IsArray())
{
ScopedStackedString entryName(element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()));
ResultCode result = ResolveImports(field.value, allocator, importPathStack, settings, element);
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
}
}
}
else if(jsonDoc.IsArray())
{
int index = 0;
for (rapidjson::Value::ValueIterator elem = jsonDoc.Begin(); elem != jsonDoc.End(); ++elem, ++index)
{
if (!elem->IsObject() && !elem->IsArray())
{
continue;
}
ScopedStackedString entryName(element, index);
ResultCode result = ResolveImports(*elem, allocator, importPathStack, settings, element);
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
}
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode JsonImportResolver::RestoreImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
{
using namespace JsonSerializationResult;
if (jsonDoc.IsObject() || jsonDoc.IsArray())
{
const BaseJsonImporter::ImportDirectivesList& importDirectives = settings.m_importer->GetImportDirectives();
for (auto& import : importDirectives)
{
rapidjson::Pointer importPtr = import.first;
rapidjson::Value* currentValue = importPtr.Get(jsonDoc);
rapidjson::Value importedValue(rapidjson::kObjectType);
importedValue.AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(import.second.c_str()), allocator);
ResultCode resolveResult = JsonSerialization::ResolveImports(importedValue, allocator, settings);
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
{
return resolveResult;
}
rapidjson::Value patch;
settings.m_importer->CreatePatch(patch, importedValue, *currentValue, allocator);
settings.m_importer->RestoreImport(currentValue, patch, allocator, import.second);
}
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective,
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator)
{
using namespace JsonSerializationResult;
auto importedObject = JsonSerializationUtils::ReadJsonFile(importedFilePath.Native());
if (importedObject.IsSuccess())
{
rapidjson::Value& importedDoc = importedObject.GetValue();
if (importDirective.IsObject())
{
auto patchField = importDirective.FindMember("patch");
if (patchField != importDirective.MemberEnd())
{
patch.CopyFrom(patchField->value, allocator);
}
}
importPtr->CopyFrom(importedDoc, allocator);
}
else
{
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::RestoreImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, const AZStd::string& importFilename)
{
using namespace JsonSerializationResult;
importPtr->SetObject();
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
{
rapidjson::Value importDirective(rapidjson::kObjectType);
importDirective.AddMember(rapidjson::StringRef("filename"), rapidjson::StringRef(importFilename.c_str()), allocator);
importDirective.AddMember(rapidjson::StringRef("patch"), patch, allocator);
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), importDirective, allocator);
}
else
{
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(importFilename.c_str()), allocator);
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::ApplyPatch(rapidjson::Value& target,
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator)
{
using namespace JsonSerializationResult;
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
{
return AZ::JsonSerialization::ApplyPatch(target, allocator, patch, JsonMergeApproach::JsonMergePatch);
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::CreatePatch(rapidjson::Value& patch,
const rapidjson::Value& source, const rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator)
{
return JsonSerialization::CreatePatch(patch, allocator, source, target, JsonMergeApproach::JsonMergePatch);
}
void BaseJsonImporter::AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile)
{
m_importDirectives.emplace_back(jsonPtr, AZStd::move(importFile));
}
void BaseJsonImporter::AddImportedFile(AZStd::string importedFile)
{
m_importedFiles.insert(AZStd::move(importedFile));
}
const BaseJsonImporter::ImportDirectivesList& BaseJsonImporter::GetImportDirectives()
{
return m_importDirectives;
}
const BaseJsonImporter::ImportedFilesList& BaseJsonImporter::GetImportedFiles()
{
return m_importedFiles;
}
} // namespace AZ
@@ -0,0 +1,108 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include<AzCore/IO/Path/Path.h>
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/pointer.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
#include <AzCore/Serialization/Json/StackedString.h>
namespace AZ
{
struct JsonImportSettings;
class BaseJsonImporter
{
public:
AZ_RTTI(BaseJsonImporter, "{7B225807-7B43-430F-8B11-C794DCF5ACA5}");
using ImportDirectivesList = AZStd::vector<AZStd::pair<rapidjson::Pointer, AZStd::string>>;
using ImportedFilesList = AZStd::unordered_set<AZStd::string>;
virtual JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective,
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator);
virtual JsonSerializationResult::ResultCode RestoreImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator,
const AZStd::string& importFilename);
virtual JsonSerializationResult::ResultCode ApplyPatch(rapidjson::Value& target,
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator);
virtual JsonSerializationResult::ResultCode CreatePatch(rapidjson::Value& patch,
const rapidjson::Value& source, const rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator);
void AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile);
const ImportDirectivesList& GetImportDirectives();
void AddImportedFile(AZStd::string importedFile);
const ImportedFilesList& GetImportedFiles();
virtual ~BaseJsonImporter() = default;
protected:
ImportDirectivesList m_importDirectives;
ImportedFilesList m_importedFiles;
};
enum class ImportTracking : AZ::u8
{
None = 0,
Dependencies = (1<<0),
Imports = (1<<1),
All = (Dependencies | Imports)
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ImportTracking);
class JsonImportResolver final
{
public:
using ImportPathStack = AZStd::vector<AZ::IO::FixedMaxPath>;
JsonImportResolver() = delete;
JsonImportResolver& operator=(const JsonImportResolver& rhs) = delete;
JsonImportResolver& operator=(JsonImportResolver&& rhs) = delete;
JsonImportResolver(const JsonImportResolver& rhs) = delete;
JsonImportResolver(JsonImportResolver&& rhs) = delete;
~JsonImportResolver() = delete;
static JsonSerializationResult::ResultCode ResolveImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, StackedString& element);
static JsonSerializationResult::ResultCode RestoreImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
private:
static JsonSerializationResult::ResultCode ResolveNestedImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element);
};
struct JsonImportSettings final
{
JsonSerializationResult::JsonIssueCallback m_reporting;
BaseJsonImporter* m_importer = nullptr;
ImportTracking m_resolveFlags = ImportTracking::All;
AZ::IO::FixedMaxPath m_loadedJsonPath;
};
} // namespace AZ
@@ -706,7 +706,7 @@ namespace AZ
rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
}
if (result.GetOutcome() == Outcomes::Success)
if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
{
rapidjson::Value name;
name.CopyFrom(field.name, allocator, true);
@@ -717,6 +717,10 @@ namespace AZ
{
return result;
}
else
{
resultCode.Combine(result);
}
}
// Do an extra pass to find all the fields that are removed.
@@ -751,7 +755,7 @@ namespace AZ
rapidjson::Value value;
ResultCode result = CreateMergePatchInternal(value, allocator,
rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
if (result.GetOutcome() == Outcomes::Success)
if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
{
rapidjson::Value name;
name.CopyFrom(field.name, allocator, true);
@@ -762,11 +766,20 @@ namespace AZ
{
return result;
}
else
{
resultCode.Combine(result);
}
}
if (target.MemberCount() == 0)
{
resultCode.Combine(settings.m_reporting("Added empty object to JSON Merge Patch.",
ResultCode(Tasks::CreatePatch, Outcomes::Success), element));
}
}
patch = AZStd::move(resultValue);
resultCode.Combine(ResultCode(Tasks::CreatePatch, Outcomes::Success));
return resultCode;
}
else
@@ -10,6 +10,7 @@
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
#include <AzCore/Serialization/Json/JsonDeserializer.h>
#include <AzCore/Serialization/Json/JsonImporter.h>
#include <AzCore/Serialization/Json/JsonMerger.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonSerializer.h>
@@ -19,11 +20,6 @@
namespace AZ
{
const char* JsonSerialization::TypeIdFieldIdentifier = "$type";
const char* JsonSerialization::DefaultStringIdentifier = "{}";
const char* JsonSerialization::KeyFieldIdentifier = "Key";
const char* JsonSerialization::ValueFieldIdentifier = "Value";
namespace JsonSerializationInternal
{
template<typename T>
@@ -394,6 +390,60 @@ namespace AZ
}
}
JsonSerializationResult::ResultCode JsonSerialization::ResolveImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
{
using namespace JsonSerializationResult;
if (settings.m_importer == nullptr)
{
AZ_Assert(false, "Importer object needs to be provided");
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
}
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
{
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
};
if (!settings.m_reporting)
{
settings.m_reporting = issueReportingCallback;
}
JsonImportResolver::ImportPathStack importPathStack;
importPathStack.push_back(settings.m_loadedJsonPath);
StackedString element(StackedString::Format::JsonPointer);
return JsonImportResolver::ResolveImports(jsonDoc, allocator, importPathStack, settings, element);
}
JsonSerializationResult::ResultCode JsonSerialization::RestoreImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
{
using namespace JsonSerializationResult;
if (settings.m_importer == nullptr)
{
AZ_Assert(false, "Importer object needs to be provided");
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
}
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
{
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
};
if (!settings.m_reporting)
{
settings.m_reporting = issueReportingCallback;
}
settings.m_resolveFlags = ImportTracking::None;
return JsonImportResolver::RestoreImports(jsonDoc, allocator, settings);
}
JsonSerializationResult::ResultCode JsonSerialization::DefaultIssueReporter(AZStd::string& scratchBuffer,
AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path)
{
@@ -18,6 +18,8 @@
namespace AZ
{
class BaseJsonSerializer;
struct JsonImportSettings;
enum class JsonMergeApproach
{
@@ -51,10 +53,11 @@ namespace AZ
class JsonSerialization final
{
public:
static const char* TypeIdFieldIdentifier;
static const char* DefaultStringIdentifier;
static const char* KeyFieldIdentifier;
static const char* ValueFieldIdentifier;
static constexpr const char* TypeIdFieldIdentifier = "$type";
static constexpr const char* DefaultStringIdentifier = "{}";
static constexpr const char* KeyFieldIdentifier = "Key";
static constexpr const char* ValueFieldIdentifier = "Value";
static constexpr const char* ImportDirectiveIdentifier = "$import";
//! Merges two json values together by applying "patch" to "target" using the selected merge algorithm.
//! This version of ApplyPatch is destructive to "target". If the patch can't be correctly applied it will
@@ -284,6 +287,22 @@ namespace AZ
//! @return An enum containing less, equal or greater. In case of an error, the value for the enum will "error".
static JsonSerializerCompareResult Compare(const rapidjson::Value& lhs, const rapidjson::Value& rhs);
//! Resolves all import directives, including nested imports, in the given document. An importer object needs to be passed
//! in through the settings.
//! @param jsonDoc The json document in which to resolve imports.
//! @param allocator The allocator associated with the json document.
//! @param settings Additional settings that control the way the imports are resolved.
static JsonSerializationResult::ResultCode ResolveImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
//! Restores all import directives that were present in the json document. The same importer object that was
//! passed into ResolveImports through the settings needs to be passed here through settings as well.
//! @param jsonDoc The json document in which to restore imports.
//! @param allocator The allocator associated with the json document.
//! @param settings Additional settings that control the way the imports are restored.
static JsonSerializationResult::ResultCode RestoreImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
private:
JsonSerialization() = delete;
~JsonSerialization() = delete;
@@ -69,6 +69,9 @@ namespace AZ
case Tasks::CreatePatch:
target.append("a create patch operation ");
break;
case Tasks::Import:
target.append("an import operation");
break;
default:
target.append("an unknown operation ");
break;
@@ -32,7 +32,8 @@ namespace AZ
ReadField, //!< Task to read a field from JSON to a value.
WriteValue, //!< Task to write a value to a JSON field.
Merge, //!< Task to merge two JSON values/documents together.
CreatePatch //!< Task to create a patch to transform one value/document to another.
CreatePatch, //!< Task to create a patch to transform one value/document to another.
Import //!< Task to import a JSON document.
};
//! Describes how the task was processed.
@@ -522,6 +522,8 @@ set(FILES
Serialization/Json/IntSerializer.cpp
Serialization/Json/JsonDeserializer.h
Serialization/Json/JsonDeserializer.cpp
Serialization/Json/JsonImporter.cpp
Serialization/Json/JsonImporter.h
Serialization/Json/JsonMerger.h
Serialization/Json/JsonMerger.cpp
Serialization/Json/JsonSerialization.h
@@ -0,0 +1,413 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/Json/JsonImporter.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <Tests/Serialization/Json/JsonSerializationTests.h>
namespace JsonSerializationTests
{
class JsonImportingTests;
class JsonImporterCustom
: public AZ::BaseJsonImporter
{
public:
AZ_RTTI(JsonImporterCustom, "{003F5896-71E0-4A50-A14F-08C319B06AD0}");
AZ::JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective,
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) override;
JsonImporterCustom(JsonImportingTests* tests)
{
testClass = tests;
}
private:
JsonImportingTests* testClass;
};
class JsonImportingTests
: public BaseJsonSerializerFixture
{
public:
void SetUp() override
{
BaseJsonSerializerFixture::SetUp();
}
void TearDown() override
{
BaseJsonSerializerFixture::TearDown();
}
void GetTestDocument(const AZStd::string& docName, rapidjson::Document& out)
{
const char *objectJson = R"({
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "value_3"
})";
const char *arrayJson = R"([
{ "element_1" : "value_1" },
{ "element_2" : "value_2" },
{ "element_3" : "value_3" }
])";
const char *nestedImportJson = R"({
"desc" : "Nested Import",
"obj" : {"$import" : "object.json"}
})";
const char *nestedImportCycle1Json = R"({
"desc" : "Nested Import Cycle 1",
"obj" : {"$import" : "nested_import_c2.json"}
})";
const char *nestedImportCycle2Json = R"({
"desc" : "Nested Import Cycle 2",
"obj" : {"$import" : "nested_import_c1.json"}
})";
if (docName.compare("object.json") == 0)
{
out.Parse(objectJson);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("array.json") == 0)
{
out.Parse(arrayJson);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("nested_import.json") == 0)
{
out.Parse(nestedImportJson);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("nested_import_c1.json") == 0)
{
out.Parse(nestedImportCycle1Json);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("nested_import_c2.json") == 0)
{
out.Parse(nestedImportCycle2Json);
ASSERT_FALSE(out.HasParseError());
}
}
protected:
void TestImportLoadStore(const char* input, const char* expectedImportedValue)
{
m_jsonDocument->Parse(input);
ASSERT_FALSE(m_jsonDocument->HasParseError());
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
rapidjson::Document expectedOutcome;
expectedOutcome.Parse(expectedImportedValue);
ASSERT_FALSE(expectedOutcome.HasParseError());
TestResolveImports(importerObj);
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutcome.GetObject());
rapidjson::Document originalInput;
originalInput.Parse(input);
ASSERT_FALSE(originalInput.HasParseError());
TestRestoreImports(importerObj);
Expect_DocStrEq(m_jsonDocument->GetObject(), originalInput.GetObject());
m_jsonDocument->SetObject();
delete importerObj;
}
void TestImportCycle(const char* input)
{
m_jsonDocument->Parse(input);
ASSERT_FALSE(m_jsonDocument->HasParseError());
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
AZ::JsonSerializationResult::ResultCode result = TestResolveImports(importerObj);
EXPECT_EQ(result.GetOutcome(), AZ::JsonSerializationResult::Outcomes::Catastrophic);
m_jsonDocument->SetObject();
delete importerObj;
}
void TestInsertNewImport(const char* input, const char* expectedRestoredValue)
{
m_jsonDocument->Parse(input);
ASSERT_FALSE(m_jsonDocument->HasParseError());
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
TestResolveImports(importerObj);
importerObj->AddImportDirective(rapidjson::Pointer("/object_2"), "object.json");
rapidjson::Document expectedOutput;
expectedOutput.Parse(expectedRestoredValue);
ASSERT_FALSE(expectedOutput.HasParseError());
TestRestoreImports(importerObj);
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutput.GetObject());
m_jsonDocument->SetObject();
delete importerObj;
}
AZ::JsonSerializationResult::ResultCode TestResolveImports(JsonImporterCustom* importerObj)
{
AZ::JsonImportSettings settings;
settings.m_importer = importerObj;
return AZ::JsonSerialization::ResolveImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
}
AZ::JsonSerializationResult::ResultCode TestRestoreImports(JsonImporterCustom* importerObj)
{
AZ::JsonImportSettings settings;
settings.m_importer = importerObj;
return AZ::JsonSerialization::RestoreImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
}
};
AZ::JsonSerializationResult::ResultCode JsonImporterCustom::ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective, const AZ::IO::FixedMaxPath& importedFilePath,
rapidjson::Document::AllocatorType& allocator)
{
AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::Import);
rapidjson::Document importedDoc;
testClass->GetTestDocument(importedFilePath.String(), importedDoc);
if (importDirective.IsObject())
{
auto patchField = importDirective.FindMember("patch");
if (patchField != importDirective.MemberEnd())
{
patch.CopyFrom(patchField->value, allocator);
}
}
importPtr->CopyFrom(importedDoc, allocator);
return resultCode;
}
// Test Cases
TEST_F(JsonImportingTests, ImportSimpleObjectTest)
{
const char* inputFile = R"(
{
"name" : "simple_object_import",
"object": {"$import" : "object.json"}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_object_import",
"object": {
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "value_3"
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, ImportSimpleObjectPatchTest)
{
const char* inputFile = R"(
{
"name" : "simple_object_import",
"object": {
"$import" : {
"filename" : "object.json",
"patch" : { "field_2" : "patched_value" }
}
}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_object_import",
"object": {
"field_1" : "value_1",
"field_2" : "patched_value",
"field_3" : "value_3"
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, ImportSimpleArrayTest)
{
const char* inputFile = R"(
{
"name" : "simple_array_import",
"object": {"$import" : "array.json"}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_array_import",
"object": [
{ "element_1" : "value_1" },
{ "element_2" : "value_2" },
{ "element_3" : "value_3" }
]
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, ImportSimpleArrayPatchTest)
{
const char* inputFile = R"(
{
"name" : "simple_array_import",
"object": {
"$import" : {
"filename" : "array.json",
"patch" : [ { "element_1" : "patched_value" } ]
}
}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_array_import",
"object": [
{ "element_1" : "patched_value" }
]
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, NestedImportTest)
{
const char* inputFile = R"(
{
"name" : "nested_import",
"object": {"$import" : "nested_import.json"}
}
)";
const char* expectedOutput = R"(
{
"name" : "nested_import",
"object": {
"desc" : "Nested Import",
"obj" : {
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "value_3"
}
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, NestedImportPatchTest)
{
const char* inputFile = R"(
{
"name" : "nested_import",
"object": {
"$import" : {
"filename" : "nested_import.json",
"patch" : { "obj" : { "field_3" : "patched_value" } }
}
}
}
)";
const char* expectedOutput = R"(
{
"name" : "nested_import",
"object": {
"desc" : "Nested Import",
"obj" : {
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "patched_value"
}
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, NestedImportCycleTest)
{
const char* inputFile = R"(
{
"name" : "nested_import_cycle",
"object": {"$import" : "nested_import_c1.json"}
}
)";
TestImportCycle(inputFile);
}
TEST_F(JsonImportingTests, InsertNewImportTest)
{
const char* inputFile = R"(
{
"name" : "simple_object_import",
"object_1": {"$import" : "object.json"},
"object_2": {
"field_1" : "other_value",
"field_2" : "value_2",
"field_3" : "value_3"
}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_object_import",
"object_1": {"$import" : "object.json"},
"object_2": {
"$import" : {
"filename" : "object.json",
"patch" : { "field_1" : "other_value" }
}
}
}
)";
TestInsertNewImport(inputFile, expectedOutput);
}
}
@@ -121,6 +121,7 @@ set(FILES
Serialization/Json/TestCases_Classes.cpp
Serialization/Json/TestCases_Compare.cpp
Serialization/Json/TestCases_Enum.cpp
Serialization/Json/TestCases_Importing.cpp
Serialization/Json/TestCases_Patching.cpp
Serialization/Json/TestCases_Pointers.h
Serialization/Json/TestCases_Pointers.cpp
@@ -43,7 +43,7 @@ namespace AzFramework
class IMatchmakingAsyncRequests
{
public:
AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
AZ_RTTI(IMatchmakingAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
IMatchmakingAsyncRequests() = default;
virtual ~IMatchmakingAsyncRequests() = default;
@@ -60,4 +60,31 @@ namespace AzFramework
// @param stopMatchmakingRequest The request of StopMatchmaking operation
virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
};
//! MatchmakingAsyncRequestNotifications
//! The notifications correspond to matchmaking async requests
class MatchmakingAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
virtual void OnAcceptMatchAsyncComplete() = 0;
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
virtual void OnStopMatchmakingAsyncComplete() = 0;
};
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
} // namespace AzFramework
@@ -13,36 +13,10 @@
namespace AzFramework
{
//! MatchmakingAsyncRequestNotifications
//! The notifications correspond to matchmaking async requests
class MatchmakingAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
virtual void OnAcceptMatchAsyncComplete() = 0;
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
virtual void OnStopMatchmakingAsyncComplete() = 0;
};
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
//! MatchmakingNotifications
//! The matchmaking notifications to listen for performing required operations
class MatchAcceptanceNotifications
//! based on matchmaking ticket event
class MatchmakingNotifications
: public AZ::EBusTraits
{
public:
@@ -55,8 +29,18 @@ namespace AzFramework
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE
// OnMatchAcceptance is fired when match is found and pending on acceptance
// Use this notification to accept found match
virtual void OnMatchAcceptance() = 0;
// OnMatchComplete is fired when match is complete
virtual void OnMatchComplete() = 0;
// OnMatchError is fired when match is processed with error
virtual void OnMatchError() = 0;
// OnMatchFailure is fired when match is failed to complete
virtual void OnMatchFailure() = 0;
};
using MatchAcceptanceNotificationBus = AZ::EBus<MatchAcceptanceNotifications>;
using MatchmakingNotificationBus = AZ::EBus<MatchmakingNotifications>;
} // namespace AzFramework
@@ -22,7 +22,7 @@ namespace AzFramework
AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType));
if (!pWatcher)
{
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
return false;
}
else
@@ -31,7 +31,7 @@ namespace AzFramework
ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
if (!pCommunicator || !pCommunicator->IsValid())
{
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
return false;
}
else
@@ -308,7 +308,7 @@ namespace AzFramework
event.data.data32[2] = 0;
event.data.data32[3] = 1;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
[[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
@@ -333,7 +333,7 @@ namespace AzFramework
event.data.data32[2] = 0;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
[[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(
@@ -7,17 +7,35 @@
*/
#include <AzFramework/Application/Application.h>
#include <sys/resource.h>
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/XcbApplication.h>
#endif
constexpr rlim_t g_minimumOpenFileHandles = 65536L;
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
{
////////////////////////////////////////////////////////////////////////////////////////////////
Application::Implementation* Application::Implementation::Create()
{
// The default open file limit for processes may not be enough for O3DE applications.
// We will need to increase to the recommended value if the current open file limit
// is not sufficient.
rlimit currentLimit;
int get_limit_result = getrlimit(RLIMIT_NOFILE, &currentLimit);
AZ_Warning("Application", get_limit_result == 0, "Unable to read current ulimit open file limits");
if ((get_limit_result == 0) && (currentLimit.rlim_cur < g_minimumOpenFileHandles || currentLimit.rlim_max < g_minimumOpenFileHandles))
{
rlimit newLimit;
newLimit.rlim_cur = g_minimumOpenFileHandles; // Soft Limit
newLimit.rlim_max = g_minimumOpenFileHandles; // Hard Limit
[[maybe_unused]] int set_limit_result = setrlimit(RLIMIT_NOFILE, &newLimit);
AZ_Assert(set_limit_result == 0, "Unable to update open file limits");
}
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
return aznew XcbApplication();
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
@@ -91,31 +91,42 @@ namespace AzFramework
return processId == 0;
}
/*! Executes a command in the child process after the fork operation has been executed.
* This function will never return. If the execvp command fails this will call _exit with
* the errno value as the return value since continuing execution after a execvp command
* is invalid (it will be running the parent's code and in its address space and will
* cause many issues).
/*! Executes a command in the child process after the fork operation
* has been executed. This function will never return. If the execvpe
* command fails this will call _exit since continuing execution after
* a execvpe command is invalid (it will be running the parent's code
* and in its address space and will cause many issues).
*
* This function runs after a `fork()` call. `fork()` creates a copy of
* the current process, including the current state of the process's
* memory, at the time the call is made. However, it only creates a
* copy of the one thread that called `fork()`. This means that if any
* mutexes are locked by other threads at the time of `fork()`, those
* mutexes will remain locked in the child process, with no way to
* unlock them. So this function needs to ensure that it does as little
* work as possible.
*
* \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer.
* \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
* \param environmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
* \param processLaunchInfo - struct containing information about luanching the command
* \param startupInfo - struct containing information needed to startup the command
* \param errorPipe - a pipe file descriptor used to communicate a failed execvpe call's error code to the parent process
*/
void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo)
[[noreturn]] static void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo, const AZStd::array<int, 2>& errorPipe)
{
close(errorPipe[0]);
if (!processLaunchInfo.m_workingDirectory.empty())
{
int res = chdir(processLaunchInfo.m_workingDirectory.c_str());
if (res != 0)
{
std::cerr << strerror(errno) << std::endl;
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str());
write(errorPipe[1], &errno, sizeof(int));
// We *have* to _exit as we are the child process and simply
// returning at this point would mean we would start running
// the code from our parent process and that will just wreck
// havoc.
_exit(errno);
_exit(0);
}
}
@@ -135,15 +146,17 @@ namespace AzFramework
startupInfo.SetupHandlesForChildProcess();
execve(commandAndArgs[0], commandAndArgs, environmentVariables);
execvpe(commandAndArgs[0], commandAndArgs, environmentVariables);
const int errval = errno;
// If we get here then execve failed to run the requested program and
// If we get here then execvpe failed to run the requested program and
// we have an error. In this case we need to exit the child process
// to stop it from continuing to run as a clone of the parent
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno));
std::cerr << strerror(errno) << std::endl;
// to stop it from continuing to run as a clone of the parent.
// Communicate the error code back to the parent via a pipe for the
// parent to read.
write(errorPipe[1], &errval, sizeof(errval));
_exit(errno);
_exit(0);
}
}
@@ -212,9 +225,8 @@ namespace AzFramework
AZStd::string outputString;
bool inQuotes = false;
for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos)
for (const char currentChar : processLaunchInfo.m_commandlineParameters)
{
char currentChar = processLaunchInfo.m_commandlineParameters[pos];
if (currentChar == '"')
{
inQuotes = !inQuotes;
@@ -231,7 +243,7 @@ namespace AzFramework
outputString.push_back(currentChar);
}
}
if (!outputString.empty())
{
commandTokens.push_back(outputString);
@@ -249,10 +261,10 @@ namespace AzFramework
return false;
}
// Because of the way execve is defined we need to copy the strings from
// Because of the way execvpe is defined we need to copy the strings from
// AZ::string (using c_str() returns a const char*) into a non-const char*
// Need to add one more as exec requires the array's last element to be a null pointer
// Need to add one more as execvpe requires the array's last element to be a null pointer
char** commandAndArgs = new char*[commandTokens.size() + 1];
for (int i = 0; i < commandTokens.size(); ++i)
{
@@ -275,7 +287,7 @@ namespace AzFramework
azstrcat(environmentVariable.get(), envVarString.size() + 1, envVarString.c_str());
environmentVariablesVector.emplace_back(environmentVariable.get());
}
// Adding one more as exec expects the array to have a nullptr as the last element
// Adding one more as execvpe expects the array to have a nullptr as the last element
environmentVariablesVector.emplace_back(nullptr);
environmentVariables = environmentVariablesVector.data();
}
@@ -288,15 +300,50 @@ namespace AzFramework
AZ_Assert(environmentVariables, "Environment variables for current process not available\n");
}
// Set up a pipe to communicate the error code from the subprocess's execvpe call
AZStd::array<int, 2> childErrorPipeFds{};
pipe(childErrorPipeFds.data());
// This configures the write end of the pipe to close on calls to `exec`
fcntl(childErrorPipeFds[1], F_SETFD, fcntl(childErrorPipeFds[1], F_GETFD) | FD_CLOEXEC);
pid_t child_pid = fork();
if (IsIdChildProcess(child_pid))
{
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo);
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo, childErrorPipeFds);
}
processData.m_childProcessId = child_pid;
// Close these handles as they are only to be used by the child process
processData.m_startupInfo.CloseAllHandles();
close(childErrorPipeFds[1]);
{
int errorCodeFromChild = 0;
int count = 0;
// Read from the error pipe.
// * If the child's call to execvpe succeeded, then the pipe will
// be closed due to setting FD_CLOEXEC on the write end of the
// pipe. `read()` will return 0.
// * If the child's call to execvpe failed, the child will have
// written the error code to the pipe. `read()` will return >0, and
// the data to be read is the error code from execvpe.
while ((count = read(childErrorPipeFds[0], &errorCodeFromChild, sizeof(errorCodeFromChild))) == -1)
{
if (errno != EAGAIN && errno != EINTR)
{
break;
}
}
if (count)
{
AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild));
processData.m_childProcessIsDone = true;
child_pid = -1;
}
}
close(childErrorPipeFds[0]);
processData.m_childProcessId = child_pid;
for (int i = 0; i < commandTokens.size(); i++)
{
@@ -7,6 +7,7 @@
*/
#include <dlfcn.h>
#include <iostream>
#include <AzCore/IO/Path/Path.h>
#include <AzTest/Platform.h>
#include <sys/types.h>
@@ -20,11 +21,16 @@ public:
explicit ModuleHandle(const std::string& lib)
: m_libHandle(nullptr)
{
std::string libext = lib;
if (!AZ::Test::EndsWith(libext, ".dylib"))
AZ::IO::FixedMaxPath libext = AZStd::string_view{ lib.c_str(), lib.size() };
if (!libext.Stem().Native().starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX))
{
libext += ".dylib";
libext = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + libext.Native();
}
if (libext.Extension() != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
{
libext.Native() += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
}
m_libHandle = dlopen(libext.c_str(), RTLD_NOW);
const char* error = dlerror();
if (error)
@@ -53,7 +53,7 @@ namespace AzToolsFramework
AZ_Assert(m_loaderInterface != nullptr,
"Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab"));
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "newLevel.prefab"));
m_sliceOwnershipService.BusConnect(m_entityContextId);
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
m_editorSliceOwnershipService.BusConnect();
@@ -9,6 +9,8 @@
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
@@ -38,6 +40,13 @@ namespace AzToolsFramework
AZ::Edit::SliceFlags::DontGatherReference);
}
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->ConstantProperty(
"EditorPrefabComponentTypeId", BehaviorConstant(AZ::Uuid(EditorPrefabComponent::EditorPrefabComponentTypeId)))
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation);
}
}
void EditorPrefabComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
@@ -16,7 +16,9 @@ namespace AzToolsFramework
class EditorPrefabComponent : public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_COMPONENT(EditorPrefabComponent, "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}", EditorComponentBase);
static constexpr const char* const EditorPrefabComponentTypeId = "{756E5F9C-3E08-4F8D-855C-A5AEEFB6FCDD}";
AZ_COMPONENT(EditorPrefabComponent, EditorPrefabComponentTypeId, EditorComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services);
@@ -28,7 +28,9 @@ namespace AzToolsFramework::Prefab
"Instance Entity Mapper Interface could not be found. "
"Check that it is being correctly initialized.");
EditorEntityInfoNotificationBus::Handler::BusConnect();
EditorEntityContextNotificationBus::Handler::BusConnect();
PrefabPublicNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabFocusInterface>::Register(this);
AZ::Interface<PrefabFocusPublicInterface>::Register(this);
}
@@ -37,10 +39,12 @@ namespace AzToolsFramework::Prefab
{
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
AZ::Interface<PrefabFocusInterface>::Unregister(this);
PrefabPublicNotificationBus::Handler::BusDisconnect();
EditorEntityContextNotificationBus::Handler::BusDisconnect();
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
}
void PrefabFocusHandler::Initialize()
void PrefabFocusHandler::InitializeEditorInterfaces()
{
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
AZ_Assert(
@@ -55,13 +59,6 @@ namespace AzToolsFramework::Prefab
"Prefab - PrefabFocusHandler - "
"Focus Mode Interface could not be found. "
"Check that it is being correctly initialized.");
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(
m_instanceEntityMapperInterface,
"Prefab - PrefabFocusHandler - "
"Instance Entity Mapper Interface could not be found. "
"Check that it is being correctly initialized.");
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
@@ -90,12 +87,12 @@ namespace AzToolsFramework::Prefab
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{
if (index < 0 || index >= m_instanceFocusVector.size())
if (index < 0 || index >= m_instanceFocusHierarchy.size())
{
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
}
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index];
InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index];
FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
@@ -134,42 +131,38 @@ namespace AzToolsFramework::Prefab
return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on."));
}
if (!m_isInitialized)
// Close all container entities in the old path.
CloseInstanceContainers(m_instanceFocusHierarchy);
m_focusedInstance = focusedInstance;
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
AZ::EntityId containerEntityId;
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
{
Initialize();
containerEntityId = focusedInstance->get().GetContainerEntityId();
}
if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get())
else
{
// Close all container entities in the old path
CloseInstanceContainers(m_instanceFocusVector);
m_focusedInstance = focusedInstance;
m_focusedTemplateId = focusedInstance->get().GetTemplateId();
AZ::EntityId containerEntityId;
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
{
containerEntityId = focusedInstance->get().GetContainerEntityId();
}
else
{
containerEntityId = AZ::EntityId();
}
containerEntityId = AZ::EntityId();
}
// Focus on the descendants of the container entity
// Focus on the descendants of the container entity in the Editor, if the interface is initialized.
if (m_focusModeInterface)
{
m_focusModeInterface->SetFocusRoot(containerEntityId);
// Refresh path variables
RefreshInstanceFocusList();
// Open all container entities in the new path
OpenInstanceContainers(m_instanceFocusVector);
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
// Refresh path variables.
RefreshInstanceFocusList();
RefreshInstanceFocusPath();
// Open all container entities in the new path.
OpenInstanceContainers(m_instanceFocusHierarchy);
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
return AZ::Success();
}
@@ -220,49 +213,80 @@ namespace AzToolsFramework::Prefab
const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{
return aznumeric_cast<int>(m_instanceFocusVector.size());
return aznumeric_cast<int>(m_instanceFocusHierarchy.size());
}
void PrefabFocusHandler::OnEntityStreamLoadSuccess()
void PrefabFocusHandler::OnContextReset()
{
if (!m_isInitialized)
{
Initialize();
}
// Clear the old focus vector
m_instanceFocusVector.clear();
m_instanceFocusHierarchy.clear();
// Focus on the root prefab (AZ::EntityId() will default to it)
FocusOnPrefabInstanceOwningEntityId(AZ::EntityId());
}
void PrefabFocusHandler::OnEntityInfoUpdatedName(AZ::EntityId entityId, [[maybe_unused]]const AZStd::string& name)
{
// Determine if the entityId is the container for any of the instances in the vector
auto result = AZStd::find_if(
m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(),
[entityId](const InstanceOptionalReference& instance)
{
return (instance->get().GetContainerEntityId() == entityId);
}
);
if (result != m_instanceFocusHierarchy.end())
{
// Refresh the path and notify changes.
RefreshInstanceFocusPath();
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
}
void PrefabFocusHandler::OnPrefabInstancePropagationEnd()
{
// Refresh the path and notify changes in case propagation updated any container names.
RefreshInstanceFocusPath();
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
void PrefabFocusHandler::RefreshInstanceFocusList()
{
m_instanceFocusVector.clear();
m_instanceFocusPath.clear();
m_instanceFocusHierarchy.clear();
AZStd::list<InstanceOptionalReference> instanceFocusList;
// Use a support list to easily push front while traversing the prefab hierarchy
InstanceOptionalReference currentInstance = m_focusedInstance;
while (currentInstance.has_value())
{
instanceFocusList.push_front(currentInstance);
m_instanceFocusHierarchy.emplace_back(currentInstance);
currentInstance = currentInstance->get().GetParentInstance();
}
// Populate internals using the support list
for (auto& instance : instanceFocusList)
// Invert the vector, since we need the top instance to be at index 0
AZStd::reverse(m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end());
}
void PrefabFocusHandler::RefreshInstanceFocusPath()
{
m_instanceFocusPath.clear();
for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy)
{
m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName());
m_instanceFocusVector.emplace_back(instance);
}
}
void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
{
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
{
return;
}
for (const InstanceOptionalReference& instance : instances)
{
if (instance.has_value())
@@ -274,6 +298,12 @@ namespace AzToolsFramework::Prefab
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
{
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
{
return;
}
for (const InstanceOptionalReference& instance : instances)
{
if (instance.has_value())
@@ -11,9 +11,11 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework
@@ -30,7 +32,9 @@ namespace AzToolsFramework::Prefab
class PrefabFocusHandler final
: private PrefabFocusInterface
, private PrefabFocusPublicInterface
, private PrefabPublicNotificationBus::Handler
, private EditorEntityContextNotificationBus::Handler
, private EditorEntityInfoNotificationBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0);
@@ -38,9 +42,8 @@ namespace AzToolsFramework::Prefab
PrefabFocusHandler();
~PrefabFocusHandler();
void Initialize();
// PrefabFocusInterface overrides ...
void InitializeEditorInterfaces() override;
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
@@ -54,25 +57,34 @@ namespace AzToolsFramework::Prefab
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
// EditorEntityContextNotificationBus overrides ...
void OnEntityStreamLoadSuccess() override;
void OnContextReset() override;
// EditorEntityInfoNotificationBus overrides ...
void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override;
// PrefabPublicNotifications overrides ...
void OnPrefabInstancePropagationEnd();
private:
PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance);
void RefreshInstanceFocusList();
void RefreshInstanceFocusPath();
void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
//! The instance the editor is currently focusing on.
InstanceOptionalReference m_focusedInstance;
//! The templateId of the focused instance.
TemplateId m_focusedTemplateId;
AZStd::vector<InstanceOptionalReference> m_instanceFocusVector;
//! The list of instances going from the root (index 0) to the focused instance.
AZStd::vector<InstanceOptionalReference> m_instanceFocusHierarchy;
//! A path containing the names of the containers in the instance focus hierarchy, separated with a /.
AZ::IO::Path m_instanceFocusPath;
ContainerEntityInterface* m_containerEntityInterface = nullptr;
FocusModeInterface* m_focusModeInterface = nullptr;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
bool m_isInitialized = false;
};
} // namespace AzToolsFramework::Prefab
@@ -26,6 +26,11 @@ namespace AzToolsFramework::Prefab
public:
AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}");
//! Initializes the editor interfaces for Prefab Focus mode.
//! If this is not called on initialization, the Prefab Focus Mode functions will still work
//! but won't trigger the Editor APIs to visualize focus mode on the UI.
virtual void InitializeEditorInterfaces() = 0;
//! Set the focused prefab instance to the owning instance of the entityId provided.
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0;
@@ -974,7 +974,7 @@ namespace AzToolsFramework
return DeleteFromInstance(entityIds, true);
}
PrefabOperationResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
DuplicatePrefabResult PrefabPublicHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
{
if (entityIds.empty())
{
@@ -1021,6 +1021,7 @@ namespace AzToolsFramework
ScopedUndoBatch undoBatch("Duplicate Entities");
EntityIdList duplicatedEntityAndInstanceIds;
{
AZ_PROFILE_SCOPE(AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
@@ -1033,7 +1034,7 @@ namespace AzToolsFramework
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
{
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
return AZ::Failure(retrieveEntitiesAndInstancesOutcome.TakeError());
}
// Take a snapshot of the instance DOM before we manipulate it
@@ -1044,8 +1045,6 @@ namespace AzToolsFramework
PrefabDom instanceDomAfter;
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
EntityIdList duplicatedEntityAndInstanceIds;
// Duplicate any nested entities and instances as requested
AZStd::unordered_map<InstanceAlias, Instance*> newInstanceAliasToOldInstanceMap;
AZStd::unordered_map<EntityAlias, EntityAlias> duplicateEntityAliasMap;
@@ -1114,7 +1113,7 @@ namespace AzToolsFramework
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, duplicatedEntityAndInstanceIds);
}
return AZ::Success();
return AZ::Success(AZStd::move(duplicatedEntityAndInstanceIds));
}
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
@@ -63,7 +63,7 @@ namespace AzToolsFramework
PrefabOperationResult DeleteEntitiesInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
@@ -26,6 +26,7 @@ namespace AzToolsFramework
{
typedef AZ::Outcome<AZ::EntityId, AZStd::string> CreatePrefabResult;
typedef AZ::Outcome<AZ::EntityId, AZStd::string> InstantiatePrefabResult;
typedef AZ::Outcome<EntityIdList, AZStd::string> DuplicatePrefabResult;
typedef AZ::Outcome<void, AZStd::string> PrefabOperationResult;
typedef AZ::Outcome<bool, AZStd::string> PrefabRequestResult;
typedef AZ::Outcome<AZ::EntityId, AZStd::string> PrefabEntityResult;
@@ -160,14 +161,15 @@ namespace AzToolsFramework
/**
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
* @param entities The entities to duplicate.
* @return An outcome object; on failure, it comes with an error message detailing the cause of the error.
* @return An outcome object with a list of ids of target entities' duplicates if duplication succeeded;
* on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
/**
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
* instance and the parent, removing links between this instance and it's nested instances, adding entities directly
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
* owned by this instance under the parent instance.
* Bails if the entity is not a container entity or belongs to the level prefab instance.
* @param containerEntityId The container entity id of the instance to detach.
@@ -25,6 +25,7 @@ namespace AzToolsFramework
{
using CreatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
using InstantiatePrefabResult = AZ::Outcome<AZ::EntityId, AZStd::string>;
using DuplicatePrefabResult = AZ::Outcome<EntityIdList, AZStd::string>;
using PrefabOperationResult = AZ::Outcome<void, AZStd::string>;
/**
@@ -69,6 +70,29 @@ namespace AzToolsFramework
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) = 0;
/**
* If the entity id is a container entity id, detaches the prefab instance corresponding to it. This includes converting
* the container entity into a regular entity and putting it under the parent prefab, removing the link between this
* instance and the parent, removing links between this instance and its nested instances, and adding entities directly
* owned by this instance under the parent instance.
* Bails if the entity is not a container entity or belongs to the level prefab instance.
* Return an outcome object; on failure, it comes with an error message detailing the cause of the error.
*/
virtual PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) = 0;
/**
* Duplicates all entities in the owning instance. Bails if the entities don't all belong to the same instance.
* Return an outcome object with a list of ids of given entities' duplicates if duplication succeeded;
* on failure, it comes with an error message detailing the cause of the error.
*/
virtual DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) = 0;
/**
* Get the file path to the prefab file for the prefab instance owning the entity provided.
* Returns the path to the prefab, or an empty path if the entity is owned by the level.
*/
virtual AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const = 0;
};
using PrefabPublicRequestBus = AZ::EBus<PrefabPublicRequests>;
@@ -28,6 +28,9 @@ namespace AzToolsFramework
->Event("CreatePrefabInMemory", &PrefabPublicRequests::CreatePrefabInMemory)
->Event("InstantiatePrefab", &PrefabPublicRequests::InstantiatePrefab)
->Event("DeleteEntitiesAndAllDescendantsInInstance", &PrefabPublicRequests::DeleteEntitiesAndAllDescendantsInInstance)
->Event("DetachPrefab", &PrefabPublicRequests::DetachPrefab)
->Event("DuplicateEntitiesInInstance", &PrefabPublicRequests::DuplicateEntitiesInInstance)
->Event("GetOwningInstancePrefabPath", &PrefabPublicRequests::GetOwningInstancePrefabPath)
;
}
}
@@ -62,5 +65,19 @@ namespace AzToolsFramework
return m_prefabPublicInterface->DeleteEntitiesAndAllDescendantsInInstance(entityIds);
}
PrefabOperationResult PrefabPublicRequestHandler::DetachPrefab(const AZ::EntityId& containerEntityId)
{
return m_prefabPublicInterface->DetachPrefab(containerEntityId);
}
DuplicatePrefabResult PrefabPublicRequestHandler::DuplicateEntitiesInInstance(const EntityIdList& entityIds)
{
return m_prefabPublicInterface->DuplicateEntitiesInInstance(entityIds);
}
AZStd::string PrefabPublicRequestHandler::GetOwningInstancePrefabPath(AZ::EntityId entityId) const
{
return m_prefabPublicInterface->GetOwningInstancePrefabPath(entityId).Native();
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -34,6 +34,9 @@ namespace AzToolsFramework
CreatePrefabResult CreatePrefabInMemory(const EntityIdList& entityIds, AZStd::string_view filePath) override;
InstantiatePrefabResult InstantiatePrefab(AZStd::string_view filePath, AZ::EntityId parent, const AZ::Vector3& position) override;
PrefabOperationResult DeleteEntitiesAndAllDescendantsInInstance(const EntityIdList& entityIds) override;
PrefabOperationResult DetachPrefab(const AZ::EntityId& containerEntityId) override;
DuplicatePrefabResult DuplicateEntitiesInInstance(const EntityIdList& entityIds) override;
AZStd::string GetOwningInstancePrefabPath(AZ::EntityId entityId) const override;
private:
PrefabPublicInterface* m_prefabPublicInterface = nullptr;
@@ -23,7 +23,7 @@ namespace AzToolsFramework
}
//PrefabInstanceUndo
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation)
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation)
: PrefabUndoBase(undoOperationName)
{
m_useImmediatePropagation = useImmediatePropagation;
@@ -45,7 +45,7 @@ namespace AzToolsFramework
: public PrefabUndoBase
{
public:
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation = true);
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, bool useImmediatePropagation = true);
void Capture(
const PrefabDom& initialState,
@@ -20,6 +20,8 @@
#include <AzFramework/Process/ProcessWatcher.h>
#include <AzToolsFramework/SourceControl/PerforceConnection.h>
#include <QProcess>
namespace AzToolsFramework
{
namespace
@@ -75,9 +77,17 @@ namespace AzToolsFramework
m_resolveKey = true;
m_testTrust = false;
// set up signals before we start thread.
m_shutdownThreadSignal = false;
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
// Check to see if the 'p4' command is available at the command line
int p4VersionExitCode = QProcess::execute("p4", QStringList{ "-V" });
m_p4ApplicationDetected = (p4VersionExitCode == 0);
if (m_p4ApplicationDetected)
{
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
}
SourceControlConnectionRequestBus::Handler::BusConnect();
SourceControlCommandBus::Handler::BusConnect();
@@ -88,10 +98,13 @@ namespace AzToolsFramework
SourceControlCommandBus::Handler::BusDisconnect();
SourceControlConnectionRequestBus::Handler::BusDisconnect();
m_shutdownThreadSignal = true; // tell the thread to die.
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
m_WorkerThread.join(); // wait for the thread to finish.
m_WorkerThread = AZStd::thread();
if (m_p4ApplicationDetected)
{
m_shutdownThreadSignal = true; // tell the thread to die.
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
m_WorkerThread.join(); // wait for the thread to finish.
m_WorkerThread = AZStd::thread();
}
SetConnection(nullptr);
}
@@ -260,5 +260,7 @@ namespace AzToolsFramework
AZStd::atomic_bool m_validConnection;
SourceControlState m_connectionState;
bool m_p4ApplicationDetected { false };
};
} // namespace AzToolsFramework
@@ -27,6 +27,7 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
@@ -135,6 +136,10 @@ namespace AzToolsFramework
return;
}
// Initialize Editor functionality for the Prefab Focus Handler
auto prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
prefabFocusInterface->InitializeEditorInterfaces();
EditorContextMenuBus::Handler::BusConnect();
EditorEventsBus::Handler::BusConnect();
PrefabInstanceContainerNotificationBus::Handler::BusConnect();
@@ -250,7 +255,7 @@ namespace AzToolsFramework
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
{
// Edit Prefab
if (prefabWipFeaturesEnabled && !s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
{
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
@@ -1154,25 +1159,14 @@ namespace AzToolsFramework
{
s_editorEntityUiInterface->RegisterEntity(entityId, m_prefabUiHandler.GetHandlerId());
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
if (prefabWipFeaturesEnabled)
{
// Register entity as a container
s_containerEntityInterface->RegisterEntityAsContainer(entityId);
}
// Register entity as a container
s_containerEntityInterface->RegisterEntityAsContainer(entityId);
}
}
void PrefabIntegrationManager::OnPrefabComponentDeactivate(AZ::EntityId entityId)
{
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
if (prefabWipFeaturesEnabled && !s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
if (!s_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
{
// Unregister entity as a container
s_containerEntityInterface->UnregisterEntityAsContainer(entityId);
@@ -316,14 +316,7 @@ namespace AzToolsFramework
void PrefabUiHandler::OnDoubleClick(AZ::EntityId entityId) const
{
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
if (prefabWipFeaturesEnabled)
{
// Focus on this prefab
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
}
// Focus on this prefab
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
}
}
@@ -72,7 +72,7 @@ struct FolderRootWatch::PlatformImplementation
// Add the folder to watch and track it
int watchHandle = inotify_add_watch(m_iNotifyHandle,
cleanPath.toUtf8().constData(),
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY);
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
{
@@ -95,7 +95,7 @@ struct FolderRootWatch::PlatformImplementation
int watchHandle = inotify_add_watch(m_iNotifyHandle,
dirName.toUtf8().constData(),
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY);
IN_CREATE | IN_CLOSE_WRITE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVE);
if (!m_handleToFolderMapLock.tryLock(s_handleToFolderMapLockTimeout))
{
@@ -10,7 +10,9 @@
#include <utilities/BatchApplicationManager.h>
#include <utilities/ApplicationServer.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/Utils/Utils.h>
#include <connection/connectionManager.h>
#include <QCoreApplication>
#include <QTemporaryDir>
@@ -98,10 +100,26 @@ namespace AssetProcessorMessagesTests
int argC = 0;
m_batchApplicationManager = AZStd::make_unique<UnitTestBatchApplicationManager>(&argC, nullptr, nullptr);
m_batchApplicationManager->BeforeRun();
// Override Game Name to be "AutomatedTesting"
AssetUtilities::ComputeProjectName("AutomatedTesting", true);
auto registry = AZ::SettingsRegistry::Get();
EXPECT_NE(registry, nullptr);
constexpr AZ::SettingsRegistryInterface::FixedValueString bootstrapKey{
AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey
};
constexpr AZ::SettingsRegistryInterface::FixedValueString projectPathKey{ bootstrapKey + "/project_path" };
registry->Set(projectPathKey, "AutomatedTesting");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(*registry);
// Force the branch token into settings registry before starting the application manager.
// This avoids writing the asset_processor.setreg file which can cause fileIO errors.
const AZ::IO::FixedMaxPathString enginePath = AZ::Utils::GetEnginePath();
constexpr AZ::SettingsRegistryInterface::FixedValueString branchTokenKey{ bootstrapKey + "/assetProcessor_branch_token" };
AZStd::string token;
AZ::StringFunc::AssetPath::CalculateBranchToken(enginePath.c_str(), token);
registry->Set(branchTokenKey, token.c_str());
auto status = m_batchApplicationManager->BeforeRun();
ASSERT_EQ(status, ApplicationManager::BeforeRunStatus::Status_Success);
m_batchApplicationManager->m_platformConfiguration = new PlatformConfiguration();
m_batchApplicationManager->InitAssetProcessorManager();
@@ -159,21 +177,25 @@ namespace AssetProcessorMessagesTests
ASSERT_TRUE(result);
});
}
void TearDown() override
{
QEventLoop eventLoop;
if (m_batchApplicationManager->m_connectionManager)
{
QEventLoop eventLoop;
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
QObject::connect(m_batchApplicationManager->m_connectionManager, &ConnectionManager::ReadyToQuit, &eventLoop, &QEventLoop::quit);
m_batchApplicationManager->m_connectionManager->QuitRequested();
m_batchApplicationManager->m_connectionManager->QuitRequested();
eventLoop.exec();
eventLoop.exec();
}
m_assetSystemComponent->Deactivate();
if (m_assetSystemComponent)
{
m_assetSystemComponent->Deactivate();
}
m_batchApplicationManager->Destroy();
}
@@ -505,6 +505,14 @@ bool ApplicationManager::StartAZFramework()
AzFramework::Application::Descriptor appDescriptor;
AZ::ComponentApplication::StartupParameters params;
QDir projectPath{ AssetUtilities::ComputeProjectPath() };
if (!projectPath.exists("project.json"))
{
AZStd::string errorMsg = AZStd::string::format("Path '%s' is not a valid project path.", projectPath.path().toUtf8().constData());
AssetProcessor::MessageInfoBus::Broadcast(&AssetProcessor::MessageInfoBus::Events::OnErrorMessage, errorMsg.c_str());
return false;
}
QString projectName = AssetUtilities::ComputeProjectName();
// Prevent loading of gems in the Create method of the ComponentApplication
@@ -520,7 +528,6 @@ bool ApplicationManager::StartAZFramework()
//Registering all the Components
m_frameworkApp.RegisterComponentDescriptor(AzFramework::LogComponent::CreateDescriptor());
Reflect();
const AzFramework::CommandLine* commandLine = nullptr;
@@ -95,6 +95,8 @@ GUIApplicationManager::~GUIApplicationManager()
ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun()
{
AssetProcessor::MessageInfoBus::Handler::BusConnect();
ApplicationManager::BeforeRunStatus status = ApplicationManagerBase::BeforeRun();
if (status != ApplicationManager::BeforeRunStatus::Status_Success)
{
@@ -109,7 +111,6 @@ ApplicationManager::BeforeRunStatus GUIApplicationManager::BeforeRun()
#if defined(EXTERNAL_CRASH_REPORTING)
CrashHandler::ToolsCrashHandler::InitCrashHandler("AssetProcessor", projectAssetRoot.absolutePath().toStdString());
#endif
AssetProcessor::MessageInfoBus::Handler::BusConnect();
// we have to monitor both the cache folder and the database file and restart AP if either of them gets deleted
// It is important to note that we are monitoring the parent folder and not the actual cache folder itself since
@@ -436,98 +437,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
connection = Qt::QueuedConnection;
}
if (m_isCurrentlyLoadingGems)
{
// if something goes wrong during gem initialization, this is a special case and we need to be extra helpful.
const char* userSettingsFile = "_WAF_/user_settings.options";
const char* defaultSettingsFile = "_WAF_/default_settings.json";
QDir engineRoot;
AssetUtilities::ComputeEngineRoot(engineRoot);
QString settingsPath = engineRoot.absoluteFilePath(userSettingsFile);
QString friendlyErrorMessage;
bool usingDefaults = false;
if (QFile::exists(settingsPath))
{
QSettings loader(settingsPath, QSettings::IniFormat);
QVariant settingValue = loader.value("Game Projects/enabled_game_projects");
QStringList compiledProjects = settingValue.toStringList();
if (compiledProjects.isEmpty())
{
QByteArray byteArray;
QFile jsonFile;
jsonFile.setFileName(engineRoot.absoluteFilePath(defaultSettingsFile));
jsonFile.open(QIODevice::ReadOnly | QIODevice::Text);
byteArray = jsonFile.readAll();
jsonFile.close();
QJsonObject settingsObject = QJsonDocument::fromJson(byteArray).object();
QJsonArray projectsArray = settingsObject["Game Projects"].toArray();
if (!projectsArray.isEmpty())
{
auto projectObject = projectsArray[0].toObject();
QString projects = projectObject["default_value"].toString();
if (!projects.isEmpty())
{
compiledProjects = projects.split(',');
usingDefaults = true;
}
}
}
for (int i = 0; i < compiledProjects.size(); ++i)
{
compiledProjects[i] = compiledProjects[i].trimmed();
}
QString enabledProject = AssetUtilities::ComputeProjectName();
if (!compiledProjects.contains(enabledProject))
{
QString projectSourceLine;
if (usingDefaults)
{
projectSourceLine = QString("The currently compiled projects according to the defaults in %1 are '%2'").arg(defaultSettingsFile);
}
else
{
projectSourceLine = QString("The currently compiled projects according to %1 are '%2'").arg(userSettingsFile);
}
projectSourceLine = projectSourceLine.arg(compiledProjects.join(", "));
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
"The enabled game project is not in the list of compiled projects.\n"
"Please configure the enabled project to be compiled and rebuild or change the enabled project.\n"
"The currently enabled game project (from bootstrap.cfg or /%4 command-line parameter) is '%1'.\n"
"%2\n"
"Full error text:\n"
"%3"
).arg(enabledProject).arg(projectSourceLine).arg(message).arg(AssetUtilities::ProjectPathOverrideParameter);
}
}
if (friendlyErrorMessage.isEmpty())
{
friendlyErrorMessage = QString("An error occurred while loading gems.\n"
"This can happen when new gems are added to a project, but those gems need to be built in order to function.\n"
"This can also happen when switching to a different project, one which uses gems which are not yet built.\n"
"To continue, please build the current project before attempting to run Asset Processor again.\n\n"
"Full error text:\n"
"%1").arg(message);
}
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, friendlyErrorMessage), Q_ARG(bool, true));
}
else
{
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true));
}
QMetaObject::invokeMethod(this, "ShowMessageBox", connection, Q_ARG(QString, QString("Error")), Q_ARG(QString, QString(message)), Q_ARG(bool, true));
return true;
}