merging latest development
Signed-off-by: kberg-amzn <karlberg@amazon.com>
This commit is contained in:
@@ -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>
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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(
|
||||
|
||||
+18
@@ -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, ¤tLimit);
|
||||
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
|
||||
|
||||
+71
-24
@@ -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)
|
||||
|
||||
+1
-1
@@ -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>;
|
||||
|
||||
+17
@@ -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,
|
||||
|
||||
+18
-5
@@ -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
|
||||
|
||||
+9
-15
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user