Merge branch 'development' into memory/overrideshim_removal

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>

# Conflicts:
#	Code/Framework/AzCore/AzCore/Memory/AllocatorBase.cpp
#	Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapAllocator.cpp
#	Code/Framework/AzCore/AzCore/Memory/BestFitExternalMapSchema.cpp
#	Code/Framework/AzCore/AzCore/Memory/PoolSchema.cpp
#	Code/Framework/AzCore/AzCore/Memory/SystemAllocator.cpp
#	Code/Framework/AzCore/Tests/Memory/AllocatorBenchmarks.cpp
This commit is contained in:
Esteban Papp
2022-01-05 11:34:52 -08:00
1267 changed files with 157305 additions and 27177 deletions
-7
View File
@@ -30,8 +30,6 @@ CAboutDialog::CAboutDialog(QString versionText, QString richTextCopyrightNotice,
m_ui->setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
connect(m_ui->m_transparentAgreement, &QLabel::linkActivated, this, &CAboutDialog::OnCustomerAgreement);
m_ui->m_transparentTrademarks->setText(versionText);
m_ui->m_transparentAllRightReserved->setObjectName("copyrightNotice");
@@ -84,9 +82,4 @@ void CAboutDialog::mouseReleaseEvent(QMouseEvent* event)
QDialog::mouseReleaseEvent(event);
}
void CAboutDialog::OnCustomerAgreement()
{
QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.o3debinaries.org/license")));
}
#include <moc_AboutDialog.cpp>
-2
View File
@@ -30,8 +30,6 @@ public:
private:
void OnCustomerAgreement();
void mouseReleaseEvent(QMouseEvent* event) override;
void paintEvent(QPaintEvent* event) override;
+6 -8
View File
@@ -181,14 +181,17 @@
</spacer>
</item>
<item>
<widget class="ClickableLabel" name="m_transparentAgreement">
<widget class="QLabel" name="m_transparentAgreement">
<property name="text">
<string>Terms of Use</string>
<string>&lt;a href=&quot;https://www.o3debinaries.org/license&quot;&gt;Terms of Use&lt;/a&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="showDecoration" stdset="0">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
@@ -274,11 +277,6 @@
<extends>QWidget</extends>
<header>qsvgwidget.h</header>
</customwidget>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>QtUI/ClickableLabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
@@ -1,30 +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 "AnimationBipedBoneNames.h"
namespace EditorAnimationBones::Biped
{
const char* Pelvis = "Bip01 Pelvis";
const char* Head = "Bip01 Head";
const char* Weapon = "weapon_bone";
const char* LeftEye = "eye_bone_left";
const char* RightEye = "eye_bone_right";
const char* Spine[5] = { "Bip01 Spine", "Bip01 Spine1", "Bip01 Spine2", "Bip01 Spine3", "Bip01 Spine4" };
const char* Neck[2] = { "Bip01 Neck", "Bip01 Neck1" };
const char* LeftHeel = "Bip01 L Heel";
const char* LeftToe[2] = { "Bip01 L Toe0", "Bip01 L Toe1" };
const char* RightHeel = "Bip01 R Heel";
const char* RightToe[2] = { "Bip01 R Toe0", "Bip01 R Toe1" };
} // namespace EditorAnimationBones::Biped
@@ -1,34 +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_ANIMATION_ANIMATIONBIPEDBONENAMES_H
#define CRYINCLUDE_EDITOR_ANIMATION_ANIMATIONBIPEDBONENAMES_H
#pragma once
namespace EditorAnimationBones
{
namespace Biped
{
extern const char* Pelvis;
extern const char* Head;
extern const char* Weapon;
extern const char* Spine[5];
extern const char* Neck[2];
extern const char* LeftEye;
extern const char* RightEye;
extern const char* LeftHeel;
extern const char* RightHeel;
extern const char* LeftToe[2];
extern const char* RightToe[2];
}
}
#endif // CRYINCLUDE_EDITOR_ANIMATION_ANIMATIONBIPEDBONENAMES_H
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:58ef978b31b31df9aaf715a0e9b006fde414a17a3ff15a3bf680eaad7418867a
size 364
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:98a681ec3d89ee57c5d1057fe984dcf8ad45721f47ae4df57fa358fbee85e616
size 385
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24a2b2c9242a841c20e7815dab0d80a575844055328aea413d28b7283b65a92e
size 386
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ce23a276fec849b8f832fab96d3b738793335c27d37ae3813158387f3415b508
size 377
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c03befab41765200f4f28dbf1e0b2a702d2244bfa79b0d463f5d58d0a26095fc
size 386
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:418c3f0f27854b3795841359014d87686a7bf94daf2568d9cfd3ffac22675f69
size 386
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d3a831f34ac53c9b1f20037290e8a2b62a3cfb8a4f86467591f44fd2a0e3c15b
size 379
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4267102ca7a889c34eff905480a68878d4d56e15bc723a5b0575cd472e259f5d
size 389
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a0df013dd102b87348fba18b4da5443591309e9c40166d27ae928636924154ea
size 388
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e713076ab5abbbb2cf28da431a339e9905acc790e35295f025aa2e79e1c04141
size 376
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9e5af9d62ceafc3b8a1dfc36772350cd623fcc86c68711b299e143ff133f79b6
size 387
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:17c5fb3d7b87ea87a98934954c721573c641bc44005a34f1e16589d7f39b71e8
size 409
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6f5c78d9f764b62fb7dcf400c91c1edea9d7f88a426ba513fbf70825c6bcd2ac
size 383
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:376b549602afffca407525b77c1a9821bf6a0e279792ae2e52fe0a4f7c3c5bd4
size 364
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e7dc48f8d324b7563b168f27ebde1e00ee2bd11ba462f114a05b297913e285c5
size 374
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:66b73afbd6dba1caaedfaae161b277b460b5198f7fc00bec414530116c567276
size 375
-3
View File
@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ae6e6714acf495246f4e59f6e5640f3a4417ea50100d37a116950d2b859aed0c
size 417
-111
View File
@@ -1,111 +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_CONSOLESCBMFC_H
#define CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCBMFC_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QTextEdit>
#include <QtWidgets/QPushButton>
#include "ConsoleSCB.h"
#endif
class QMenu;
class ConsoleWidget;
class QFocusEvent;
namespace Ui {
class ConsoleMFC;
}
namespace MFC
{
struct ConsoleLine
{
QString text;
bool newLine;
};
typedef std::deque<ConsoleLine> Lines;
class ConsoleLineEdit
: public QLineEdit
{
Q_OBJECT
public:
explicit ConsoleLineEdit(QWidget* parent = nullptr);
protected:
void mousePressEvent(QMouseEvent* ev) override;
void mouseDoubleClickEvent(QMouseEvent* ev) override;
void keyPressEvent(QKeyEvent* ev) override;
bool event(QEvent* ev) override;
signals:
void variableEditorRequested();
void setWindowTitle(const QString&);
private:
void DisplayHistory(bool bForward);
QStringList m_history;
unsigned int m_historyIndex;
bool m_bReusedHistory;
};
class ConsoleTextEdit
: public QTextEdit
{
Q_OBJECT
public:
explicit ConsoleTextEdit(QWidget* parent = nullptr);
};
class CConsoleSCB
: public QWidget
{
Q_OBJECT
public:
explicit CConsoleSCB(QWidget* parent = nullptr);
~CConsoleSCB();
static void RegisterViewClass();
void SetInputFocus();
void AddToConsole(const QString& text, bool bNewLine);
void FlushText();
void showPopupAndSetTitle();
QSize sizeHint() const override;
QSize minimumSizeHint() const override;
static CConsoleSCB* GetCreatedInstance();
static void AddToPendingLines(const QString& text, bool bNewLine); // call this function instead of AddToConsole() until an instance of CConsoleSCB exists to prevent messages from getting lost
public Q_SLOTS:
void OnStyleSettingsChanged();
private Q_SLOTS:
void showVariableEditor();
private:
QScopedPointer<Ui::ConsoleMFC> ui;
int m_richEditTextLength;
Lines m_lines;
static Lines s_pendingLines;
QList<QColor> m_colorTable;
SEditorSettings::ConsoleColorTheme m_backgroundTheme;
};
} // namespace MFC
#endif // CRYINCLUDE_EDITOR_CONTROLS_CONSOLESCB_H
-132
View File
@@ -1,132 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConsoleMFC</class>
<widget class="QWidget" name="Console">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>120</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Ignored">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Console</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QTextEdit" name="textEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="container2" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>20</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>20</height>
</size>
</property>
<property name="autoFillBackground">
<bool>true</bool>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="margin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="button">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>20</width>
<height>30</height>
</size>
</property>
<property name="baseSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="MFC::ConsoleLineEdit" name="lineEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MFC::ConsoleLineEdit</class>
<extends>QLineEdit</extends>
<header>ConsoleSCBMFC.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="ConsoleSCB.qrc"/>
</resources>
<connections/>
</ui>
@@ -1,48 +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 "HotTrackingTreeCtrl.h"
// Qt
#include <QMouseEvent>
CHotTrackingTreeCtrl::CHotTrackingTreeCtrl(QWidget* parent)
: QTreeWidget(parent)
{
setMouseTracking(true);
m_hHoverItem = nullptr;
}
void CHotTrackingTreeCtrl::mouseMoveEvent(QMouseEvent* event)
{
QTreeWidgetItem* hItem = itemAt(event->pos());
if (m_hHoverItem != nullptr)
{
QFont font = m_hHoverItem->font(0);
font.setBold(false);
m_hHoverItem->setFont(0, font);
m_hHoverItem = nullptr;
}
if (hItem != nullptr)
{
QFont font = hItem->font(0);
font.setBold(true);
hItem->setFont(0, font);
m_hHoverItem = hItem;
}
QTreeWidget::mouseMoveEvent(event);
}
#include <Controls/moc_HotTrackingTreeCtrl.cpp>
@@ -1,33 +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_HOTTRACKINGTREECTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_HOTTRACKINGTREECTRL_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QTreeWidget>
#endif
class CHotTrackingTreeCtrl
: public QTreeWidget
{
Q_OBJECT
public:
CHotTrackingTreeCtrl(QWidget* parent = 0);
virtual ~CHotTrackingTreeCtrl(){};
protected:
void mouseMoveEvent(QMouseEvent* event) override;
private:
QTreeWidgetItem* m_hHoverItem;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_HOTTRACKINGTREECTRL_H
-567
View File
@@ -1,567 +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 "ImageListCtrl.h"
// Qt
#include <QPainter>
#include <QScrollBar>
//////////////////////////////////////////////////////////////////////////
CImageListCtrl::CImageListCtrl(QWidget* parent)
: QAbstractItemView(parent)
, m_itemSize(60, 60)
, m_borderSize(4, 4)
, m_style(DefaultStyle)
{
setItemDelegate(new QImageListDelegate(this));
setAutoFillBackground(false);
QPalette p = palette();
p.setColor(QPalette::Highlight, QColor(255, 55, 50));
setPalette(p);
horizontalScrollBar()->setRange(0, 0);
verticalScrollBar()->setRange(0, 0);
}
//////////////////////////////////////////////////////////////////////////
CImageListCtrl::~CImageListCtrl()
{
}
//////////////////////////////////////////////////////////////////////////
CImageListCtrl::ListStyle CImageListCtrl::Style() const
{
return m_style;
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::SetStyle(ListStyle style)
{
m_style = style;
scheduleDelayedItemsLayout();
}
//////////////////////////////////////////////////////////////////////////
const QSize& CImageListCtrl::ItemSize() const
{
return m_itemSize;
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::SetItemSize(QSize size)
{
Q_ASSERT(size.isValid());
m_itemSize = size;
scheduleDelayedItemsLayout();
}
//////////////////////////////////////////////////////////////////////////
const QSize& CImageListCtrl::BorderSize() const
{
return m_borderSize;
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::SetBorderSize(QSize size)
{
Q_ASSERT(size.isValid());
m_borderSize = size;
scheduleDelayedItemsLayout();
}
//////////////////////////////////////////////////////////////////////////
QModelIndexList CImageListCtrl::ItemsInRect(const QRect& rect) const
{
QModelIndexList list;
if (!model())
{
return list;
}
QHash<int, QRect>::const_iterator i;
QHash<int, QRect>::const_iterator c = m_geometry.cend();
for (i = m_geometry.cbegin(); i != c; ++i)
{
if (i.value().intersects(rect))
{
list << model()->index(i.key(), 0, rootIndex());
}
}
return list;
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::paintEvent(QPaintEvent* event)
{
QAbstractItemView::paintEvent(event);
if (!model())
{
return;
}
const int rowCount = model()->rowCount();
if (m_geometry.isEmpty() && rowCount)
{
updateGeometries();
}
QPainter painter(viewport());
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.setBackground(palette().window());
painter.setFont(font());
QStyleOptionViewItem option;
option.palette = palette();
option.font = font();
option.fontMetrics = fontMetrics();
option.decorationAlignment = Qt::AlignCenter;
const QRect visibleRect(QPoint(horizontalOffset(), verticalOffset()), viewport()->contentsRect().size());
painter.translate(-horizontalOffset(), -verticalOffset());
for (int r = 0; r < rowCount; ++r)
{
const QModelIndex& index = model()->index(r, 0, rootIndex());
option.rect = m_geometry.value(r);
if (!option.rect.intersects(visibleRect))
{
continue;
}
option.state = QStyle::State_None;
if (selectionModel()->isSelected(index))
{
option.state |= QStyle::State_Selected;
}
if (currentIndex() == index)
{
option.state |= QStyle::State_HasFocus;
}
QAbstractItemDelegate* idt = itemDelegate(index);
idt->paint(&painter, option, index);
}
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::rowsInserted(const QModelIndex& parent, int start, int end)
{
QAbstractItemView::rowsInserted(parent, start, end);
if (isVisible())
{
scheduleDelayedItemsLayout();
}
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::updateGeometries()
{
ClearItemGeometries();
if (!model())
{
return;
}
const int rowCount = model()->rowCount();
const int nPageHorz = viewport()->width();
const int nPageVert = viewport()->height();
if (nPageHorz == 0 || nPageVert == 0 || rowCount <= 0)
{
return;
}
int x = m_borderSize.width();
int y = m_borderSize.height();
const int nItemWidth = m_itemSize.width() + m_borderSize.width();
if (m_style == HorizontalStyle)
{
for (int row = 0; row < rowCount; ++row)
{
m_geometry.insert(row, QRect(QPoint(x, y), m_itemSize));
x += nItemWidth;
}
horizontalScrollBar()->setPageStep(viewport()->width());
horizontalScrollBar()->setRange(0, x - viewport()->width());
}
else
{
const int nTextHeight = fontMetrics().height();
const int nItemHeight = m_itemSize.height() + m_borderSize.height() + nTextHeight;
int nNumOfHorzItems = nPageHorz / nItemWidth;
if (nNumOfHorzItems <= 0)
{
nNumOfHorzItems = 1;
}
for (int row = 0; row < rowCount; ++row)
{
m_geometry.insert(row, QRect(QPoint(x, y), m_itemSize));
if ((row + 1) % nNumOfHorzItems == 0)
{
y += nItemHeight;
x = m_borderSize.width();
}
else
{
x += nItemWidth;
}
}
verticalScrollBar()->setPageStep(viewport()->height());
verticalScrollBar()->setRange(0, (y + nItemHeight) - viewport()->height());
}
}
//////////////////////////////////////////////////////////////////////////
QModelIndex CImageListCtrl::indexAt(const QPoint& point) const
{
if (!model())
{
return QModelIndex();
}
const QPoint p = point +
QPoint(horizontalOffset(), verticalOffset());
QHash<int, QRect>::const_iterator i;
QHash<int, QRect>::const_iterator c = m_geometry.cend();
for (i = m_geometry.cbegin(); i != c; ++i)
{
if (i.value().contains(p))
{
return model()->index(i.key(), 0, rootIndex());
}
}
return QModelIndex();
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::scrollTo(const QModelIndex& index, ScrollHint hint)
{
if (!index.isValid())
{
return;
}
QRect rect = m_geometry.value(index.row());
switch (hint)
{
case EnsureVisible:
if (horizontalOffset() > rect.right())
{
horizontalScrollBar()->setValue(rect.left());
}
else if ((horizontalOffset() + viewport()->width()) < rect.left())
{
horizontalScrollBar()->setValue(rect.right() - viewport()->width());
}
if (verticalOffset() > rect.bottom())
{
verticalScrollBar()->setValue(rect.top());
}
else if ((verticalOffset() + viewport()->height()) < rect.top())
{
verticalScrollBar()->setValue(rect.bottom() - viewport()->height());
}
break;
case PositionAtTop:
horizontalScrollBar()->setValue(rect.left());
verticalScrollBar()->setValue(rect.top());
break;
case PositionAtBottom:
horizontalScrollBar()->setValue(rect.right() - viewport()->width());
verticalScrollBar()->setValue(rect.bottom() - viewport()->height());
break;
case PositionAtCenter:
horizontalScrollBar()->setValue(rect.center().x() - (viewport()->width() / 2));
verticalScrollBar()->setValue(rect.center().y() - (viewport()->height() / 2));
break;
}
}
//////////////////////////////////////////////////////////////////////////
QRect CImageListCtrl::visualRect(const QModelIndex& index) const
{
if (!index.isValid())
{
return QRect();
}
if (!m_geometry.contains(index.row()))
{
return QRect();
}
return m_geometry.value(index.row())
.translated(-horizontalOffset(), -verticalOffset());
}
//////////////////////////////////////////////////////////////////////////
QRect CImageListCtrl::ItemGeometry(const QModelIndex& index) const
{
Q_ASSERT(index.model() == model());
Q_ASSERT(m_geometry.contains(index.row()));
return m_geometry.value(index.row());
}
void CImageListCtrl::SetItemGeometry(const QModelIndex& index, const QRect& rect)
{
Q_ASSERT(index.model() == model());
m_geometry.insert(index.row(), rect);
update(rect);
}
void CImageListCtrl::ClearItemGeometries()
{
m_geometry.clear();
}
//////////////////////////////////////////////////////////////////////////
int CImageListCtrl::horizontalOffset() const
{
return horizontalScrollBar()->value();
}
//////////////////////////////////////////////////////////////////////////
int CImageListCtrl::verticalOffset() const
{
return verticalScrollBar()->value();
}
//////////////////////////////////////////////////////////////////////////
bool CImageListCtrl::isIndexHidden([[maybe_unused]] const QModelIndex& index) const
{
return false; /* not supported */
}
//////////////////////////////////////////////////////////////////////////
QModelIndex CImageListCtrl::moveCursor(CursorAction cursorAction, [[maybe_unused]] Qt::KeyboardModifiers modifiers)
{
if (!model())
{
return QModelIndex();
}
const int rowCount = model()->rowCount();
if (0 == rowCount)
{
return QModelIndex();
}
switch (cursorAction)
{
case MoveHome:
return model()->index(0, 0, rootIndex());
case MoveEnd:
return model()->index(rowCount - 1, 0, rootIndex());
case MovePrevious:
{
QModelIndex current = currentIndex();
if (current.isValid())
{
return model()->index((current.row() - 1) % rowCount, 0, rootIndex());
}
} break;
case MoveNext:
{
QModelIndex current = currentIndex();
if (current.isValid())
{
return model()->index((current.row() + 1) % rowCount, 0, rootIndex());
}
} break;
case MoveUp:
case MoveDown:
case MoveLeft:
case MoveRight:
case MovePageUp:
case MovePageDown:
/* TODO */
break;
}
return QModelIndex();
}
//////////////////////////////////////////////////////////////////////////
void CImageListCtrl::setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags flags)
{
if (!model())
{
return;
}
const QRect lrect =
rect.translated(horizontalOffset(), verticalOffset());
QHash<int, QRect>::const_iterator i;
QHash<int, QRect>::const_iterator c = m_geometry.cend();
for (i = m_geometry.cbegin(); i != c; ++i)
{
if (i.value().intersects(lrect))
{
selectionModel()->select(model()->index(i.key(), 0, rootIndex()), flags);
}
}
}
//////////////////////////////////////////////////////////////////////////
QRegion CImageListCtrl::visualRegionForSelection(const QItemSelection& selection) const
{
QRegion region;
foreach(const QModelIndex &index, selection.indexes())
{
region += visualRect(index);
}
return region;
}
//////////////////////////////////////////////////////////////////////////
QImageListDelegate::QImageListDelegate(QObject* parent)
: QAbstractItemDelegate(parent)
{
}
//////////////////////////////////////////////////////////////////////////
void QImageListDelegate::paint(QPainter* painter,
const QStyleOptionViewItem& option, const QModelIndex& index) const
{
painter->save();
painter->setFont(option.font);
if (option.rect.isValid())
{
painter->setClipRect(option.rect);
}
QRect innerRect = option.rect.adjusted(1, 1, -1, -1);
QRect textRect(innerRect.left(), innerRect.bottom() - option.fontMetrics.height(),
innerRect.width(), option.fontMetrics.height() + 1);
/* fill item background */
painter->fillRect(option.rect, option.palette.color(QPalette::Base));
/* draw image */
if (index.data(Qt::DecorationRole).isValid())
{
const QPixmap& p = index.data(Qt::DecorationRole).value<QPixmap>();
if (p.isNull() || p.size() == QSize(1, 1))
{
emit InvalidPixmapGenerated(index);
}
else
{
painter->drawPixmap(innerRect, p);
}
}
/* draw text */
const QColor trColor = option.palette.color(QPalette::Shadow);
painter->fillRect(textRect, (option.state & QStyle::State_Selected) ?
trColor.lighter() : trColor);
if (option.state & QStyle::State_Selected)
{
painter->setPen(QPen(option.palette.color(QPalette::HighlightedText)));
QFont f = painter->font();
f.setBold(true);
painter->setFont(f);
}
else
{
painter->setPen(QPen(option.palette.color(QPalette::Text)));
}
painter->drawText(textRect, index.data(Qt::DisplayRole).toString(),
QTextOption(option.decorationAlignment));
painter->setPen(QPen(option.palette.color(QPalette::Shadow)));
painter->drawRect(textRect);
/* draw border */
if (option.state & QStyle::State_Selected)
{
QPen pen(option.palette.color(QPalette::Highlight));
pen.setWidth(2);
painter->setPen(pen);
painter->drawRect(innerRect);
}
else
{
painter->setPen(QPen(option.palette.color(QPalette::Shadow)));
painter->drawRect(option.rect);
}
if (option.state & QStyle::State_HasFocus)
{
QPen pen(Qt::DotLine);
pen.setColor(option.palette.color(QPalette::AlternateBase));
painter->setPen(pen);
painter->drawRect(option.rect);
}
painter->restore();
}
//////////////////////////////////////////////////////////////////////////
QSize QImageListDelegate::sizeHint(const QStyleOptionViewItem& option,
[[maybe_unused]] const QModelIndex& index) const
{
return option.rect.size();
}
//////////////////////////////////////////////////////////////////////////
QVector<int> QImageListDelegate::paintingRoles() const
{
return QVector<int>() << Qt::DecorationRole << Qt::DisplayRole;
}
#include <Controls/moc_ImageListCtrl.cpp>
-97
View File
@@ -1,97 +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_IMAGELISTCTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_IMAGELISTCTRL_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QAbstractItemView>
#include <QHash>
#endif
//////////////////////////////////////////////////////////////////////////
// Custom control to display list of images.
//////////////////////////////////////////////////////////////////////////
class CImageListCtrl
: public QAbstractItemView
{
Q_OBJECT
public:
enum ListStyle
{
DefaultStyle,
HorizontalStyle
};
public:
CImageListCtrl(QWidget* parent = nullptr);
~CImageListCtrl();
ListStyle Style() const;
void SetStyle(ListStyle style);
const QSize& ItemSize() const;
void SetItemSize(QSize size);
const QSize& BorderSize() const;
void SetBorderSize(QSize size);
// Get all items inside specified rectangle.
QModelIndexList ItemsInRect(const QRect& rect) const;
QModelIndex indexAt(const QPoint& point) const override;
void scrollTo(const QModelIndex& index, ScrollHint hint = EnsureVisible) override;
QRect visualRect(const QModelIndex& index) const override;
protected:
QRect ItemGeometry(const QModelIndex& index) const;
void SetItemGeometry(const QModelIndex& index, const QRect& rect);
void ClearItemGeometries();
int horizontalOffset() const override;
int verticalOffset() const override;
bool isIndexHidden(const QModelIndex& index) const override;
QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers) override;
void setSelection(const QRect& rect, QItemSelectionModel::SelectionFlags flags) override;
QRegion visualRegionForSelection(const QItemSelection& selection) const override;
void paintEvent(QPaintEvent* event) override;
void rowsInserted(const QModelIndex& parent, int start, int end) override;
void updateGeometries() override;
private:
QHash<int, QRect> m_geometry;
QSize m_itemSize;
QSize m_borderSize;
ListStyle m_style;
};
class QImageListDelegate
: public QAbstractItemDelegate
{
Q_OBJECT
signals:
void InvalidPixmapGenerated(const QModelIndex& index) const;
public:
QImageListDelegate(QObject* parent = nullptr);
void paint(QPainter* painter,
const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
QVector<int> paintingRoles() const override;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_IMAGELISTCTRL_H
-67
View File
@@ -1,67 +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 "MultiMonHelper.h"
// Qt
#include <QScreen>
////////////////////////////////////////////////////////////////////////////
void ClipOrCenterRectToMonitor(QRect *prc, const UINT flags)
{
const QScreen* currentScreen = nullptr;
QRect rc;
Q_ASSERT(prc);
const auto screens = qApp->screens();
for (auto screen : screens)
{
if (screen->geometry().contains(prc->center()))
{
currentScreen = screen;
break;
}
}
if (!currentScreen)
{
return;
}
const int w = prc->width();
const int h = prc->height();
if (flags & MONITOR_WORKAREA)
{
rc = currentScreen->availableGeometry();
}
else
{
rc = currentScreen->geometry();
}
// center or clip the passed rect to the monitor rect
if (flags & MONITOR_CENTER)
{
prc->setLeft(rc.left() + (rc.right() - rc.left() - w) / 2);
prc->setTop(rc.top() + (rc.bottom() - rc.top() - h) / 2);
prc->setRight(prc->left() + w);
prc->setBottom(prc->top() + h);
}
else
{
prc->setLeft(qMax(rc.left(), qMin(rc.right() - w, prc->left())));
prc->setTop(qMax(rc.top(), qMin(rc.bottom() - h, prc->top())));
prc->setRight(prc->left() + w);
prc->setBottom(prc->top() + h);
}
}
-44
View File
@@ -1,44 +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_MULTIMONHELPER_H
#define CRYINCLUDE_EDITOR_CONTROLS_MULTIMONHELPER_H
#pragma once
// Taken from: http://msdn.microsoft.com/en-us/library/dd162826(v=vs.85).aspx
#define MONITOR_CENTER 0x0001 // center rect to monitor
#define MONITOR_CLIP 0x0000 // clip rect to monitor
#define MONITOR_WORKAREA 0x0002 // use monitor work area
#define MONITOR_AREA 0x0000 // use monitor entire area
//
// ClipOrCenterRectToMonitor
//
// The most common problem apps have when running on a
// multimonitor system is that they "clip" or "pin" windows
// based on the SM_CXSCREEN and SM_CYSCREEN system metrics.
// Because of app compatibility reasons these system metrics
// return the size of the primary monitor.
//
// This shows how you use the multi-monitor functions
// to do the same thing.
//
// params:
// prc : pointer to QRect to modify
// flags : some combination of the MONITOR_* flags above
//
// example:
//
// ClipOrCenterRectToMonitor(&aRect, MONITOR_CLIP | MONITOR_WORKAREA);
//
// Takes parameter pointer to RECT "aRect" and flags MONITOR_CLIP | MONITOR_WORKAREA
// This will modify aRect without resizing it so that it remains within the on-screen boundaries.
void ClipOrCenterRectToMonitor(QRect *prc, const UINT flags);
#endif // CRYINCLUDE_EDITOR_CONTROLS_MULTIMONHELPER_H
-143
View File
@@ -1,143 +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 "NumberCtrl.h"
QNumberCtrl::QNumberCtrl(QWidget* parent)
: QDoubleSpinBox(parent)
, m_bMouseDown(false)
, m_bDragged(false)
, m_bUndoEnabled(false)
, m_prevValue(0)
{
connect(this, &QAbstractSpinBox::editingFinished, this, &QNumberCtrl::onEditingFinished);
}
void QNumberCtrl::changeEvent(QEvent* event)
{
if (event->type() == QEvent::EnabledChange)
{
setButtonSymbols(isEnabled() ? UpDownArrows : NoButtons);
}
QDoubleSpinBox::changeEvent(event);
}
void QNumberCtrl::SetRange(double newMin, double newMax)
{
// Avoid setting this value if its close to the current value, because otherwise qt will pump events into the queue to redraw/etc.
if ( (!AZ::IsClose(this->minimum(), newMin, DBL_EPSILON)) || (!AZ::IsClose(this->maximum(), newMax, DBL_EPSILON)) )
{
setRange(newMin, newMax);
}
}
void QNumberCtrl::mousePressEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton)
{
emit mousePressed();
m_bMouseDown = true;
m_bDragged = false;
m_mousePos = event->pos();
if (m_bUndoEnabled && !CUndo::IsRecording())
{
GetIEditor()->BeginUndo();
}
emit dragStarted();
grabMouse();
}
QDoubleSpinBox::mousePressEvent(event);
}
void QNumberCtrl::mouseReleaseEvent(QMouseEvent* event)
{
QDoubleSpinBox::mouseReleaseEvent(event);
if (event->button() == Qt::LeftButton)
{
m_bMouseDown = m_bDragged = false;
emit valueUpdated();
emit valueChanged();
if (m_bUndoEnabled && CUndo::IsRecording())
{
GetIEditor()->AcceptUndo(m_undoText);
}
emit dragFinished();
releaseMouse();
m_prevValue = value();
emit mouseReleased();
}
}
void QNumberCtrl::mouseMoveEvent(QMouseEvent* event)
{
QDoubleSpinBox::mousePressEvent(event);
if (m_bMouseDown)
{
m_bDragged = true;
int dy = event->pos().y() - m_mousePos.y();
setValue(value() - singleStep() * dy);
emit valueUpdated();
m_mousePos = event->pos();
}
}
void QNumberCtrl::EnableUndo(const QString& undoText)
{
m_undoText = undoText;
m_bUndoEnabled = true;
}
void QNumberCtrl::focusInEvent(QFocusEvent* event)
{
m_prevValue = value();
QDoubleSpinBox::focusInEvent(event);
}
void QNumberCtrl::onEditingFinished()
{
bool undo = m_bUndoEnabled && !CUndo::IsRecording() && m_prevValue != value();
if (undo)
{
GetIEditor()->BeginUndo();
}
emit valueUpdated();
emit valueChanged();
if (undo)
{
GetIEditor()->AcceptUndo(m_undoText);
}
m_prevValue = value();
}
#include <Controls/moc_NumberCtrl.cpp>
-64
View File
@@ -1,64 +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_NUMBERCTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_NUMBERCTRL_H
#pragma once
// NumberCtrl.h : header file
//
#if !defined(Q_MOC_RUN)
#include <QDoubleSpinBox>
#endif
class QNumberCtrl
: public QDoubleSpinBox
{
Q_OBJECT
public:
QNumberCtrl(QWidget* parent = nullptr);
bool IsDragging() const { return m_bDragged; }
//! If called will enable undo with given text when control is modified.
void EnableUndo(const QString& undoText);
void SetRange(double newMin, double maxRange);
Q_SIGNALS:
void dragStarted();
void dragFinished();
void valueUpdated();
void valueChanged();
void mouseReleased();
void mousePressed();
protected:
void changeEvent(QEvent* event) override;
void focusInEvent(QFocusEvent* event) override;
void mousePressEvent(QMouseEvent* event) override;
void mouseMoveEvent(QMouseEvent* event) override;
void mouseReleaseEvent(QMouseEvent* event) override;
private:
void onEditingFinished();
void onValueChanged(double d);
bool m_bMouseDown;
bool m_bDragged;
QPoint m_mousePos;
bool m_bUndoEnabled;
double m_prevValue;
QString m_undoText;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_NUMBERCTRL_H
-93
View File
@@ -1,93 +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 "TextEditorCtrl.h"
// CTextEditorCtrl
CTextEditorCtrl::CTextEditorCtrl(QWidget* pParent)
: QTextEdit(pParent)
{
m_bModified = true;
QFont font;
font.setFamily("Courier New");
font.setFixedPitch(true);
font.setPointSize(10);
setFont(font);
setLineWrapMode(NoWrap);
connect(this, &QTextEdit::textChanged, this, &CTextEditorCtrl::OnChange);
}
CTextEditorCtrl::~CTextEditorCtrl()
{
}
// CTextEditorCtrl message handlers
void CTextEditorCtrl::LoadFile(const QString& sFileName)
{
if (m_filename == sFileName)
{
return;
}
m_filename = sFileName;
clear();
CCryFile file(sFileName.toUtf8().data(), "rb");
if (file.Open(sFileName.toUtf8().data(), "rb"))
{
size_t length = file.GetLength();
QByteArray text;
text.resize(static_cast<int>(length));
file.ReadRaw(text.data(), length);
setPlainText(text);
}
m_bModified = false;
}
//////////////////////////////////////////////////////////////////////////
void CTextEditorCtrl::SaveFile(const QString& sFileName)
{
if (sFileName.isEmpty())
{
return;
}
if (!CFileUtil::OverwriteFile(sFileName.toUtf8().data()))
{
return;
}
QFile file(sFileName);
file.open(QFile::WriteOnly);
file.write(toPlainText().toUtf8());
m_bModified = false;
}
//////////////////////////////////////////////////////////////////////////
void CTextEditorCtrl::OnChange()
{
m_bModified = true;
}
#include <Controls/moc_TextEditorCtrl.cpp>
-42
View File
@@ -1,42 +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_TEXTEDITORCTRL_H
#define CRYINCLUDE_EDITOR_CONTROLS_TEXTEDITORCTRL_H
#pragma once
// CTextEditorCtrl
#if !defined(Q_MOC_RUN)
#include <QTextEdit>
#endif
class CTextEditorCtrl
: public QTextEdit
{
Q_OBJECT
public:
CTextEditorCtrl(QWidget* pParent = nullptr);
virtual ~CTextEditorCtrl();
void LoadFile(const QString& sFileName);
void SaveFile(const QString& sFileName);
QString GetFilename() const { return m_filename; }
bool IsModified() const { return m_bModified; }
//! Must be called after OnChange message.
void OnChange();
protected:
QString m_filename;
bool m_bModified;
};
#endif // CRYINCLUDE_EDITOR_CONTROLS_TEXTEDITORCTRL_H
-322
View File
@@ -1,322 +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_TREECTRLUTILS_H
#define CRYINCLUDE_EDITOR_CONTROLS_TREECTRLUTILS_H
#pragma once
#include <iterator>
namespace TreeCtrlUtils
{
template <typename P>
class TreeItemIterator
: public P
{
public:
typedef P Traits;
//iterator traits, required by STL
typedef ptrdiff_t difference_type;
typedef HTREEITEM value_type;
typedef HTREEITEM* pointer;
typedef HTREEITEM& reference;
typedef std::forward_iterator_tag iterator_category;
TreeItemIterator()
: pCtrl(0)
, hItem(0) {}
explicit TreeItemIterator(const P& traits)
: P(traits)
, pCtrl(0)
, hItem(0) {}
TreeItemIterator(const TreeItemIterator& other)
: P(other)
, pCtrl(other.pCtrl)
, hItem(other.hItem) {}
TreeItemIterator(CTreeCtrl* pCtrl, HTREEITEM hItem)
: pCtrl(pCtrl)
, hItem(hItem) {}
TreeItemIterator(CTreeCtrl* pCtrl, HTREEITEM hItem, const P& traits)
: P(traits)
, pCtrl(pCtrl)
, hItem(hItem) {}
HTREEITEM operator*() {return hItem; }
bool operator==(const TreeItemIterator& other) const {return pCtrl == other.pCtrl && hItem == other.hItem; }
bool operator!=(const TreeItemIterator& other) const {return pCtrl != other.pCtrl || hItem != other.hItem; }
TreeItemIterator& operator++()
{
HTREEITEM hNextItem = 0;
if (RecurseToChildren(hItem))
{
hNextItem = (pCtrl ? pCtrl->GetChildItem(hItem) : 0);
}
while (pCtrl && hItem && !hNextItem)
{
hNextItem = pCtrl->GetNextSiblingItem(hItem);
if (!hNextItem)
{
hItem = pCtrl->GetParentItem(hItem);
}
}
hItem = hNextItem;
return *this;
}
TreeItemIterator operator++(int) {TreeItemIterator old = *this; ++(*this); return old; }
CTreeCtrl* pCtrl;
HTREEITEM hItem;
};
class NonRecursiveTreeItemIteratorTraits
{
public:
bool RecurseToChildren(HTREEITEM hItem) {return false; }
};
typedef TreeItemIterator<NonRecursiveTreeItemIteratorTraits> NonRecursiveTreeItemIterator;
class RecursiveTreeItemIteratorTraits
{
public:
bool RecurseToChildren(HTREEITEM hItem) {return true; }
};
typedef TreeItemIterator<RecursiveTreeItemIteratorTraits> RecursiveTreeItemIterator;
inline RecursiveTreeItemIterator BeginTreeItemsRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
if (hItem == 0)
{
hItem = (pCtrl ? pCtrl->GetRootItem() : 0);
}
return RecursiveTreeItemIterator(pCtrl, hItem);
}
inline RecursiveTreeItemIterator EndTreeItemsRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
HTREEITEM hEndItem = 0;
HTREEITEM hParent = hItem;
do
{
if (hParent)
{
hEndItem = pCtrl->GetNextSiblingItem(hParent);
}
hParent = (pCtrl && hParent ? pCtrl->GetParentItem(hParent) : 0);
}
while (hParent && !hEndItem);
return RecursiveTreeItemIterator(pCtrl, hEndItem);
}
inline NonRecursiveTreeItemIterator BeginTreeItemsNonRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
if (hItem == 0)
{
hItem = (pCtrl ? pCtrl->GetRootItem() : 0);
}
if (hItem)
{
hItem = pCtrl->GetChildItem(hItem);
}
return NonRecursiveTreeItemIterator(pCtrl, hItem);
}
inline NonRecursiveTreeItemIterator EndTreeItemsNonRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
HTREEITEM hEndItem = 0;
HTREEITEM hParent = 0;
while (hParent && !hEndItem)
{
hParent = (pCtrl && hItem ? pCtrl->GetParentItem(hItem) : 0);
if (hParent)
{
hEndItem = pCtrl->GetNextSiblingItem(hParent);
}
}
return NonRecursiveTreeItemIterator(pCtrl, hEndItem);
}
template <typename T, typename P>
class TreeItemDataIterator
{
public:
typedef T Type;
typedef TreeItemIterator<P> InternalIterator;
//iterator traits, required by STL
typedef ptrdiff_t difference_type;
typedef Type* value_type;
typedef Type** pointer;
typedef Type*& reference;
typedef std::forward_iterator_tag iterator_category;
TreeItemDataIterator() {}
TreeItemDataIterator(const TreeItemDataIterator& other)
: iterator(other.iterator) {AdvanceToValidIterator(); }
explicit TreeItemDataIterator(const InternalIterator& iterator)
: iterator(iterator) {AdvanceToValidIterator(); }
Type* operator*() {return reinterpret_cast<Type*>(iterator.pCtrl->GetItemData(iterator.hItem)); }
bool operator==(const TreeItemDataIterator& other) const {return iterator == other.iterator; }
bool operator!=(const TreeItemDataIterator& other) const {return iterator != other.iterator; }
HTREEITEM GetTreeItem() {return iterator.hItem; }
TreeItemDataIterator& operator++()
{
++iterator;
AdvanceToValidIterator();
return *this;
}
TreeItemDataIterator operator++(int) {TreeItemDataIterator old = *this; ++(*this); return old; }
private:
void AdvanceToValidIterator()
{
while (iterator.pCtrl && iterator.hItem && !iterator.pCtrl->GetItemData(iterator.hItem))
{
++iterator;
}
}
InternalIterator iterator;
};
template <typename T>
class RecursiveItemDataIteratorType
{
public: typedef TreeItemDataIterator<T, RecursiveTreeItemIteratorTraits> type;
};
template <typename T>
inline TreeItemDataIterator<T, RecursiveTreeItemIteratorTraits> BeginTreeItemDataRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
return TreeItemDataIterator<T, RecursiveTreeItemIteratorTraits>(BeginTreeItemsRecursive(pCtrl, hItem));
}
template <typename T>
inline TreeItemDataIterator<T, RecursiveTreeItemIteratorTraits> EndTreeItemDataRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
return TreeItemDataIterator<T, RecursiveTreeItemIteratorTraits>(EndTreeItemsRecursive(pCtrl, hItem));
}
template <typename T>
class NonRecursiveItemDataIteratorType
{
typedef TreeItemDataIterator<T, NonRecursiveTreeItemIteratorTraits> type;
};
template <typename T>
inline TreeItemDataIterator<T, NonRecursiveTreeItemIteratorTraits> BeginTreeItemDataNonRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
return TreeItemDataIterator<T, NonRecursiveTreeItemIteratorTraits>(BeginTreeItemsNonRecursive(pCtrl, hItem));
}
template <typename T>
inline TreeItemDataIterator<T, NonRecursiveTreeItemIteratorTraits> EndTreeItemDataNonRecursive(CTreeCtrl* pCtrl, HTREEITEM hItem = 0)
{
return TreeItemDataIterator<T, NonRecursiveTreeItemIteratorTraits>(EndTreeItemsNonRecursive(pCtrl, hItem));
}
class SelectedTreeItemIterator
{
public:
SelectedTreeItemIterator()
: pCtrl(0)
, hItem(0) {}
SelectedTreeItemIterator(const SelectedTreeItemIterator& other)
: pCtrl(other.pCtrl)
, hItem(other.hItem) {}
SelectedTreeItemIterator(CXTTreeCtrl* pCtrl, HTREEITEM hItem)
: pCtrl(pCtrl)
, hItem(hItem) {}
HTREEITEM operator*() {return hItem; }
bool operator==(const SelectedTreeItemIterator& other) const {return pCtrl == other.pCtrl && hItem == other.hItem; }
bool operator!=(const SelectedTreeItemIterator& other) const {return pCtrl != other.pCtrl || hItem != other.hItem; }
SelectedTreeItemIterator& operator++()
{
hItem = (pCtrl ? pCtrl->GetNextSelectedItem(hItem) : 0);
return *this;
}
SelectedTreeItemIterator operator++(int) {SelectedTreeItemIterator old = *this; ++(*this); return old; }
CXTTreeCtrl* pCtrl;
HTREEITEM hItem;
};
SelectedTreeItemIterator BeginSelectedTreeItems(CXTTreeCtrl* pCtrl)
{
return SelectedTreeItemIterator(pCtrl, (pCtrl ? pCtrl->GetFirstSelectedItem() : 0));
}
SelectedTreeItemIterator EndSelectedTreeItems(CXTTreeCtrl* pCtrl)
{
return SelectedTreeItemIterator(pCtrl, 0);
}
template <typename T>
class SelectedTreeItemDataIterator
{
public:
typedef T Type;
typedef SelectedTreeItemIterator InternalIterator;
SelectedTreeItemDataIterator() {}
SelectedTreeItemDataIterator(const SelectedTreeItemDataIterator& other)
: iterator(other.iterator) {AdvanceToValidIterator(); }
explicit SelectedTreeItemDataIterator(const InternalIterator& iterator)
: iterator(iterator) {AdvanceToValidIterator(); }
Type* operator*() {return reinterpret_cast<Type*>(iterator.pCtrl->GetItemData(iterator.hItem)); }
bool operator==(const SelectedTreeItemDataIterator& other) const {return iterator == other.iterator; }
bool operator!=(const SelectedTreeItemDataIterator& other) const {return iterator != other.iterator; }
HTREEITEM GetTreeItem() {return iterator.hItem; }
SelectedTreeItemDataIterator& operator++()
{
++iterator;
AdvanceToValidIterator();
return *this;
}
SelectedTreeItemDataIterator operator++(int) {SelectedTreeItemDataIterator old = *this; ++(*this); return old; }
private:
void AdvanceToValidIterator()
{
while (iterator.pCtrl && iterator.hItem && !iterator.pCtrl->GetItemData(iterator.hItem))
{
++iterator;
}
}
InternalIterator iterator;
};
template <typename T>
SelectedTreeItemDataIterator<T> BeginSelectedTreeItemData(CXTTreeCtrl* pCtrl)
{
return SelectedTreeItemDataIterator<T>(BeginSelectedTreeItems(pCtrl));
}
template <typename T>
SelectedTreeItemDataIterator<T> EndSelectedTreeItemData(CXTTreeCtrl* pCtrl)
{
return SelectedTreeItemDataIterator<T>(EndSelectedTreeItems(pCtrl));
}
}
#endif // CRYINCLUDE_EDITOR_CONTROLS_TREECTRLUTILS_H
+12 -4
View File
@@ -104,6 +104,11 @@ namespace
}
}
// Currently (December 13, 2021), this function is only used by slice editor code.
// When the slice editor is not enabled, there are no references to the
// HideActionWhileEntitiesDeselected function, causing a compiler warning and
// subsequently a build error.
#ifdef ENABLE_SLICE_EDITOR
void HideActionWhileEntitiesDeselected(QAction* action, EEditorNotifyEvent editorNotifyEvent)
{
if (action == nullptr)
@@ -127,6 +132,7 @@ namespace
break;
}
}
#endif
void DisableActionWhileInSimMode(QAction* action, EEditorNotifyEvent editorNotifyEvent)
{
@@ -374,7 +380,6 @@ QMenu* LevelEditorMenuHandler::CreateFileMenu()
{
DisableActionWhileLevelChanges(fileOpenSlice, e);
}));
#endif
// Save Selected Slice
auto saveSelectedSlice = fileMenu.AddAction(ID_FILE_SAVE_SELECTED_SLICE);
@@ -391,7 +396,7 @@ QMenu* LevelEditorMenuHandler::CreateFileMenu()
{
HideActionWhileEntitiesDeselected(saveSliceToRoot, e);
}));
#endif
// Open Recent
m_mostRecentLevelsMenu = fileMenu.AddMenu(tr("Open Recent"));
connect(m_mostRecentLevelsMenu, &QMenu::aboutToShow, this, &LevelEditorMenuHandler::UpdateMRUFiles);
@@ -439,9 +444,10 @@ QMenu* LevelEditorMenuHandler::CreateFileMenu()
// Show Log File
fileMenu.AddAction(ID_FILE_EDITLOGFILE);
#ifdef ENABLE_SLICE_EDITOR
fileMenu.AddSeparator();
fileMenu.AddAction(ID_FILE_RESAVESLICES);
#endif
fileMenu.AddSeparator();
@@ -538,6 +544,7 @@ void LevelEditorMenuHandler::PopulateEditMenu(ActionManager::MenuWrapper& editMe
auto snapMenu = modifyMenu.AddMenu(tr("Snap"));
snapMenu.AddAction(AzToolsFramework::SnapAngle);
snapMenu.AddAction(AzToolsFramework::SnapToGrid);
auto transformModeMenu = modifyMenu.AddMenu(tr("Transform Mode"));
transformModeMenu.AddAction(AzToolsFramework::EditModeMove);
@@ -723,7 +730,8 @@ QMenu* LevelEditorMenuHandler::CreateViewMenu()
// MISSING AVIRECORDER
viewportViewsMenuWrapper.AddSeparator();
viewportViewsMenuWrapper.AddAction(ID_DISPLAY_SHOWHELPERS);
viewportViewsMenuWrapper.AddAction(AzToolsFramework::Helpers);
viewportViewsMenuWrapper.AddAction(AzToolsFramework::Icons);
// Refresh Style
viewMenu.AddAction(ID_SKINS_REFRESH);
+3 -8
View File
@@ -380,13 +380,13 @@ void CCryEditApp::RegisterActionHandlers()
ON_COMMAND(ID_IMPORT_ASSET, OnOpenAssetImporter)
ON_COMMAND(ID_EDIT_LEVELDATA, OnEditLevelData)
ON_COMMAND(ID_FILE_EDITLOGFILE, OnFileEditLogFile)
ON_COMMAND(ID_FILE_RESAVESLICES, OnFileResaveSlices)
ON_COMMAND(ID_FILE_EDITEDITORINI, OnFileEditEditorini)
ON_COMMAND(ID_PREFERENCES, OnPreferences)
ON_COMMAND(ID_REDO, OnRedo)
ON_COMMAND(ID_TOOLBAR_WIDGET_REDO, OnRedo)
ON_COMMAND(ID_FILE_OPEN_LEVEL, OnOpenLevel)
#ifdef ENABLE_SLICE_EDITOR
ON_COMMAND(ID_FILE_RESAVESLICES, OnFileResaveSlices)
ON_COMMAND(ID_FILE_NEW_SLICE, OnCreateSlice)
ON_COMMAND(ID_FILE_OPEN_SLICE, OnOpenSlice)
#endif
@@ -445,7 +445,6 @@ void CCryEditApp::RegisterActionHandlers()
ON_COMMAND(ID_OPEN_ASSET_BROWSER, OnOpenAssetBrowserView)
ON_COMMAND(ID_OPEN_AUDIO_CONTROLS_BROWSER, OnOpenAudioControlsEditor)
ON_COMMAND(ID_DISPLAY_SHOWHELPERS, OnShowHelpers)
ON_COMMAND(ID_OPEN_TRACKVIEW, OnOpenTrackView)
ON_COMMAND(ID_OPEN_UICANVASEDITOR, OnOpenUICanvasEditor)
@@ -2617,12 +2616,6 @@ void CCryEditApp::OnUpdateSelected(QAction* action)
action->setEnabled(!GetIEditor()->GetSelection()->IsEmpty());
}
void CCryEditApp::OnShowHelpers()
{
GetIEditor()->GetDisplaySettings()->DisplayHelpers(!GetIEditor()->GetDisplaySettings()->IsDisplayHelpers());
GetIEditor()->Notify(eNotify_OnDisplayRenderUpdate);
}
//////////////////////////////////////////////////////////////////////////
void CCryEditApp::OnEditLevelData()
{
@@ -2636,6 +2629,7 @@ void CCryEditApp::OnFileEditLogFile()
CFileUtil::EditTextFile(CLogFile::GetLogFileName(), 0, IFileUtil::FILE_TYPE_SCRIPT);
}
#ifdef ENABLE_SLICE_EDITOR
void CCryEditApp::OnFileResaveSlices()
{
AZStd::vector<AZ::Data::AssetInfo> sliceAssetInfos;
@@ -2766,6 +2760,7 @@ void CCryEditApp::OnFileResaveSlices()
}
}
#endif
//////////////////////////////////////////////////////////////////////////
void CCryEditApp::OnFileEditEditorini()
-1
View File
@@ -237,7 +237,6 @@ public:
void OnSyncPlayerUpdate(QAction* action);
void OnResourcesReduceworkingset();
void OnDummyCommand() {};
void OnShowHelpers();
void OnFileSave();
void OnUpdateDocumentReady(QAction* action);
void OnUpdateFileOpen(QAction* action);
-2
View File
@@ -68,8 +68,6 @@ void CDisplaySettings::SetObjectHideMask(int hideMask)
m_objectHideMask = hideMask;
gSettings.objectHideMask = m_objectHideMask;
GetIEditor()->Notify(eNotify_OnDisplayRenderUpdate);
};
//////////////////////////////////////////////////////////////////////////
-542
View File
@@ -1,542 +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 "EditorPanelUtils.h"
#include <AzCore/Utils/Utils.h>
// Qt
#include <QInputDialog>
#include <QFileDialog>
#include <QXmlStreamReader>
// Editor
#include "IEditorPanelUtils.h"
#include "Objects/EntityObject.h"
#include "CryEditDoc.h"
#include "ViewManager.h"
#include "Controls/QToolTipWidget.h"
#include "Objects/SelectionGroup.h"
#ifndef PI
#define PI 3.14159265358979323f
#endif
struct ToolTip
{
bool isValid;
QString title;
QString content;
QString specialContent;
QString disabledContent;
};
// internal implementation for better compile times - should also never be used externally, use IParticleEditorUtils interface for that.
class CEditorPanelUtils_Impl
: public IEditorPanelUtils
{
public:
void SetViewportDragOperation(void(* dropCallback)(CViewport* viewport, int dragPointX, int dragPointY, void* custom), void* custom) override
{
for (int i = 0; i < GetIEditor()->GetViewManager()->GetViewCount(); i++)
{
GetIEditor()->GetViewManager()->GetView(i)->SetGlobalDropCallback(dropCallback, custom);
}
}
public:
int PreviewWindow_GetDisplaySettingsDebugFlags(CDisplaySettings* settings) override
{
CRY_ASSERT(settings);
return settings->GetDebugFlags();
}
void PreviewWindow_SetDisplaySettingsDebugFlags(CDisplaySettings* settings, int flags) override
{
CRY_ASSERT(settings);
settings->SetDebugFlags(flags);
}
protected:
QVector<HotKey> hotkeys;
bool m_hotkeysAreEnabled;
public:
bool HotKey_Import() override
{
QVector<QPair<QString, QString> > keys;
QString filepath = QFileDialog::getOpenFileName(nullptr, "Select shortcut configuration to load",
QString(), "HotKey Config Files (*.hkxml)");
QFile file(filepath);
if (!file.open(QIODevice::ReadOnly))
{
return false;
}
QXmlStreamReader stream(&file);
bool result = true;
while (!stream.isEndDocument())
{
if (stream.isStartElement())
{
if (stream.name() == "HotKey")
{
QPair<QString, QString> key;
QXmlStreamAttributes att = stream.attributes();
for (QXmlStreamAttribute attr : att)
{
if (attr.name().compare(QLatin1String("path"), Qt::CaseInsensitive) == 0)
{
key.first = attr.value().toString();
}
if (attr.name().compare(QLatin1String("sequence"), Qt::CaseInsensitive) == 0)
{
key.second = attr.value().toString();
}
}
if (!key.first.isEmpty())
{
keys.push_back(key); // we allow blank key sequences for unassigned shortcuts
}
else
{
result = false; //but not blank paths!
}
}
}
stream.readNext();
}
file.close();
if (result)
{
HotKey_BuildDefaults();
for (QPair<QString, QString> key : keys)
{
for (int j = 0; j < hotkeys.count(); j++)
{
if (hotkeys[j].path.compare(key.first, Qt::CaseInsensitive) == 0)
{
hotkeys[j].SetPath(key.first.toStdString().c_str());
hotkeys[j].SetSequenceFromString(key.second.toStdString().c_str());
}
}
}
}
return result;
}
void HotKey_Export() override
{
auto settingDir = AZ::IO::FixedMaxPath(AZ::Utils::GetEnginePath()) / "Editor" / "Plugins" / "ParticleEditorPlugin" / "settings";
QString filepath = QFileDialog::getSaveFileName(nullptr, "Select shortcut configuration to load", settingDir.c_str(), "HotKey Config Files (*.hkxml)");
QFile file(filepath);
if (!file.open(QIODevice::WriteOnly))
{
return;
}
QXmlStreamWriter stream(&file);
stream.setAutoFormatting(true);
stream.writeStartDocument();
stream.writeStartElement("HotKeys");
for (HotKey key : hotkeys)
{
stream.writeStartElement("HotKey");
stream.writeAttribute("path", key.path);
stream.writeAttribute("sequence", key.sequence.toString());
stream.writeEndElement();
}
stream.writeEndElement();
stream.writeEndDocument();
file.close();
}
QKeySequence HotKey_GetShortcut(const char* path) override
{
for (HotKey combo : hotkeys)
{
if (combo.IsMatch(path))
{
return combo.sequence;
}
}
return QKeySequence();
}
bool HotKey_IsPressed(const QKeyEvent* event, const char* path) override
{
if (!m_hotkeysAreEnabled)
{
return false;
}
unsigned int keyInt = 0;
//Capture any modifiers
Qt::KeyboardModifiers modifiers = QApplication::keyboardModifiers();
if (modifiers & Qt::ShiftModifier)
{
keyInt += Qt::SHIFT;
}
if (modifiers & Qt::ControlModifier)
{
keyInt += Qt::CTRL;
}
if (modifiers & Qt::AltModifier)
{
keyInt += Qt::ALT;
}
if (modifiers & Qt::MetaModifier)
{
keyInt += Qt::META;
}
//Capture any key
keyInt += event->key();
QString t0 = QKeySequence(keyInt).toString();
QString t1 = HotKey_GetShortcut(path).toString();
//if strings match then shortcut is pressed
if (t1.compare(t0, Qt::CaseInsensitive) == 0)
{
return true;
}
return false;
}
bool HotKey_IsPressed(const QShortcutEvent* event, const char* path) override
{
if (!m_hotkeysAreEnabled)
{
return false;
}
QString t0 = event->key().toString();
QString t1 = HotKey_GetShortcut(path).toString();
//if strings match then shortcut is pressed
if (t1.compare(t0, Qt::CaseInsensitive) == 0)
{
return true;
}
return false;
}
bool HotKey_LoadExisting() override
{
QSettings settings("O3DE", "O3DE");
QString group = "Hotkeys/";
HotKey_BuildDefaults();
int size = settings.beginReadArray(group);
for (int i = 0; i < size; i++)
{
settings.setArrayIndex(i);
QPair<QString, QString> hotkey;
hotkey.first = settings.value("name").toString();
hotkey.second = settings.value("keySequence").toString();
if (!hotkey.first.isEmpty())
{
for (int j = 0; j < hotkeys.count(); j++)
{
if (hotkeys[j].path.compare(hotkey.first, Qt::CaseInsensitive) == 0)
{
hotkeys[j].SetPath(hotkey.first.toStdString().c_str());
hotkeys[j].SetSequenceFromString(hotkey.second.toStdString().c_str());
}
}
}
}
settings.endArray();
if (hotkeys.isEmpty())
{
return false;
}
return true;
}
void HotKey_SaveCurrent() override
{
QSettings settings("O3DE", "O3DE");
QString group = "Hotkeys/";
settings.remove("Hotkeys/");
settings.sync();
settings.beginWriteArray(group);
int saveIndex = 0;
for (HotKey key : hotkeys)
{
if (!key.path.isEmpty())
{
settings.setArrayIndex(saveIndex++);
settings.setValue("name", key.path);
settings.setValue("keySequence", key.sequence.toString());
}
}
settings.endArray();
settings.sync();
}
void HotKey_BuildDefaults() override
{
m_hotkeysAreEnabled = true;
QVector<QPair<QString, QString> > keys;
while (hotkeys.count() > 0)
{
hotkeys.takeAt(0);
}
//MENU SELECTION SHORTCUTS////////////////////////////////////////////////
keys.push_back(QPair<QString, QString>("Menus.File Menu", "Alt+F"));
keys.push_back(QPair<QString, QString>("Menus.Edit Menu", "Alt+E"));
keys.push_back(QPair<QString, QString>("Menus.View Menu", "Alt+V"));
//FILE MENU SHORTCUTS/////////////////////////////////////////////////////
keys.push_back(QPair<QString, QString>("File Menu.Create new emitter", "Ctrl+N"));
keys.push_back(QPair<QString, QString>("File Menu.Create new library", "Ctrl+Shift+N"));
keys.push_back(QPair<QString, QString>("File Menu.Create new folder", ""));
keys.push_back(QPair<QString, QString>("File Menu.Import", "Ctrl+I"));
keys.push_back(QPair<QString, QString>("File Menu.Import level library", "Ctrl+Shift+I"));
keys.push_back(QPair<QString, QString>("File Menu.Save", "Ctrl+S"));
keys.push_back(QPair<QString, QString>("File Menu.Close", "Ctrl+Q"));
//EDIT MENU SHORTCUTS/////////////////////////////////////////////////////
keys.push_back(QPair<QString, QString>("Edit Menu.Copy", "Ctrl+C"));
keys.push_back(QPair<QString, QString>("Edit Menu.Paste", "Ctrl+V"));
keys.push_back(QPair<QString, QString>("Edit Menu.Duplicate", "Ctrl+D"));
keys.push_back(QPair<QString, QString>("Edit Menu.Undo", "Ctrl+Z"));
keys.push_back(QPair<QString, QString>("Edit Menu.Redo", "Ctrl+Shift+Z"));
keys.push_back(QPair<QString, QString>("Edit Menu.Group", "Ctrl+G"));
keys.push_back(QPair<QString, QString>("Edit Menu.Ungroup", "Ctrl+Shift+G"));
keys.push_back(QPair<QString, QString>("Edit Menu.Rename", "Ctrl+R"));
keys.push_back(QPair<QString, QString>("Edit Menu.Reset", ""));
keys.push_back(QPair<QString, QString>("Edit Menu.Edit Hotkeys", ""));
keys.push_back(QPair<QString, QString>("Edit Menu.Assign to selected", "Ctrl+Space"));
keys.push_back(QPair<QString, QString>("Edit Menu.Insert Comment", "Ctrl+Alt+M"));
keys.push_back(QPair<QString, QString>("Edit Menu.Enable/Disable Emitter", "Ctrl+E"));
keys.push_back(QPair<QString, QString>("File Menu.Enable All", ""));
keys.push_back(QPair<QString, QString>("File Menu.Disable All", ""));
keys.push_back(QPair<QString, QString>("Edit Menu.Delete", "Del"));
//VIEW MENU SHORTCUTS/////////////////////////////////////////////////////
keys.push_back(QPair<QString, QString>("View Menu.Reset Layout", ""));
//PLAYBACK CONTROL////////////////////////////////////////////////////////
keys.push_back(QPair<QString, QString>("Previewer.Play/Pause Toggle", "Space"));
keys.push_back(QPair<QString, QString>("Previewer.Step forward through time", "c"));
keys.push_back(QPair<QString, QString>("Previewer.Loop Toggle", "z"));
keys.push_back(QPair<QString, QString>("Previewer.Reset Playback", "x"));
keys.push_back(QPair<QString, QString>("Previewer.Focus", "Ctrl+F"));
keys.push_back(QPair<QString, QString>("Previewer.Zoom In", "w"));
keys.push_back(QPair<QString, QString>("Previewer.Zoom Out", "s"));
keys.push_back(QPair<QString, QString>("Previewer.Pan Left", "a"));
keys.push_back(QPair<QString, QString>("Previewer.Pan Right", "d"));
for (QPair<QString, QString> key : keys)
{
unsigned int index = hotkeys.count();
hotkeys.push_back(HotKey());
hotkeys[index].SetPath(key.first.toStdString().c_str());
hotkeys[index].SetSequenceFromString(key.second.toStdString().c_str());
}
}
void HotKey_SetKeys(QVector<HotKey> keys) override
{
hotkeys = keys;
}
QVector<HotKey> HotKey_GetKeys() override
{
return hotkeys;
}
QString HotKey_GetPressedHotkey(const QKeyEvent* event) override
{
if (!m_hotkeysAreEnabled)
{
return "";
}
for (HotKey key : hotkeys)
{
if (HotKey_IsPressed(event, key.path.toUtf8()))
{
return key.path;
}
}
return "";
}
QString HotKey_GetPressedHotkey(const QShortcutEvent* event) override
{
if (!m_hotkeysAreEnabled)
{
return "";
}
for (HotKey key : hotkeys)
{
if (HotKey_IsPressed(event, key.path.toUtf8()))
{
return key.path;
}
}
return "";
}
//building the default hotkey list re-enables hotkeys
//do not use this when rebuilding the default list is a possibility.
void HotKey_SetEnabled(bool val) override
{
m_hotkeysAreEnabled = val;
}
bool HotKey_IsEnabled() const override
{
return m_hotkeysAreEnabled;
}
protected:
QMap<QString, ToolTip> m_tooltips;
void ToolTip_ParseNode(XmlNodeRef node)
{
if (QString(node->getTag()).compare("tooltip", Qt::CaseInsensitive) != 0)
{
unsigned int childCount = node->getChildCount();
for (unsigned int i = 0; i < childCount; i++)
{
ToolTip_ParseNode(node->getChild(i));
}
}
QString title = node->getAttr("title");
QString content = node->getAttr("content");
QString specialContent = node->getAttr("special_content");
QString disabledContent = node->getAttr("disabled_content");
QMap<QString, ToolTip>::iterator itr = m_tooltips.insert(node->getAttr("path"), ToolTip());
itr->isValid = true;
itr->title = title;
itr->content = content;
itr->specialContent = specialContent;
itr->disabledContent = disabledContent;
unsigned int childCount = node->getChildCount();
for (unsigned int i = 0; i < childCount; i++)
{
ToolTip_ParseNode(node->getChild(i));
}
}
ToolTip GetToolTip(QString path)
{
if (m_tooltips.contains(path))
{
return m_tooltips[path];
}
ToolTip temp;
temp.isValid = false;
return temp;
}
public:
void ToolTip_LoadConfigXML(QString filepath) override
{
XmlNodeRef node = GetIEditor()->GetSystem()->LoadXmlFromFile(filepath.toStdString().c_str());
ToolTip_ParseNode(node);
}
void ToolTip_BuildFromConfig(IQToolTip* tooltip, QString path, QString option, QString optionalData = "", bool isEnabled = true) override
{
AZ_Assert(tooltip, "tooltip cannot be null");
QString title = ToolTip_GetTitle(path, option);
QString content = ToolTip_GetContent(path, option);
QString specialContent = ToolTip_GetSpecialContentType(path, option);
QString disabledContent = ToolTip_GetDisabledContent(path, option);
// Even if these items are empty, we set them anyway to clear out any data that was left over from when the tooltip was used for a different object.
tooltip->SetTitle(title);
tooltip->SetContent(content);
//this only handles simple creation...if you need complex call this then add specials separate
if (!specialContent.contains("::"))
{
tooltip->AddSpecialContent(specialContent, optionalData);
}
if (!isEnabled) // If disabled, add disabled value
{
tooltip->AppendContent(disabledContent);
}
}
QString ToolTip_GetTitle(QString path, QString option) override
{
if (!option.isEmpty() && GetToolTip(path + "." + option).isValid)
{
return GetToolTip(path + "." + option).title;
}
if (!option.isEmpty() && GetToolTip("Options." + option).isValid)
{
return GetToolTip("Options." + option).title;
}
return GetToolTip(path).title;
}
QString ToolTip_GetContent(QString path, QString option) override
{
if (!option.isEmpty() && GetToolTip(path + "." + option).isValid)
{
return GetToolTip(path + "." + option).content;
}
if (!option.isEmpty() && GetToolTip("Options." + option).isValid)
{
return GetToolTip("Options." + option).content;
}
return GetToolTip(path).content;
}
QString ToolTip_GetSpecialContentType(QString path, QString option) override
{
if (!option.isEmpty() && GetToolTip(path + "." + option).isValid)
{
return GetToolTip(path + "." + option).specialContent;
}
if (!option.isEmpty() && GetToolTip("Options." + option).isValid)
{
return GetToolTip("Options." + option).specialContent;
}
return GetToolTip(path).specialContent;
}
QString ToolTip_GetDisabledContent(QString path, QString option) override
{
if (!option.isEmpty() && GetToolTip(path + "." + option).isValid)
{
return GetToolTip(path + "." + option).disabledContent;
}
if (!option.isEmpty() && GetToolTip("Options." + option).isValid)
{
return GetToolTip("Options." + option).disabledContent;
}
return GetToolTip(path).disabledContent;
}
};
IEditorPanelUtils* CreateEditorPanelUtils()
{
return new CEditorPanelUtils_Impl();
}
-16
View File
@@ -1,16 +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
*
*/
// Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#ifndef CRYINCLUDE_CRYEDITOR_EDITORPANELUTILS_H
#define CRYINCLUDE_CRYEDITOR_EDITORPANELUTILS_H
#pragma once
struct IEditorPanelUtils;
IEditorPanelUtils* CreateEditorPanelUtils();
#endif
+25
View File
@@ -112,6 +112,31 @@ void EditorPreferencesDialog::showEvent(QShowEvent* event)
QDialog::showEvent(event);
}
void WidgetHandleKeyPressEvent(QWidget* widget, QKeyEvent* event)
{
// If the enter key is pressed during any text input, the dialog box will close
// making it inconvenient to do multiple edits. This routine captures the
// Key_Enter or Key_Return and clears the focus to give a visible cue that
// editing of that field has finished and then doesn't propogate it.
if (event->key() != Qt::Key::Key_Enter && event->key() != Qt::Key::Key_Return)
{
QApplication::sendEvent(widget, event);
}
else
{
if (QWidget* editWidget = QApplication::focusWidget())
{
editWidget->clearFocus();
}
}
}
void EditorPreferencesDialog::keyPressEvent(QKeyEvent* event)
{
WidgetHandleKeyPressEvent(this, event);
}
void EditorPreferencesDialog::OnTreeCurrentItemChanged()
{
QTreeWidgetItem* currentItem = ui->pageTree->currentItem();
+3
View File
@@ -19,6 +19,8 @@ namespace Ui
class EditorPreferencesTreeWidgetItem;
void WidgetHandleKeyPressEvent(QWidget* widget, QKeyEvent* event);
class EditorPreferencesDialog
: public QDialog
, public AzToolsFramework::IPropertyEditorNotify
@@ -36,6 +38,7 @@ public:
protected:
void showEvent(QShowEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
private:
void CreateImages();
+13 -8
View File
@@ -46,6 +46,7 @@
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerInterface.h>
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/Viewport/ViewportSettings.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
@@ -665,13 +666,7 @@ void EditorViewportWidget::OnBeginPrepareRender()
RenderAll();
// Draw 2D helpers.
#ifdef LYSHINE_ATOM_TODO
TransformationMatrices backupSceneMatrices;
#endif
m_debugDisplay->DepthTestOff();
#ifdef LYSHINE_ATOM_TODO
m_renderer->Set2DMode(m_rcClient.right(), m_rcClient.bottom(), backupSceneMatrices);
#endif
auto prevState = m_debugDisplay->GetState();
m_debugDisplay->SetState(e_Mode3D | e_AlphaBlended | e_FillModeSolid | e_CullModeBack | e_DepthWriteOn | e_DepthTestOn);
@@ -1627,14 +1622,14 @@ Vec3 EditorViewportWidget::ViewToWorld(
auto ray = m_renderViewport->ViewportScreenToWorldRay(AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(vp));
const float maxDistance = 10000.f;
Vec3 v = AZVec3ToLYVec3(ray.direction) * maxDistance;
Vec3 v = AZVec3ToLYVec3(ray.m_direction) * maxDistance;
if (!_finite(v.x) || !_finite(v.y) || !_finite(v.z))
{
return Vec3(0, 0, 0);
}
Vec3 colp = AZVec3ToLYVec3(ray.origin) + 0.002f * v;
Vec3 colp = AZVec3ToLYVec3(ray.m_origin) + 0.002f * v;
return colp;
}
@@ -2426,6 +2421,16 @@ AZ::Vector3 EditorViewportSettings::DefaultEditorCameraPosition() const
return SandboxEditor::CameraDefaultEditorPosition();
}
bool EditorViewportSettings::IconsVisible() const
{
return AzToolsFramework::IconsVisible();
}
bool EditorViewportSettings::HelpersVisible() const
{
return AzToolsFramework::HelpersVisible();
}
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
bool EditorViewportWidget::ShouldPreviewFullscreen() const
+2
View File
@@ -79,6 +79,8 @@ struct EditorViewportSettings : public AzToolsFramework::ViewportInteraction::Vi
float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
bool IconsVisible() const override;
bool HelpersVisible() const override;
};
// EditorViewportWidget window
+2 -2
View File
@@ -442,7 +442,7 @@ AZ::Outcome<void, AZStd::string> CGameEngine::Init(
REGISTER_COMMAND("quit", CGameEngine::HandleQuitRequest, VF_RESTRICTEDMODE, "Quit/Shutdown the engine");
EBUS_EVENT(CrySystemEventBus, OnCryEditorInitialized);
return AZ::Success();
}
@@ -465,7 +465,7 @@ void CGameEngine::SetLevelPath(const QString& path)
const char* oldExtension = EditorUtils::LevelFile::GetOldCryFileExtension();
const char* defaultExtension = EditorUtils::LevelFile::GetDefaultFileExtension();
// Store off if
// Store off if
if (QFileInfo(path + oldExtension).exists())
{
m_levelExtension = oldExtension;
-5
View File
@@ -69,7 +69,6 @@ class CSelectionTreeManager;
struct SEditorSettings;
class CGameExporter;
class IAWSResourceManager;
struct IEditorPanelUtils;
namespace WinWidget
{
@@ -168,8 +167,6 @@ enum EEditorNotifyEvent
eNotify_OnVegetationObjectSelection, // When vegetation objects selection change.
eNotify_OnVegetationPanelUpdate, // When vegetation objects selection change.
eNotify_OnDisplayRenderUpdate, // Sent when editor finish terrain texture generation.
eNotify_OnDataBaseUpdate, // DataBase Library was modified.
eNotify_OnLayerImportBegin, //layer import was started
@@ -528,8 +525,6 @@ struct IEditor
virtual IEditorMaterialManager* GetIEditorMaterialManager() = 0; // Vladimir@Conffx
//! Returns IconManager.
virtual IIconManager* GetIconManager() = 0;
//! Get Panel Editor Utilities
virtual IEditorPanelUtils* GetEditorPanelUtils() = 0;
//! Get Music Manager.
virtual CMusicManager* GetMusicManager() = 0;
virtual float GetTerrainElevation(float x, float y) = 0;
+1 -14
View File
@@ -81,9 +81,6 @@ AZ_POP_DISABLE_WARNING
// AWSNativeSDK
#include <AWSNativeSDKInit/AWSNativeSDKInit.h>
#include "IEditorPanelUtils.h"
#include "EditorPanelUtils.h"
#include "Core/QtEditorApplication.h" // for Editor::EditorQtApplication
static CCryEditDoc * theDocument;
@@ -143,7 +140,6 @@ CEditorImpl::CEditorImpl()
, m_QtApplication(static_cast<Editor::EditorQtApplication*>(qApp))
, m_pImageUtil(nullptr)
, m_pLogFile(nullptr)
, m_panelEditorUtils(nullptr)
{
// note that this is a call into EditorCore.dll, which stores the g_pEditorPointer for all shared modules that share EditorCore.dll
// this means that they don't need to do SetIEditor(...) themselves and its available immediately
@@ -167,8 +163,6 @@ CEditorImpl::CEditorImpl()
m_pDisplaySettings->LoadRegistry();
m_pPluginManager = new CPluginManager;
m_panelEditorUtils = CreateEditorPanelUtils();
m_pObjectManager = new CObjectManager;
m_pViewManager = new CViewManager;
m_pIconManager = new CIconManager;
@@ -301,8 +295,6 @@ CEditorImpl::~CEditorImpl()
SAFE_DELETE(m_pViewManager)
SAFE_DELETE(m_pObjectManager) // relies on prefab manager
SAFE_DELETE(m_panelEditorUtils);
// some plugins may be exporter - this must be above plugin manager delete.
SAFE_DELETE(m_pExportManager);
@@ -1445,7 +1437,7 @@ ISourceControl* CEditorImpl::GetSourceControl()
{
IClassDesc* pClass = classes[i];
ISourceControl* pSCM = nullptr;
HRESULT hRes = pClass->QueryInterface(__uuidof(ISourceControl), (void**)&pSCM);
HRESULT hRes = pClass->QueryInterface(__az_uuidof(ISourceControl), (void**)&pSCM);
if (!FAILED(hRes) && pSCM)
{
m_pSourceControl = pSCM;
@@ -1658,8 +1650,3 @@ void CEditorImpl::DestroyQMimeData(QMimeData* data) const
{
delete data;
}
IEditorPanelUtils* CEditorImpl::GetEditorPanelUtils()
{
return m_panelEditorUtils;
}
-2
View File
@@ -298,7 +298,6 @@ public:
IEditorMaterialManager* GetIEditorMaterialManager() override; // Vladimir@Conffx
IImageUtil* GetImageUtil() override; // Vladimir@conffx
SEditorSettings* GetEditorSettings() override;
IEditorPanelUtils* GetEditorPanelUtils() override;
ILogFile* GetLogFile() override { return m_pLogFile; }
void UnloadPlugins() override;
@@ -356,7 +355,6 @@ protected:
CErrorsDlg* m_pErrorsDlg;
//! Source control interface.
ISourceControl* m_pSourceControl;
IEditorPanelUtils* m_panelEditorUtils;
CSelectionTreeManager* m_pSelectionTreeManager;
-131
View File
@@ -1,131 +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_CRYEDITOR_IPANELEDITORUTILS_H
#define CRYINCLUDE_CRYEDITOR_IPANELEDITORUTILS_H
#pragma once
#include "Cry_Vector3.h"
#include "DisplaySettings.h"
#include "Include/IDisplayViewport.h"
#include "Include/IIconManager.h"
#include <qstringlist.h>
#include <qkeysequence.h>
#include <qevent.h>
#include <QShortcutEvent>
class CBaseObject;
class CViewport;
class IQToolTip;
struct HotKey
{
HotKey()
: path("")
, sequence(QKeySequence())
{
}
void CopyFrom(const HotKey& other)
{
path = other.path;
sequence = other.sequence;
}
void SetPath(const char* _path)
{
path = QString(_path);
}
void SetSequenceFromString(const char* _sequence)
{
sequence = QKeySequence::fromString(_sequence);
}
void SetSequence(const QKeySequence& other)
{
sequence = other;
}
bool IsMatch(QString _path)
{
return path.compare(_path, Qt::CaseInsensitive) == 0;
}
bool IsMatch(QKeySequence _sequence)
{
return sequence.matches(_sequence);
}
bool operator < (const HotKey& other) const
{
//split the paths into lists compare per level
QStringList m_categories = path.split('.');
QStringList o_categories = other.path.split('.');
int m_catSize = m_categories.size();
int o_catSize = o_categories.size();
int size = (m_catSize < o_catSize) ? m_catSize : o_catSize;
//sort categories to keep them together
for (int i = 0; i < size; i++)
{
if (m_categories[i] < o_categories[i])
{
return true;
}
if (m_categories[i] > o_categories[i])
{
return false;
}
}
//if comparing a category and a item in that category the category is < item
return m_catSize > o_catSize;
}
QKeySequence sequence;
QString path;
};
struct IEditorPanelUtils
{
virtual ~IEditorPanelUtils() {}
virtual void SetViewportDragOperation(void(*)(CViewport* viewport, int dragPointX, int dragPointY, void* custom), void* custom) = 0;
//PREVIEW WINDOW UTILS////////////////////////////////////////////////////
virtual int PreviewWindow_GetDisplaySettingsDebugFlags(CDisplaySettings* settings) = 0;
virtual void PreviewWindow_SetDisplaySettingsDebugFlags(CDisplaySettings* settings, int flags) = 0;
//HOTKEY UTILS////////////////////////////////////////////////////////////
virtual bool HotKey_Import() = 0;
virtual void HotKey_Export() = 0;
virtual QKeySequence HotKey_GetShortcut(const char* path) = 0;
virtual bool HotKey_IsPressed(const QKeyEvent* event, const char* path) = 0;
virtual bool HotKey_IsPressed(const QShortcutEvent* event, const char* path) = 0;
virtual bool HotKey_LoadExisting() = 0;
virtual void HotKey_SaveCurrent() = 0;
virtual void HotKey_BuildDefaults() = 0;
virtual void HotKey_SetKeys(QVector<HotKey> keys) = 0;
virtual QVector<HotKey> HotKey_GetKeys() = 0;
virtual QString HotKey_GetPressedHotkey(const QKeyEvent* event) = 0;
virtual QString HotKey_GetPressedHotkey(const QShortcutEvent* event) = 0;
virtual void HotKey_SetEnabled(bool val) = 0;
virtual bool HotKey_IsEnabled() const = 0;
//TOOLTIP UTILS///////////////////////////////////////////////////////////
//! Loads a table of tooltip configuration data from an xml file.
virtual void ToolTip_LoadConfigXML(QString filepath) = 0;
//! Initializes a QToolTipWidget from loaded configuration data (see ToolTip_LoadConfigXML())
//! \param tooltip Will be initialized using loaded configuration data
//! \param path Variable serialization path. Will be used as the key for looking up data in the configuration table. (ex: "Rotation.Rotation_Rate_X")
//! \param option Name of a sub-option of the variable specified by "path". (ex: "Emitter_Strength" will look up the tooltip data for "Rotation.Rotation_Rate_X.Emitter_Strength")
//! \param optionalData The argument to be used with "special_content" feature. See ToolTip_GetSpecialContentType() and QToolTipWidget::AddSpecialContent().
//! \param isEnabled If false, the tooltip will indicate the reason why the widget is disabled.
virtual void ToolTip_BuildFromConfig(IQToolTip* tooltip, QString path, QString option, QString optionalData = "", bool isEnabled = true) = 0;
virtual QString ToolTip_GetTitle(QString path, QString option = "") = 0;
virtual QString ToolTip_GetContent(QString path, QString option = "") = 0;
virtual QString ToolTip_GetSpecialContentType(QString path, QString option = "") = 0;
virtual QString ToolTip_GetDisabledContent(QString path, QString option = "") = 0;
};
#endif
+2 -5
View File
@@ -31,10 +31,7 @@ struct IUnknown
};
#endif
#ifdef __uuidof
#undef __uuidof
#endif
#define __uuidof(T) T::uuid()
#define __az_uuidof(T) T::uuid()
#if defined(AZ_PLATFORM_LINUX) || defined(AZ_PLATFORM_MAC)
@@ -107,7 +104,7 @@ struct IClassDesc
template<class Q>
HRESULT STDMETHODCALLTYPE QueryInterface(Q** pp)
{
return QueryInterface(__uuidof(Q), (void**)pp);
return QueryInterface(__az_uuidof(Q), (void**)pp);
}
//////////////////////////////////////////////////////////////////////////
+1 -1
View File
@@ -60,7 +60,7 @@ struct IViewPaneClass
//////////////////////////////////////////////////////////////////////////
HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObj)
{
if (riid == __uuidof(IViewPaneClass))
if (riid == __az_uuidof(IViewPaneClass))
{
*ppvObj = this;
return S_OK;
-1
View File
@@ -187,6 +187,5 @@ public:
MOCK_METHOD0(UnloadPlugins, void());
MOCK_METHOD0(LoadPlugins, void());
MOCK_METHOD1(GetSearchPath, QString(EEditorPathName));
MOCK_METHOD0(GetEditorPanelUtils, IEditorPanelUtils* ());
};
@@ -1,58 +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 <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <QtUI/ClickableLabel.h>
#include <QApplication>
using namespace AZ;
using namespace ::testing;
namespace UnitTest
{
class TestingClickableLabel
: public ScopedAllocatorSetupFixture
{
public:
ClickableLabel m_clickableLabel;
};
TEST_F(TestingClickableLabel, CursorDoesNotUpdateWhileDisabled)
{
m_clickableLabel.setEnabled(false);
QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
QEnterEvent enterEvent{ QPointF(), QPointF(), QPointF() };
QApplication::sendEvent(&m_clickableLabel, &enterEvent);
const Qt::CursorShape cursorShape = QApplication::overrideCursor()->shape();
EXPECT_THAT(cursorShape, Ne(Qt::PointingHandCursor));
EXPECT_THAT(cursorShape, Eq(Qt::BlankCursor));
}
TEST_F(TestingClickableLabel, DoesNotRespondToDblClickWhileDisabled)
{
m_clickableLabel.setEnabled(false);
bool linkActivated = false;
QObject::connect(&m_clickableLabel, &QLabel::linkActivated, [&linkActivated]()
{
linkActivated = true;
});
QMouseEvent mouseEvent {
QEvent::MouseButtonDblClick, QPointF(),
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier };
QApplication::sendEvent(&m_clickableLabel, &mouseEvent);
EXPECT_THAT(linkActivated, Eq(false));
}
} // namespace UnitTest
@@ -12,6 +12,7 @@
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Application/ToolsApplication.h>
@@ -63,6 +64,11 @@ namespace DisplaySettingsPythonBindingsUnitTests
m_app.Start(appDesc);
m_app.RegisterComponentDescriptor(AzToolsFramework::DisplaySettingsComponent::CreateDescriptor());
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
void TearDown() override
+68 -20
View File
@@ -47,6 +47,7 @@ AZ_POP_DISABLE_WARNING
#include <AzToolsFramework/API/EditorAnimationSystemRequestBus.h>
#include <AzToolsFramework/SourceControl/QtSourceControlNotificationHandler.h>
#include <AzToolsFramework/PythonTerminal/ScriptTermDialog.h>
#include <AzToolsFramework/Viewport/ViewportSettings.h>
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
// AzQtComponents
@@ -445,11 +446,11 @@ void MainWindow::Initialize()
{
m_viewPaneManager->SetMainWindow(m_viewPaneHost, &m_settings, /*unused*/ QByteArray());
InitActions();
RegisterStdViewClasses();
InitCentralWidget();
InitActions();
// load toolbars ("shelves") and macros
GetIEditor()->GetToolBoxManager()->Load(m_actionManager);
@@ -642,11 +643,11 @@ void MainWindow::InitActions()
.SetStatusTip(tr("Create a new slice"));
am->AddAction(ID_FILE_OPEN_SLICE, tr("Open Slice..."))
.SetStatusTip(tr("Open an existing slice"));
#endif
am->AddAction(ID_FILE_SAVE_SELECTED_SLICE, tr("Save selected slice")).SetShortcut(tr("Alt+S"))
.SetStatusTip(tr("Save the selected slice to the first level root"));
am->AddAction(ID_FILE_SAVE_SLICE_TO_ROOT, tr("Save Slice to root")).SetShortcut(tr("Ctrl+Alt+S"))
.SetStatusTip(tr("Save the selected slice to the top level root"));
#endif
am->AddAction(ID_FILE_SAVE_LEVEL, tr("&Save"))
.SetShortcut(tr("Ctrl+S"))
.SetReserved()
@@ -676,7 +677,9 @@ void MainWindow::InitActions()
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateSelected);
am->AddAction(ID_FILE_EXPORTOCCLUSIONMESH, tr("Export Occlusion Mesh"));
am->AddAction(ID_FILE_EDITLOGFILE, tr("Show Log File"));
#ifdef ENABLE_SLICE_EDITOR
am->AddAction(ID_FILE_RESAVESLICES, tr("Resave All Slices"));
#endif
am->AddAction(ID_FILE_PROJECT_MANAGER_SETTINGS, tr("Edit Project Settings..."));
am->AddAction(ID_FILE_PROJECT_MANAGER_NEW, tr("New Project..."));
am->AddAction(ID_FILE_PROJECT_MANAGER_OPEN, tr("Open Project..."));
@@ -798,27 +801,40 @@ void MainWindow::InitActions()
EditorTransformComponentSelectionRequests::Mode::Scale);
});
am->AddAction(AzToolsFramework::SnapToGrid, tr("Snap to grid"))
am->AddAction(AzToolsFramework::SnapToGrid, tr("Grid snapping"))
.SetIcon(Style::icon("Grid"))
.SetStatusTip(tr("Toggle grid snapping"))
.SetShortcut(tr("G"))
.SetToolTip(tr("Snap to grid (G)"))
.SetStatusTip(tr("Toggles snap to grid"))
.SetCheckable(true)
.RegisterUpdateCallback([](QAction* action) {
Q_ASSERT(action->isCheckable());
action->setChecked(SandboxEditor::GridSnappingEnabled());
})
.Connect(&QAction::triggered, []() { SandboxEditor::SetGridSnapping(!SandboxEditor::GridSnappingEnabled()); });
.RegisterUpdateCallback(
[](QAction* action)
{
Q_ASSERT(action->isCheckable());
action->setChecked(SandboxEditor::GridSnappingEnabled());
})
.Connect(
&QAction::triggered,
[]
{
SandboxEditor::SetGridSnapping(!SandboxEditor::GridSnappingEnabled());
});
am->AddAction(AzToolsFramework::SnapAngle, tr("Snap angle"))
am->AddAction(AzToolsFramework::SnapAngle, tr("Angle snapping"))
.SetIcon(Style::icon("Angle"))
.SetStatusTip(tr("Snap angle"))
.SetStatusTip(tr("Toggle angle snapping"))
.SetCheckable(true)
.RegisterUpdateCallback([](QAction* action) {
Q_ASSERT(action->isCheckable());
action->setChecked(SandboxEditor::AngleSnappingEnabled());
})
.Connect(&QAction::triggered, []() { SandboxEditor::SetAngleSnapping(!SandboxEditor::AngleSnappingEnabled()); });
.RegisterUpdateCallback(
[](QAction* action)
{
Q_ASSERT(action->isCheckable());
action->setChecked(SandboxEditor::AngleSnappingEnabled());
})
.Connect(
&QAction::triggered,
[]
{
SandboxEditor::SetAngleSnapping(!SandboxEditor::AngleSnappingEnabled());
});
// Display actions
am->AddAction(ID_SWITCHCAMERA_DEFAULTCAMERA, tr("Default Camera")).SetCheckable(true)
@@ -918,9 +934,41 @@ void MainWindow::InitActions()
.SetStatusTip(tr("Cycle 2D Viewport"))
.RegisterUpdateCallback(cryEdit, &CCryEditApp::OnUpdateNonGameMode);
#endif
am->AddAction(ID_DISPLAY_SHOWHELPERS, tr("Show/Hide Helpers"))
am->AddAction(AzToolsFramework::Helpers, tr("Show Helpers"))
.SetShortcut(tr("Shift+Space"))
.SetToolTip(tr("Show/Hide Helpers (Shift+Space)"));
.SetToolTip(tr("Show/Hide Helpers (Shift+Space)"))
.SetCheckable(true)
.RegisterUpdateCallback(
[](QAction* action)
{
Q_ASSERT(action->isCheckable());
action->setChecked(AzToolsFramework::HelpersVisible());
})
.Connect(
&QAction::triggered,
[]()
{
AzToolsFramework::SetHelpersVisible(!AzToolsFramework::HelpersVisible());
AzToolsFramework::ViewportInteraction::ViewportSettingsNotificationBus::Broadcast(
&AzToolsFramework::ViewportInteraction::ViewportSettingNotifications::OnDrawHelpersChanged,
AzToolsFramework::HelpersVisible());
});
am->AddAction(AzToolsFramework::Icons, tr("Show Icons"))
.SetShortcut(tr("Ctrl+Space"))
.SetToolTip(tr("Show/Hide Icons (Ctrl+Space)"))
.SetCheckable(true)
.RegisterUpdateCallback(
[](QAction* action)
{
Q_ASSERT(action->isCheckable());
action->setChecked(AzToolsFramework::IconsVisible());
})
.Connect(
&QAction::triggered,
[]()
{
AzToolsFramework::SetIconsVisible(!AzToolsFramework::IconsVisible());
});
// Audio actions
am->AddAction(ID_SOUND_STOPALLSOUNDS, tr("Stop All Sounds"))
+1 -1
View File
@@ -155,7 +155,7 @@ IViewPaneClass* CClassFactory::FindViewPaneClassByTitle(const char* pPaneTitle)
{
IViewPaneClass* viewPane = nullptr;
IClassDesc* desc = m_classes[i];
if (SUCCEEDED(desc->QueryInterface(__uuidof(IViewPaneClass), (void**)&viewPane)))
if (SUCCEEDED(desc->QueryInterface(__az_uuidof(IViewPaneClass), (void**)&viewPane)))
{
if (QString::compare(viewPane->GetPaneTitle(), pPaneTitle) == 0)
{
@@ -1986,8 +1986,3 @@ void SandboxIntegrationManager::BrowseForAssets(AssetSelectionModel& selection)
{
AssetBrowserComponentRequestBus::Broadcast(&AssetBrowserComponentRequests::PickAssets, selection, GetMainWindow());
}
bool SandboxIntegrationManager::DisplayHelpersVisible()
{
return GetIEditor()->GetDisplaySettings()->IsDisplayHelpers();
}
@@ -165,7 +165,6 @@ private:
void InstantiateSliceFromAssetId(const AZ::Data::AssetId& assetId) override;
void ClearRedoStack() override;
int GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath) override;
bool DisplayHelpersVisible() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
@@ -1281,6 +1281,8 @@ void OutlinerWidget::OnSearchTextChanged(const QString& activeTextFilter)
m_listModel->SearchStringChanged(filterString);
m_proxyModel->UpdateFilter();
m_gui->m_objectTree->expandAll();
}
void OutlinerWidget::OnFilterChanged(const AzQtComponents::SearchTypeFilterList& activeTypeFilters)
@@ -49,7 +49,7 @@ public:
// from IUnknown
HRESULT STDMETHODCALLTYPE QueryInterface(const IID& riid, void** ppvObj)
{
if (riid == __uuidof(ISourceControl) /* && m_pIntegrator*/)
if (riid == __az_uuidof(ISourceControl) /* && m_pIntegrator*/)
{
*ppvObj = this;
return S_OK;
+1 -1
View File
@@ -50,7 +50,7 @@ CStdPreferencesClassDesc::CStdPreferencesClassDesc()
HRESULT CStdPreferencesClassDesc::QueryInterface(const IID& riid, void** ppvObj)
{
if (riid == __uuidof(IPreferencesPageCreator))
if (riid == __az_uuidof(IPreferencesPageCreator))
{
*ppvObj = (IPreferencesPageCreator*)this;
return S_OK;
-101
View File
@@ -1,101 +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 "ClickableLabel.h"
ClickableLabel::ClickableLabel(const QString& text, QWidget* parent)
: QLabel(parent)
, m_text(text)
, m_showDecoration(false)
{
setTextFormat(Qt::RichText);
setTextInteractionFlags(Qt::TextBrowserInteraction);
}
ClickableLabel::ClickableLabel(QWidget* parent)
: QLabel(parent)
, m_showDecoration(false)
{
setTextFormat(Qt::RichText);
setTextInteractionFlags(Qt::TextBrowserInteraction);
}
void ClickableLabel::showEvent([[maybe_unused]] QShowEvent* event)
{
updateFormatting(false);
}
void ClickableLabel::enterEvent(QEvent* ev)
{
if (!isEnabled())
{
return;
}
updateFormatting(true);
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
QLabel::enterEvent(ev);
}
void ClickableLabel::leaveEvent(QEvent* ev)
{
if (!isEnabled())
{
return;
}
updateFormatting(false);
QApplication::restoreOverrideCursor();
QLabel::leaveEvent(ev);
}
void ClickableLabel::setText(const QString& text)
{
m_text = text;
QLabel::setText(text);
updateFormatting(false);
}
void ClickableLabel::setShowDecoration(bool b)
{
m_showDecoration = b;
updateFormatting(false);
}
void ClickableLabel::updateFormatting(bool mouseOver)
{
//FIXME: this should be done differently. Using a style sheet would be easiest.
QColor c = palette().color(QPalette::WindowText);
if (mouseOver || m_showDecoration)
{
QLabel::setText(QString(R"(<a href="dummy" style="color: %1";>%2</a>)").arg(c.name(), m_text));
}
else
{
QLabel::setText(m_text);
}
}
bool ClickableLabel::event(QEvent* e)
{
if (isEnabled())
{
if (e->type() == QEvent::MouseButtonDblClick)
{
emit linkActivated(QString());
return true; //ignore
}
}
return QLabel::event(e);
}
#include <QtUI/moc_ClickableLabel.cpp>
-39
View File
@@ -1,39 +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
*
*/
#pragma once
#ifndef CRYINCLUDE_EDITORCOMMON_CLICKABLELABEL_H
#define CRYINCLUDE_EDITORCOMMON_CLICKABLELABEL_H
#if !defined(Q_MOC_RUN)
#include <QLabel>
#endif
class SANDBOX_API ClickableLabel
: public QLabel
{
Q_OBJECT
public:
explicit ClickableLabel(const QString& text, QWidget* parent = nullptr);
explicit ClickableLabel(QWidget* parent = nullptr);
bool event(QEvent* e) override;
void setText(const QString& text);
void setShowDecoration(bool b);
protected:
void showEvent(QShowEvent* event) override;
void enterEvent(QEvent* ev) override;
void leaveEvent(QEvent* ev) override;
private:
void updateFormatting(bool mouseOver);
QString m_text;
bool m_showDecoration;
};
#endif
-1
View File
@@ -191,7 +191,6 @@
#define ID_BRUSH_CSGSUBSTRUCT 33837
#define ID_MATERIAL_PICKTOOL 33842
#define ID_MODIFY_AIPOINT_PICKIMPASSLINK 33865
#define ID_DISPLAY_SHOWHELPERS 33871
#define ID_FILE_EXPORTSELECTION 33875
#define ID_EDIT_PASTE_WITH_LINKS 33893
#define ID_FILE_EXPORT_TERRAINAREA 33904
+1 -1
View File
@@ -251,7 +251,7 @@ bool CFileUtil::ExtractDccFilenameFromAssetDatabase(const QString& assetFilename
for (size_t i = 0; i < assetDatabasePlugins.size(); ++i)
{
if (assetDatabasePlugins[i]->QueryInterface(__uuidof(IAssetItemDatabase), (void**)&pCurrentDatabaseInterface) == S_OK)
if (assetDatabasePlugins[i]->QueryInterface(__az_uuidof(IAssetItemDatabase), (void**)&pCurrentDatabaseInterface) == S_OK)
{
if (!pCurrentDatabaseInterface)
{
@@ -129,8 +129,8 @@ namespace SandboxEditor
ViewportInteractionRequestBus::EventResult(
ray, GetViewportId(), &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint);
m_mouseInteraction.m_mousePick.m_rayOrigin = ray.origin;
m_mouseInteraction.m_mousePick.m_rayDirection = ray.direction;
m_mouseInteraction.m_mousePick.m_rayOrigin = ray.m_origin;
m_mouseInteraction.m_mousePick.m_rayDirection = ray.m_direction;
m_mouseInteraction.m_mousePick.m_screenCoordinates = screenPoint;
}
+71 -52
View File
@@ -6,7 +6,6 @@
*
*/
// Description : CViewportTitleDlg implementation file
#if !defined(Q_MOC_RUN)
@@ -42,38 +41,18 @@
#include <AzCore/std/algorithm.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/Viewport/ViewportSettings.h>
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
#include <LmbrCentral/Audio/AudioSystemComponentBus.h>
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
#include "ui_ViewportTitleDlg.h"
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
#endif //!defined(Q_MOC_RUN)
#endif //! defined(Q_MOC_RUN)
// CViewportTitleDlg dialog
inline namespace Helpers
{
void ToggleHelpers()
{
const bool newValue = !GetIEditor()->GetDisplaySettings()->IsDisplayHelpers();
GetIEditor()->GetDisplaySettings()->DisplayHelpers(newValue);
GetIEditor()->Notify(eNotify_OnDisplayRenderUpdate);
if (newValue == false)
{
GetIEditor()->GetObjectManager()->SendEvent(EVENT_HIDE_HELPER);
}
AzToolsFramework::ViewportInteraction::ViewportSettingsNotificationBus::Broadcast(
&AzToolsFramework::ViewportInteraction::ViewportSettingNotifications::OnDrawHelpersChanged, newValue);
}
bool IsHelpersShown()
{
return GetIEditor()->GetDisplaySettings()->IsDisplayHelpers();
}
}
namespace
{
class CViewportTitleDlgDisplayInfoHelper
@@ -98,7 +77,7 @@ namespace
emit ViewportInfoStatusUpdated(static_cast<int>(state));
}
};
} //end anonymous namespace
} // end anonymous namespace
CViewportTitleDlg::CViewportTitleDlg(QWidget* pParent)
: QWidget(pParent)
@@ -215,13 +194,65 @@ void CViewportTitleDlg::SetupViewportInformationMenu()
m_ui->m_debugInformationMenu->setMenu(GetViewportInformationMenu());
connect(m_ui->m_debugInformationMenu, &QToolButton::clicked, this, &CViewportTitleDlg::OnToggleDisplayInfo);
m_ui->m_debugInformationMenu->setPopupMode(QToolButton::MenuButtonPopup);
}
void CViewportTitleDlg::SetupHelpersButton()
{
connect(m_ui->m_helpers, &QToolButton::clicked, this, &CViewportTitleDlg::OnToggleHelpers);
m_ui->m_helpers->setChecked(Helpers::IsHelpersShown());
if (m_helpersMenu == nullptr)
{
m_helpersMenu = new QMenu("Helpers State", this);
auto helperAction = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::Helpers);
connect(
helperAction, &QAction::triggered, this,
[this]
{
m_ui->m_helpers->setChecked(AzToolsFramework::HelpersVisible() || AzToolsFramework::IconsVisible());
});
auto iconAction = MainWindow::instance()->GetActionManager()->GetAction(AzToolsFramework::Icons);
connect(
iconAction, &QAction::triggered, this,
[this]
{
m_ui->m_helpers->setChecked(AzToolsFramework::HelpersVisible() || AzToolsFramework::IconsVisible());
});
m_helpersAction = new QAction(tr("Helpers"), m_helpersMenu);
m_helpersAction->setCheckable(true);
connect(
m_helpersAction, &QAction::triggered, this,
[helperAction]
{
helperAction->trigger();
});
m_iconsAction = new QAction(tr("Icons"), m_helpersMenu);
m_iconsAction->setCheckable(true);
connect(
m_iconsAction, &QAction::triggered, this,
[iconAction]
{
iconAction->trigger();
});
m_helpersMenu->addAction(m_helpersAction);
m_helpersMenu->addAction(m_iconsAction);
connect(
m_helpersMenu, &QMenu::aboutToShow, this,
[this]
{
m_helpersAction->setChecked(AzToolsFramework::HelpersVisible());
m_iconsAction->setChecked(AzToolsFramework::IconsVisible());
});
m_ui->m_helpers->setCheckable(true);
m_ui->m_helpers->setMenu(m_helpersMenu);
m_ui->m_helpers->setPopupMode(QToolButton::InstantPopup);
}
m_ui->m_helpers->setChecked(AzToolsFramework::HelpersVisible() || AzToolsFramework::IconsVisible());
}
void CViewportTitleDlg::SetupOverflowMenu()
@@ -279,15 +310,20 @@ void CViewportTitleDlg::SetupOverflowMenu()
UpdateMuteActionText();
}
//////////////////////////////////////////////////////////////////////////
void CViewportTitleDlg::SetViewPane(CLayoutViewPane* pViewPane)
{
if (m_pViewPane)
{
m_pViewPane->disconnect(this);
}
m_pViewPane = pViewPane;
if (m_pViewPane)
{
connect(this, &QWidget::customContextMenuRequested, m_pViewPane, &CLayoutViewPane::ShowTitleMenu);
}
}
//////////////////////////////////////////////////////////////////////////
@@ -318,7 +354,6 @@ void CViewportTitleDlg::OnInitDialog()
m_ui->m_prefabFocusPath->hide();
m_ui->m_prefabFocusBackButton->hide();
}
}
//////////////////////////////////////////////////////////////////////////
@@ -336,13 +371,6 @@ void CViewportTitleDlg::OnMaximize()
}
}
//////////////////////////////////////////////////////////////////////////
void CViewportTitleDlg::OnToggleHelpers()
{
Helpers::ToggleHelpers();
m_ui->m_helpers->setChecked(Helpers::IsHelpersShown());
}
void CViewportTitleDlg::SetNoViewportInfo()
{
AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Broadcast(
@@ -367,7 +395,6 @@ void CViewportTitleDlg::SetCompactViewportInfo()
&AZ::AtomBridge::AtomViewportInfoDisplayRequestBus::Events::SetDisplayState, AZ::AtomBridge::ViewportInfoDisplayState::CompactInfo);
}
//////////////////////////////////////////////////////////////////////////
void CViewportTitleDlg::UpdateDisplayInfo()
{
@@ -783,9 +810,6 @@ void CViewportTitleDlg::OnEditorNotifyEvent(EEditorNotifyEvent event)
{
switch (event)
{
case eNotify_OnDisplayRenderUpdate:
m_ui->m_helpers->setChecked(Helpers::IsHelpersShown());
break;
case eNotify_OnBeginGameMode:
case eNotify_OnEndGameMode:
UpdateMuteActionText();
@@ -997,24 +1021,18 @@ void CViewportTitleDlg::UpdateOverFlowMenuState()
m_angleSizeActionWidget->setEnabled(angleSnappingActive);
}
namespace
namespace
{
void PyToggleHelpers()
{
GetIEditor()->GetDisplaySettings()->DisplayHelpers(!GetIEditor()->GetDisplaySettings()->IsDisplayHelpers());
GetIEditor()->Notify(eNotify_OnDisplayRenderUpdate);
if (GetIEditor()->GetDisplaySettings()->IsDisplayHelpers() == false)
{
GetIEditor()->GetObjectManager()->SendEvent(EVENT_HIDE_HELPER);
}
AzToolsFramework::SetHelpersVisible(!AzToolsFramework::HelpersVisible());
}
bool PyIsHelpersShown()
{
return GetIEditor()->GetDisplaySettings()->IsDisplayHelpers();
return AzToolsFramework::HelpersVisible();
}
}
} // namespace
namespace AzToolsFramework
{
@@ -1029,11 +1047,12 @@ namespace AzToolsFramework
->Attribute(AZ::Script::Attributes::Category, "Legacy/Editor")
->Attribute(AZ::Script::Attributes::Module, "legacy.general");
};
addLegacyGeneral(behaviorContext->Method("toggle_helpers", PyToggleHelpers, nullptr, "Toggles the display of helpers."));
addLegacyGeneral(behaviorContext->Method("is_helpers_shown", PyIsHelpersShown, nullptr, "Gets the display state of helpers."));
}
}
}
} // namespace AzToolsFramework
#include "ViewportTitleDlg.moc"
#include <moc_ViewportTitleDlg.cpp>
+3 -1
View File
@@ -80,7 +80,6 @@ protected:
void OnSystemEvent(ESystemEvent event, UINT_PTR wparam, UINT_PTR lparam) override;
void OnMaximize();
void OnToggleHelpers();
void UpdateDisplayInfo();
void SetupCameraDropdownMenu();
@@ -153,6 +152,9 @@ protected:
QMenu* m_aspectMenu = nullptr;
QMenu* m_resolutionMenu = nullptr;
QMenu* m_viewportInformationMenu = nullptr;
QMenu* m_helpersMenu = nullptr;
QAction* m_helpersAction = nullptr;
QAction* m_iconsAction = nullptr;
QAction* m_noInformationAction = nullptr;
QAction* m_normalInformationAction = nullptr;
QAction* m_fullInformationAction = nullptr;
-18
View File
@@ -297,8 +297,6 @@ set(FILES
Util/AffineParts.cpp
Objects/BaseObject.cpp
Objects/BaseObject.h
Animation/AnimationBipedBoneNames.cpp
Animation/AnimationBipedBoneNames.h
AnimationContext.cpp
AnimationContext.h
AzAssetBrowser/AzAssetBrowserRequestHandler.cpp
@@ -336,23 +334,12 @@ set(FILES
Controls/ConsoleSCB.qrc
Controls/FolderTreeCtrl.cpp
Controls/FolderTreeCtrl.h
Controls/HotTrackingTreeCtrl.cpp
Controls/HotTrackingTreeCtrl.h
Controls/ImageHistogramCtrl.cpp
Controls/ImageHistogramCtrl.h
Controls/ImageListCtrl.cpp
Controls/ImageListCtrl.h
Controls/MultiMonHelper.cpp
Controls/MultiMonHelper.h
Controls/NumberCtrl.cpp
Controls/NumberCtrl.h
Controls/NumberCtrl.h
Controls/SplineCtrl.cpp
Controls/SplineCtrl.h
Controls/SplineCtrlEx.cpp
Controls/SplineCtrlEx.h
Controls/TextEditorCtrl.cpp
Controls/TextEditorCtrl.h
Controls/TimelineCtrl.cpp
Controls/TimelineCtrl.h
Controls/WndGridHelper.h
@@ -526,8 +513,6 @@ set(FILES
PythonEditorFuncs.h
QtUI/QCollapsibleGroupBox.h
QtUI/QCollapsibleGroupBox.cpp
QtUI/ClickableLabel.h
QtUI/ClickableLabel.cpp
QtUI/PixmapLabelPreview.h
QtUI/PixmapLabelPreview.cpp
QtUI/WaitCursor.h
@@ -800,9 +785,6 @@ set(FILES
ViewportTitleDlg.h
EditorEnvironment.cpp
EditorEnvironment.h
IEditorPanelUtils.h
EditorPanelUtils.h
EditorPanelUtils.cpp
)
-1
View File
@@ -8,7 +8,6 @@
set(FILES
Lib/Tests/IEditorMock.h
Lib/Tests/test_ClickableLabel.cpp
Lib/Tests/test_CryEditPythonBindings.cpp
Lib/Tests/test_CryEditDocPythonBindings.cpp
Lib/Tests/test_EditorPythonBindings.cpp
@@ -168,7 +168,7 @@ namespace AZ
virtual bool IsRegisterReadonlyAndShareable() { return true; }
/**
* Override this function to control automatic reload behavior.
* Override this function to control automatic reload behavior.
* By default, the asset will reload automatically.
* Return false to disable automatic reload. Potential use cases include:
* 1, If an asset is dependent on a parent asset(i.e.both assets need to be reloaded as a group) the parent asset can explicitly reload the child.
@@ -200,10 +200,10 @@ namespace AZ
AssetHandler* m_registeredHandler{ nullptr };
// This is used to identify a unique asset and should only be set by the asset manager
// This is used to identify a unique asset and should only be set by the asset manager
// and therefore does not need to be atomic.
// All shared copy of an asset should have the same identifier and therefore
// should not be modified while making copy of an existing asset.
// should not be modified while making copy of an existing asset.
int m_creationToken = s_defaultCreationToken;
// General purpose flags that should only be accessed within the asset mutex
AZStd::bitset<32> m_flags;
@@ -430,7 +430,7 @@ namespace AZ
*/
void UpgradeAssetInfo();
/**
/**
* for debugging purposes - creates a string that represents the assets id, subid, hint, and name.
* You should use this function for any time you want to show the full details of an asset in a log message
* as it will always produce a consistent output string. By convention, don't surround the output of this call
@@ -586,26 +586,26 @@ namespace AZ
/// Called when an asset is loaded, patched and ready to be used.
virtual void OnAssetReady(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset has been moved (usually due to de-fragmentation/compaction), if possible the only data pointer is provided otherwise NULL.
virtual void OnAssetMoved(Asset<AssetData> asset, void* oldDataPointer) { (void)asset; (void)oldDataPointer; }
/// Called before an asset reload has started.
virtual void OnAssetPreReload(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset has been reloaded (usually in tool mode and loose more). It should not be called in final build.
virtual void OnAssetReloaded(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset failed to reload.
virtual void OnAssetReloadError(Asset<AssetData> asset) { (void)asset; }
/// Called when an asset has been saved. In general most assets can't be saved (in a game) so make sure you check the flag.
virtual void OnAssetSaved(Asset<AssetData> asset, bool isSuccessful) { (void)asset; (void)isSuccessful; }
/// Called when an asset is unloaded.
virtual void OnAssetUnloaded(const AssetId assetId, const AssetType assetType) { (void)assetId; (void)assetType; }
/**
/**
* Called when an error happened with an asset. When this message is received the asset should be considered broken by default.
* Note that this can happen when the asset errors during load, but also happens when the asset is missing (not in catalog etc.)
* in the case of an asset that is completely missing, the Asset<T> passed in here will have no hint or other information about
@@ -1094,7 +1094,7 @@ namespace AZ
// if we are a different asset (or being swapped with a empty) then we just swap as usual.
AZStd::swap(m_assetHint, rhs.m_assetHint);
}
}
//=========================================================================
@@ -1218,7 +1218,7 @@ namespace AZ
/// Indiscriminately skips all asset references.
bool AssetFilterNoAssetLoading(const AssetFilterInfo& filterInfo);
// Shared ProductDependency concepts between AP and LY
// Shared ProductDependency concepts between AP and LY
namespace ProductDependencyInfo
{
//! Corresponds to all ProductDependencyFlags, not just LoadBehaviors
@@ -83,7 +83,7 @@ namespace AZ::Data
AZ_PROFILE_SCOPE(AzCore, "AZ::Data::LoadAssetDataStreamCallback %s",
m_filePath.c_str());
// Get the results
// Get the results
auto streamer = AZ::Interface<AZ::IO::IStreamer>::Get();
AZ::u64 bytesRead = 0;
streamer->GetReadRequestResult(fileHandle, m_buffer, bytesRead,
@@ -100,7 +100,7 @@ namespace AZ::Data
//! The path and file name of the asset being loaded
AZStd::string m_filePath;
//! The offset into the file to start loading at.
//! The offset into the file to start loading at.
size_t m_fileOffset{ 0 };
//! The amount of data that's expected to be loaded.
@@ -811,12 +811,12 @@ namespace AZ
if (behaviorContext)
{
behaviorContext->Class<EntityId>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "entity")
->Method("IsValid", &EntityId::IsValid)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("ToString", &EntityId::ToString)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::ToString)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
@@ -243,7 +243,7 @@ namespace AZ
if (StringFunc::StartsWith(curr->m_name, command, false))
{
AZLOG_INFO("- %s : %s\n", curr->m_name, curr->m_desc);
AZLOG_INFO("- %s : %s", curr->m_name, curr->m_desc);
if (commandSubset.size() < MaxConsoleCommandPlusArgsLength)
{
@@ -433,29 +433,29 @@ namespace AZ
{
if ((curr->GetFlags() & requiredSet) != requiredSet)
{
AZLOG_WARN("%s failed required set flag check\n", curr->m_name);
AZLOG_WARN("%s failed required set flag check", curr->m_name);
continue;
}
if ((curr->GetFlags() & requiredClear) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s failed required clear flag check\n", curr->m_name);
AZLOG_WARN("%s failed required clear flag check", curr->m_name);
continue;
}
if ((curr->GetFlags() & ConsoleFunctorFlags::IsCheat) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s is marked as a cheat\n", curr->m_name);
AZLOG_WARN("%s is marked as a cheat", curr->m_name);
}
if ((curr->GetFlags() & ConsoleFunctorFlags::IsDeprecated) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("%s is marked as deprecated\n", curr->m_name);
AZLOG_WARN("%s is marked as deprecated", curr->m_name);
}
if ((curr->GetFlags() & ConsoleFunctorFlags::NeedsReload) != ConsoleFunctorFlags::Null)
{
AZLOG_WARN("Changes to %s will only take effect after level reload\n", curr->m_name);
AZLOG_WARN("Changes to %s will only take effect after level reload", curr->m_name);
}
// Letting this intentionally fall-through, since in editor we can register common variables multiple times
@@ -468,7 +468,7 @@ namespace AZ
{
CVarFixedString value;
curr->GetValue(value);
AZLOG_INFO("> %s : %s\n", curr->GetName(), value.empty() ? "<empty>" : value.c_str());
AZLOG_INFO("> %s : %s", curr->GetName(), value.empty() ? "<empty>" : value.c_str());
}
flags = curr->GetFlags();
}
@@ -119,25 +119,21 @@ namespace AZ
void LoggerSystemComponent::LogInternalV(LogLevel level, const char* format, const char* file, const char* function, int32_t line, va_list args)
{
constexpr AZStd::size_t MaxLogBufferSize = 1000;
char buffer[MaxLogBufferSize];
auto buffer = AZStd::fixed_string<MaxLogBufferSize>::format_arg(format, args);
m_logEvent.Signal(level, buffer.c_str(), file, function, line);
buffer += '\n';
const AZStd::size_t length = azvsnprintf(buffer, MaxLogBufferSize, format, args);
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 1)] = '\0';
m_logEvent.Signal(level, buffer, file, function, line);
// Force a new-line before calling the AZ::Debug::Trace functions, as they assume a newline is present
buffer[AZStd::min<AZStd::size_t>(length + 1, MaxLogBufferSize - 2)] = '\n';
switch (level)
{
case LogLevel::Warn:
AZ_Warning("Logger", true, buffer);
AZ_Warning("Logger", true, buffer.c_str());
break;
case LogLevel::Error:
AZ_Error("Logger", true, buffer);
AZ_Error("Logger", true, buffer.c_str());
break;
default:
// Catch all else with trace
AZ::Debug::Trace::Output("Logger", buffer);
AZ::Debug::Trace::Output("Logger", buffer.c_str());
break;
}
}
@@ -484,6 +484,7 @@ namespace AZ::IO
// as_posix
//! Replicates the behavior of the Python pathlib as_posix method
//! by replacing the Windows Path Separator with the Posix Path Seperator
constexpr string_type AsPosix() const;
AZStd::string StringAsPosix() const;
constexpr AZStd::fixed_string<MaxPathLength> FixedMaxPathStringAsPosix() const noexcept;
@@ -1043,6 +1043,13 @@ namespace AZ::IO
// as_posix
// Returns a copy of the path with the path separators converted to PosixPathSeparator
template <typename StringType>
constexpr auto BasicPath<StringType>::AsPosix() const -> string_type
{
string_type resultPath(m_path.begin(), m_path.end());
AZStd::replace(resultPath.begin(), resultPath.end(), WindowsPathSeparator, PosixPathSeparator);
return resultPath;
}
template <typename StringType>
AZStd::string BasicPath<StringType>::StringAsPosix() const
{
AZStd::string resultPath(m_path.begin(), m_path.end());
@@ -7,6 +7,8 @@
*/
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Serialization/Json/PathSerializer.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/functional.h>
@@ -35,10 +37,8 @@ namespace AZ::IO
size_t Save(const void* classPtr, IO::GenericStream& stream, bool) override
{
/// Save paths out using the PosixPathSeparator
PathType path(reinterpret_cast<const PathType*>(classPtr)->Native(), AZ::IO::PosixPathSeparator);
path.MakePreferred();
return static_cast<size_t>(stream.Write(path.Native().size(), path.c_str()));
auto posixPathString{ reinterpret_cast<const PathType*>(classPtr)->AsPosix() };
return static_cast<size_t>(stream.Write(posixPathString.size(), posixPathString.c_str()));
}
bool Load(void* classPtr, IO::GenericStream& stream, unsigned int, bool) override
@@ -73,5 +73,11 @@ namespace AZ::IO
AZ::SerializeContext::IDataSerializer::CreateDefaultDeleteDeleter() })
;
}
else if (auto jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
{
jsonContext->Serializer<JsonPathSerializer>()
->HandlesType<Path>()
->HandlesType<FixedMaxPath>();
}
}
}
@@ -19,144 +19,144 @@
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ::IO
{
struct BlockCacheConfig final :
public IStreamerStackConfig
{
AZ_RTTI(AZ::IO::BlockCacheConfig, "{70120525-88A4-40B6-A75B-BAA7E8FD77F3}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(BlockCacheConfig, AZ::SystemAllocator, 0);
~BlockCacheConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
//! Dynamic options for the blocks size.
//! It's possible to set static sizes or use the names from this enum to have AZ::IO::Streamer automatically fill in the sizes.
//! Fixed sizes are set through the Settings Registry with "BlockSize": 524288, while dynamic values are set like
//! "BlockSize": "MemoryAlignment". In the latter case AZ::IO::Streamer will use the available hardware information and fill
//! in the actual value.
enum BlockSize : u32
{
MaxTransfer = AZStd::numeric_limits<u32>::max(), //!< The largest possible block size.
MemoryAlignment = MaxTransfer - 1, //!< The size of the minimal memory requirement of the storage device.
SizeAlignment = MemoryAlignment - 1 //!< The minimal read size required by the storage device.
};
//! The overall size of the cache in megabytes.
u32 m_cacheSizeMib{ 8 };
//! The size of the individual blocks inside the cache.
BlockSize m_blockSize{ BlockSize::MemoryAlignment };
};
class BlockCache
: public StreamStackEntry
{
public:
BlockCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
BlockCache(BlockCache&& rhs) = delete;
BlockCache(const BlockCache& rhs) = delete;
~BlockCache() override;
BlockCache& operator=(BlockCache&& rhs) = delete;
BlockCache& operator=(const BlockCache& rhs) = delete;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void UpdateStatus(Status& status) const override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
void AddDelayedRequests(AZStd::vector<FileRequest*>& internalPending);
void UpdatePendingRequestEstimations();
void FlushCache(const RequestPath& filePath);
void FlushEntireCache();
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
double CalculateHitRatePercentage() const;
double CalculateCacheableRatePercentage() const;
s32 CalculateAvailableRequestSlots() const;
protected:
static constexpr u32 s_fileNotCached = static_cast<u32>(-1);
enum class CacheResult
{
ReadFromCache, //!< Data was found in the cache and reused.
CacheMiss, //!< Data wasn't found in the cache and no sub request was queued.
Queued, //!< A sub request was created or appended and queued for processing on the next entry in the streamer stack.
Delayed //!< There's no more room to queue a new request, so delay the request until a slot becomes available.
};
struct Section
{
u8* m_output{ nullptr }; //!< The buffer to write the data to.
FileRequest* m_parent{ nullptr }; //!< If set, the file request that is split up by this section.
FileRequest* m_wait{ nullptr }; //!< If set, this contains a "wait"-operation that blocks an operation chain from continuing until this section has been loaded.
u64 m_readOffset{ 0 }; //!< Offset into the file to start reading from.
u64 m_readSize{ 0 }; //!< Number of bytes to read from file.
u64 m_blockOffset{ 0 }; //!< Offset into the cache block to start copying from.
u64 m_copySize{ 0 }; //!< Number of bytes to copy from cache.
u32 m_cacheBlockIndex{ s_fileNotCached }; //!< If assigned, the index of the cache block assigned to this section.
bool m_used{ false }; //!< Whether or not this section is used in further processing.
// Add the provided section in front of this one.
void Prefix(const Section& section);
};
using TimePoint = AZStd::chrono::system_clock::time_point;
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
void ContinueReadFile(FileRequest* request, u64 fileLength);
CacheResult ReadFromCache(FileRequest* request, Section& section, const RequestPath& filePath);
CacheResult ReadFromCache(FileRequest* request, Section& section, u32 cacheBlock);
CacheResult ServiceFromCache(FileRequest* request, Section& section, const RequestPath& filePath, bool sharedRead);
void CompleteRead(FileRequest& request);
bool SplitRequest(Section& prolog, Section& main, Section& epilog, const RequestPath& filePath, u64 fileLength,
u64 offset, u64 size, u8* buffer) const;
u8* GetCacheBlockData(u32 index);
void TouchBlock(u32 index);
AZ::u32 RecycleOldestBlock(const RequestPath& filePath, u64 offset);
u32 FindInCache(const RequestPath& filePath, u64 offset) const;
bool IsCacheBlockInFlight(u32 index) const;
void ResetCacheEntry(u32 index);
void ResetCache();
//! Map of the file requests that are being processed and the sections of the parent requests they'll complete.
AZStd::unordered_multimap<FileRequest*, Section> m_pendingRequests;
//! List of file sections that were delayed because the cache was full.
AZStd::deque<Section> m_delayedSections;
AZ::Statistics::RunningStatistic m_hitRateStat;
AZ::Statistics::RunningStatistic m_cacheableStat;
u8* m_cache;
u64 m_cacheSize;
u32 m_blockSize;
u32 m_alignment;
u32 m_numBlocks;
s32 m_numInFlightRequests{ 0 };
//! The file path associated with a cache block.
AZStd::unique_ptr<RequestPath[]> m_cachedPaths; // Array of m_numBlocks size.
//! The offset into the file the cache blocks starts at.
AZStd::unique_ptr<u64[]> m_cachedOffsets; // Array of m_numBlocks size.
//! The last time the cache block was read from.
AZStd::unique_ptr<TimePoint[]> m_blockLastTouched; // Array of m_numBlocks size.
//! The file request that's currently read data into the cache block. If null, the block has been read.
AZStd::unique_ptr<FileRequest*[]> m_inFlightRequests; // Array of m_numbBlocks size.
//! The number of requests waiting for meta data to be retrieved.
s32 m_numMetaDataRetrievalInProgress{ 0 };
//! Whether or not only the epilog ever writes to the cache.
bool m_onlyEpilogWrites;
};
} // namespace AZ::IO
namespace AZ
{
namespace IO
{
struct BlockCacheConfig final :
public IStreamerStackConfig
{
AZ_RTTI(AZ::IO::BlockCacheConfig, "{70120525-88A4-40B6-A75B-BAA7E8FD77F3}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(BlockCacheConfig, AZ::SystemAllocator, 0);
~BlockCacheConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
//! Dynamic options for the blocks size.
//! It's possible to set static sizes or use the names from this enum to have AZ::IO::Streamer automatically fill in the sizes.
//! Fixed sizes are set through the Settings Registry with "BlockSize": 524288, while dynamic values are set like
//! "BlockSize": "MemoryAlignment". In the latter case AZ::IO::Streamer will use the available hardware information and fill
//! in the actual value.
enum BlockSize : u32
{
MaxTransfer = AZStd::numeric_limits<u32>::max(), //!< The largest possible block size.
MemoryAlignment = MaxTransfer - 1, //!< The size of the minimal memory requirement of the storage device.
SizeAlignment = MemoryAlignment - 1 //!< The minimal read size required by the storage device.
};
//! The overall size of the cache in megabytes.
u32 m_cacheSizeMib{ 8 };
//! The size of the individual blocks inside the cache.
BlockSize m_blockSize{ BlockSize::MemoryAlignment };
};
class BlockCache
: public StreamStackEntry
{
public:
BlockCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
BlockCache(BlockCache&& rhs) = delete;
BlockCache(const BlockCache& rhs) = delete;
~BlockCache() override;
BlockCache& operator=(BlockCache&& rhs) = delete;
BlockCache& operator=(const BlockCache& rhs) = delete;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void UpdateStatus(Status& status) const override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
void AddDelayedRequests(AZStd::vector<FileRequest*>& internalPending);
void UpdatePendingRequestEstimations();
void FlushCache(const RequestPath& filePath);
void FlushEntireCache();
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
double CalculateHitRatePercentage() const;
double CalculateCacheableRatePercentage() const;
s32 CalculateAvailableRequestSlots() const;
protected:
static constexpr u32 s_fileNotCached = static_cast<u32>(-1);
enum class CacheResult
{
ReadFromCache, //!< Data was found in the cache and reused.
CacheMiss, //!< Data wasn't found in the cache and no sub request was queued.
Queued, //!< A sub request was created or appended and queued for processing on the next entry in the streamer stack.
Delayed //!< There's no more room to queue a new request, so delay the request until a slot becomes available.
};
struct Section
{
u8* m_output{ nullptr }; //!< The buffer to write the data to.
FileRequest* m_parent{ nullptr }; //!< If set, the file request that is split up by this section.
FileRequest* m_wait{ nullptr }; //!< If set, this contains a "wait"-operation that blocks an operation chain from continuing until this section has been loaded.
u64 m_readOffset{ 0 }; //!< Offset into the file to start reading from.
u64 m_readSize{ 0 }; //!< Number of bytes to read from file.
u64 m_blockOffset{ 0 }; //!< Offset into the cache block to start copying from.
u64 m_copySize{ 0 }; //!< Number of bytes to copy from cache.
u32 m_cacheBlockIndex{ s_fileNotCached }; //!< If assigned, the index of the cache block assigned to this section.
bool m_used{ false }; //!< Whether or not this section is used in further processing.
// Add the provided section in front of this one.
void Prefix(const Section& section);
};
using TimePoint = AZStd::chrono::system_clock::time_point;
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
void ContinueReadFile(FileRequest* request, u64 fileLength);
CacheResult ReadFromCache(FileRequest* request, Section& section, const RequestPath& filePath);
CacheResult ReadFromCache(FileRequest* request, Section& section, u32 cacheBlock);
CacheResult ServiceFromCache(FileRequest* request, Section& section, const RequestPath& filePath, bool sharedRead);
void CompleteRead(FileRequest& request);
bool SplitRequest(Section& prolog, Section& main, Section& epilog, const RequestPath& filePath, u64 fileLength,
u64 offset, u64 size, u8* buffer) const;
u8* GetCacheBlockData(u32 index);
void TouchBlock(u32 index);
AZ::u32 RecycleOldestBlock(const RequestPath& filePath, u64 offset);
u32 FindInCache(const RequestPath& filePath, u64 offset) const;
bool IsCacheBlockInFlight(u32 index) const;
void ResetCacheEntry(u32 index);
void ResetCache();
//! Map of the file requests that are being processed and the sections of the parent requests they'll complete.
AZStd::unordered_multimap<FileRequest*, Section> m_pendingRequests;
//! List of file sections that were delayed because the cache was full.
AZStd::deque<Section> m_delayedSections;
AZ::Statistics::RunningStatistic m_hitRateStat;
AZ::Statistics::RunningStatistic m_cacheableStat;
u8* m_cache;
u64 m_cacheSize;
u32 m_blockSize;
u32 m_alignment;
u32 m_numBlocks;
s32 m_numInFlightRequests{ 0 };
//! The file path associated with a cache block.
AZStd::unique_ptr<RequestPath[]> m_cachedPaths; // Array of m_numBlocks size.
//! The offset into the file the cache blocks starts at.
AZStd::unique_ptr<u64[]> m_cachedOffsets; // Array of m_numBlocks size.
//! The last time the cache block was read from.
AZStd::unique_ptr<TimePoint[]> m_blockLastTouched; // Array of m_numBlocks size.
//! The file request that's currently read data into the cache block. If null, the block has been read.
AZStd::unique_ptr<FileRequest*[]> m_inFlightRequests; // Array of m_numbBlocks size.
//! The number of requests waiting for meta data to be retrieved.
s32 m_numMetaDataRetrievalInProgress{ 0 };
//! Whether or not only the epilog ever writes to the cache.
bool m_onlyEpilogWrites;
};
} // namespace IO
AZ_TYPE_INFO_SPECIALIZE(AZ::IO::BlockCacheConfig::BlockSize, "{5D4D597D-4605-462D-A27D-8046115C5381}");
} // namespace AZ
@@ -18,77 +18,74 @@
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ
namespace AZ::IO
{
namespace IO
struct DedicatedCacheConfig final :
public IStreamerStackConfig
{
struct DedicatedCacheConfig final :
public IStreamerStackConfig
{
AZ_RTTI(AZ::IO::DedicatedCacheConfig, "{DF0F6029-02B0-464C-9846-524654335BCC}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(DedicatedCacheConfig, AZ::SystemAllocator, 0);
AZ_RTTI(AZ::IO::DedicatedCacheConfig, "{DF0F6029-02B0-464C-9846-524654335BCC}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(DedicatedCacheConfig, AZ::SystemAllocator, 0);
~DedicatedCacheConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
~DedicatedCacheConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
//! The size of the individual blocks inside the cache.
BlockCacheConfig::BlockSize m_blockSize{ BlockCacheConfig::BlockSize::MemoryAlignment };
//! The overall size of the cache in megabytes.
u32 m_cacheSizeMib{ 8 };
//! If true, only the epilog is written otherwise the prolog and epilog are written. In either case both prolog and epilog are read.
//! For uses of the cache that read mostly sequentially this flag should be set to true. If reads are more random than it's better
//! to set this flag to false.
bool m_writeOnlyEpilog{ true };
};
//! The size of the individual blocks inside the cache.
BlockCacheConfig::BlockSize m_blockSize{ BlockCacheConfig::BlockSize::MemoryAlignment };
//! The overall size of the cache in megabytes.
u32 m_cacheSizeMib{ 8 };
//! If true, only the epilog is written otherwise the prolog and epilog are written. In either case both prolog and epilog are read.
//! For uses of the cache that read mostly sequentially this flag should be set to true. If reads are more random than it's better
//! to set this flag to false.
bool m_writeOnlyEpilog{ true };
};
class DedicatedCache
: public StreamStackEntry
{
public:
DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
void SetContext(StreamerContext& context) override;
class DedicatedCache
: public StreamStackEntry
{
public:
DedicatedCache(u64 cacheSize, u32 blockSize, u32 alignment, bool onlyEpilogWrites);
void PrepareRequest(FileRequest* request) override;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
void SetContext(StreamerContext& context) override;
void UpdateStatus(Status& status) const override;
void PrepareRequest(FileRequest* request) override;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
void UpdateStatus(Status& status) const override;
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
private:
void CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data);
void DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data);
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
size_t FindCache(const RequestPath& filename, FileRange range);
size_t FindCache(const RequestPath& filename, u64 offset);
private:
void CreateDedicatedCache(FileRequest* request, FileRequest::CreateDedicatedCacheData& data);
void DestroyDedicatedCache(FileRequest* request, FileRequest::DestroyDedicatedCacheData& data);
void FlushCache(const RequestPath& filePath);
void FlushEntireCache();
void ReadFile(FileRequest* request, FileRequest::ReadData& data);
size_t FindCache(const RequestPath& filename, FileRange range);
size_t FindCache(const RequestPath& filename, u64 offset);
AZStd::vector<RequestPath> m_cachedFileNames;
AZStd::vector<FileRange> m_cachedFileRanges;
AZStd::vector<AZStd::unique_ptr<BlockCache>> m_cachedFileCaches;
AZStd::vector<size_t> m_cachedFileRefCounts;
void FlushCache(const RequestPath& filePath);
void FlushEntireCache();
AZ::Statistics::RunningStatistic m_usagePercentageStat;
AZStd::vector<RequestPath> m_cachedFileNames;
AZStd::vector<FileRange> m_cachedFileRanges;
AZStd::vector<AZStd::unique_ptr<BlockCache>> m_cachedFileCaches;
AZStd::vector<size_t> m_cachedFileRefCounts;
AZ::Statistics::RunningStatistic m_usagePercentageStat;
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
AZ::Statistics::RunningStatistic m_overallHitRateStat;
AZ::Statistics::RunningStatistic m_overallCacheableRateStat;
AZ::Statistics::RunningStatistic m_overallHitRateStat;
AZ::Statistics::RunningStatistic m_overallCacheableRateStat;
#endif
u64 m_cacheSize;
u32 m_alignment;
u32 m_blockSize;
bool m_onlyEpilogWrites;
};
} // namespace IO
} // namespace AZ
u64 m_cacheSize;
u32 m_alignment;
u32 m_blockSize;
bool m_onlyEpilogWrites;
};
} // namespace AZ::IO
@@ -21,403 +21,401 @@
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string_view.h>
namespace AZ
namespace AZ::IO
{
namespace IO
class StreamStackEntry;
class ExternalFileRequest;
using FileRequestPtr = AZStd::intrusive_ptr<ExternalFileRequest>;
class FileRequest final
{
class StreamStackEntry;
class ExternalFileRequest;
public:
inline constexpr static AZStd::chrono::system_clock::time_point s_noDeadlineTime = AZStd::chrono::system_clock::time_point::max();
using FileRequestPtr = AZStd::intrusive_ptr<ExternalFileRequest>;
class FileRequest final
friend class StreamerContext;
friend class ExternalFileRequest;
//! Stores a reference to the external request so it stays alive while the request is being processed.
//! This is needed because Streamer supports fire-and-forget requests since completion can be handled by
//! registering a callback.
struct ExternalRequestData
{
public:
inline constexpr static AZStd::chrono::system_clock::time_point s_noDeadlineTime = AZStd::chrono::system_clock::time_point::max();
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
friend class StreamerContext;
friend class ExternalFileRequest;
explicit ExternalRequestData(FileRequestPtr&& request);
//! Stores a reference to the external request so it stays alive while the request is being processed.
//! This is needed because Streamer supports fire-and-forget requests since completion can be handled by
//! registering a callback.
struct ExternalRequestData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
FileRequestPtr m_request; //!< The request that was send to Streamer.
};
explicit ExternalRequestData(FileRequestPtr&& request);
FileRequestPtr m_request; //!< The request that was send to Streamer.
};
//! Stores an instance of a RequestPath. To reduce copying instances of a RequestPath functions that
//! need a path take them by reference to the original request. In some cases a path originates from
//! within in the stack and temporary storage is needed. This struct allows for that temporary storage
//! so it can be safely referenced later.
struct RequestPathStoreData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
//! Stores an instance of a RequestPath. To reduce copying instances of a RequestPath functions that
//! need a path take them by reference to the original request. In some cases a path originates from
//! within in the stack and temporary storage is needed. This struct allows for that temporary storage
//! so it can be safely referenced later.
struct RequestPathStoreData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
explicit RequestPathStoreData(RequestPath path);
explicit RequestPathStoreData(RequestPath path);
RequestPath m_path;
};
RequestPath m_path;
};
//! Request to read data. This is an untranslated request and holds a relative path. The Scheduler
//! will translate this to the appropriate ReadData or CompressedReadData.
struct ReadRequestData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
//! Request to read data. This is an untranslated request and holds a relative path. The Scheduler
//! will translate this to the appropriate ReadData or CompressedReadData.
struct ReadRequestData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
~ReadRequestData();
RequestPath m_path; //!< Relative path to the target file.
IStreamerTypes::RequestMemoryAllocator* m_allocator; //!< Allocator used to manage the memory for this request.
AZStd::chrono::system_clock::time_point m_deadline; //!< Time by which this request should have been completed.
void* m_output; //!< The memory address assigned (during processing) to store the read data to.
u64 m_outputSize; //!< The memory size of the addressed used to store the read data.
u64 m_offset; //!< The offset in bytes into the file.
u64 m_size; //!< The number of bytes to read from the file.
IStreamerTypes::Priority m_priority; //!< Priority used for ordering requests. This is used when requests have the same deadline.
IStreamerTypes::MemoryType m_memoryType; //!< The type of memory provided by the allocator if used.
};
//! Request to read data. This is a translated request and holds an absolute path and has been
//! resolved to the archive file if needed.
struct ReadData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead);
const RequestPath& m_path; //!< The path to the file that contains the requested data.
void* m_output; //!< Target output to write the read data to.
u64 m_outputSize; //!< Size of memory m_output points to. This needs to be at least as big as m_size, but can be bigger.
u64 m_offset; //!< The offset in bytes into the file.
u64 m_size; //!< The number of bytes to read from the file.
bool m_sharedRead; //!< True if other code will be reading from the file or the stack entry can exclusively lock.
};
//! Request to read and decompress data.
struct CompressedReadData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize);
CompressionInfo m_compressionInfo;
void* m_output; //!< Target output to write the read data to.
u64 m_readOffset; //!< The offset into the decompressed to start copying from.
u64 m_readSize; //!< Number of bytes to read from the decompressed file.
};
//! Holds the progress of an operation chain until this request is explicitly completed.
struct WaitData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
};
//! Checks to see if any node in the stack can find a file at the provided path.
struct FileExistsCheckData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
explicit FileExistsCheckData(const RequestPath& path);
const RequestPath& m_path;
bool m_found{ false };
};
//! Searches for a file in the stack and retrieves the meta data. This may be slower than a file exists
//! check.
struct FileMetaDataRetrievalData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
explicit FileMetaDataRetrievalData(const RequestPath& path);
const RequestPath& m_path;
u64 m_fileSize{ 0 };
bool m_found{ false };
};
//! Cancels a request in the stream stack, if possible.
struct CancelData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHighest;
inline constexpr static bool s_failWhenUnhandled = false;
explicit CancelData(FileRequestPtr target);
FileRequestPtr m_target; //!< The request that will be canceled.
};
//! Updates the priority and deadline of a request that has not been queued yet.
struct RescheduleData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
FileRequestPtr m_target; //!< The request that will be rescheduled.
AZStd::chrono::system_clock::time_point m_newDeadline; //!< The new deadline for the request.
IStreamerTypes::Priority m_newPriority; //!< The new priority for the request.
};
//! Flushes all references to the provided file in the streaming stack.
struct FlushData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
explicit FlushData(RequestPath path);
RequestPath m_path;
};
//! Flushes all caches in the streaming stack.
struct FlushAllData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
};
//! Creates a cache dedicated to a single file. This is best used for files where blocks are read from
//! periodically such as audio banks of video files.
struct CreateDedicatedCacheData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
CreateDedicatedCacheData(RequestPath path, const FileRange& range);
RequestPath m_path;
FileRange m_range;
};
//! Destroys a cache dedicated to a single file that was previously created by CreateDedicatedCache
struct DestroyDedicatedCacheData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
DestroyDedicatedCacheData(RequestPath path, const FileRange& range);
RequestPath m_path;
FileRange m_range;
};
struct ReportData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityLow;
inline constexpr static bool s_failWhenUnhandled = false;
enum class ReportType
{
FileLocks
};
explicit ReportData(ReportType reportType);
ReportType m_reportType;
};
//! Data for a custom command. This can be used by nodes added extensions that need data that can't be stored
//! in the already provided data.
struct CustomData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
CustomData(AZStd::any data, bool failWhenUnhandled);
AZStd::any m_data; //!< The data for the custom request.
bool m_failWhenUnhandled; //!< Whether or not the request is marked as failed or success when no node process it.
};
using CommandVariant = AZStd::variant<AZStd::monostate, ExternalRequestData, RequestPathStoreData, ReadRequestData, ReadData,
CompressedReadData, WaitData, FileExistsCheckData, FileMetaDataRetrievalData, CancelData, RescheduleData, FlushData,
FlushAllData, CreateDedicatedCacheData, DestroyDedicatedCacheData, ReportData, CustomData>;
using OnCompletionCallback = AZStd::function<void(FileRequest& request)>;
AZ_CLASS_ALLOCATOR(FileRequest, SystemAllocator, 0);
enum class Usage : u8
{
Internal,
External
};
void CreateRequestLink(FileRequestPtr&& request);
void CreateRequestPathStore(FileRequest* parent, RequestPath path);
void CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
ReadRequestData(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
void CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
ReadRequestData(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
void CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead = false);
void CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo, void* output,
u64 readOffset, u64 readSize);
void CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo, void* output,
u64 readOffset, u64 readSize);
void CreateWait(FileRequest* parent);
void CreateFileExistsCheck(const RequestPath& path);
void CreateFileMetaDataRetrieval(const RequestPath& path);
void CreateCancel(FileRequestPtr target);
void CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
void CreateFlush(RequestPath path);
void CreateFlushAll();
void CreateDedicatedCacheCreation(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
void CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
void CreateReport(ReportData::ReportType reportType);
void CreateCustom(AZStd::any data, bool failWhenUnhandled = true, FileRequest* parent = nullptr);
~ReadRequestData();
void SetCompletionCallback(OnCompletionCallback callback);
CommandVariant& GetCommand();
const CommandVariant& GetCommand() const;
IStreamerTypes::RequestStatus GetStatus() const;
void SetStatus(IStreamerTypes::RequestStatus newStatus);
FileRequest* GetParent();
const FileRequest* GetParent() const;
size_t GetNumDependencies() const;
static constexpr size_t GetMaxNumDependencies();
//! Whether or not this request should fail if no node in the chain has picked up the request.
bool FailsWhenUnhandled() const;
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
template<typename T> T* GetCommandFromChain();
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
template<typename T> const T* GetCommandFromChain() const;
//! Determines if this request is contributing to the external request.
bool WorksOn(FileRequestPtr& request) const;
//! Returns the id that's assigned to the request when it was added to the pending queue.
//! The id will always increment so a smaller id means it was originally queued earlier.
size_t GetPendingId() const;
//! Set the estimated completion time for this request and it's immediate parent. The general approach
//! to getting the final estimation is to bubble up the estimation, with ever entry in the stack adding
//! it's own additional delay.
void SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time);
AZStd::chrono::system_clock::time_point GetEstimatedCompletion() const;
private:
explicit FileRequest(Usage usage = Usage::Internal);
~FileRequest();
void Reset();
void SetOptionalParent(FileRequest* parent);
inline static void OnCompletionPlaceholder(const FileRequest& /*request*/) {}
//! Command and parameters for the request.
CommandVariant m_command;
//! Status of the request.
AZStd::atomic<IStreamerTypes::RequestStatus> m_status{ IStreamerTypes::RequestStatus::Pending };
//! Called once the request has completed. This will always be called from the Streamer thread
//! and thread safety is the responsibility of called function. When assigning a lambda avoid
//! capturing a FileRequestPtr by value as this will cause a circular reference which causes
//! the FileRequestPtr to never be released and causes a memory leak. This call will
//! block the main Streamer thread until it returns so callbacks should be kept short. If
//! a longer running task is needed consider using a job to do the work.
OnCompletionCallback m_onCompletion;
//! Estimated time this request will complete. This is an estimation and depends on many
//! factors which can cause it to change drastically from moment to moment.
AZStd::chrono::system_clock::time_point m_estimatedCompletion;
//! The file request that has a dependency on this one. This can be null if there are no
//! other request depending on this one to complete.
FileRequest* m_parent{ nullptr };
//! Id assigned when the request is added to the pending queue.
size_t m_pendingId{ 0 };
//! The number of dependent file request that need to complete before this one is done.
u16 m_dependencies{ 0 };
//! Internal request. If this is true the request is created inside the streaming stack and never
//! leaves it. If true it will automatically be maintained by the scheduler, if false than it's
//! up to the owner to recycle this request.
Usage m_usage{ Usage::Internal };
//! Whether or not this request is currently in a recycle bin. This allows detecting double deletes.
bool m_inRecycleBin{ false };
RequestPath m_path; //!< Relative path to the target file.
IStreamerTypes::RequestMemoryAllocator* m_allocator; //!< Allocator used to manage the memory for this request.
AZStd::chrono::system_clock::time_point m_deadline; //!< Time by which this request should have been completed.
void* m_output; //!< The memory address assigned (during processing) to store the read data to.
u64 m_outputSize; //!< The memory size of the addressed used to store the read data.
u64 m_offset; //!< The offset in bytes into the file.
u64 m_size; //!< The number of bytes to read from the file.
IStreamerTypes::Priority m_priority; //!< Priority used for ordering requests. This is used when requests have the same deadline.
IStreamerTypes::MemoryType m_memoryType; //!< The type of memory provided by the allocator if used.
};
class StreamerContext;
class FileRequestHandle;
//! ExternalFileRequest is a wrapper around the FileRequest so it's safe to use outside the
//! Streaming Stack. The main differences are that ExternalFileRequest is used in a thread-safe
//! context and it doesn't get automatically destroyed upon completion. Instead intrusive_ptr is
//! used to handle clean up.
class ExternalFileRequest final
//! Request to read data. This is a translated request and holds an absolute path and has been
//! resolved to the archive file if needed.
struct ReadData
{
friend struct AZStd::IntrusivePtrCountPolicy<ExternalFileRequest>;
friend class FileRequestHandle;
friend class FileRequest;
friend class Streamer;
friend class StreamerContext;
friend class Scheduler;
friend class Device;
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
public:
AZ_CLASS_ALLOCATOR(ExternalFileRequest, SystemAllocator, 0);
ReadData(void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead);
explicit ExternalFileRequest(StreamerContext* owner);
private:
void add_ref();
void release();
FileRequest m_request;
AZStd::atomic_uint64_t m_refCount{ 0 };
StreamerContext* m_owner;
const RequestPath& m_path; //!< The path to the file that contains the requested data.
void* m_output; //!< Target output to write the read data to.
u64 m_outputSize; //!< Size of memory m_output points to. This needs to be at least as big as m_size, but can be bigger.
u64 m_offset; //!< The offset in bytes into the file.
u64 m_size; //!< The number of bytes to read from the file.
bool m_sharedRead; //!< True if other code will be reading from the file or the stack entry can exclusively lock.
};
class FileRequestHandle
//! Request to read and decompress data.
struct CompressedReadData
{
public:
friend class Streamer;
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
// Intentional cast operator.
FileRequestHandle(FileRequest& request)
: m_request(&request)
{}
CompressedReadData(CompressionInfo&& compressionInfo, void* output, u64 readOffset, u64 readSize);
// Intentional cast operator.
FileRequestHandle(const FileRequestPtr& request)
: m_request(request ? &request->m_request : nullptr)
{}
private:
FileRequest* m_request;
CompressionInfo m_compressionInfo;
void* m_output; //!< Target output to write the read data to.
u64 m_readOffset; //!< The offset into the decompressed to start copying from.
u64 m_readSize; //!< Number of bytes to read from the decompressed file.
};
bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
} // namespace IO
} // namespace AZ
//! Holds the progress of an operation chain until this request is explicitly completed.
struct WaitData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
inline constexpr static bool s_failWhenUnhandled = true;
};
//! Checks to see if any node in the stack can find a file at the provided path.
struct FileExistsCheckData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
explicit FileExistsCheckData(const RequestPath& path);
const RequestPath& m_path;
bool m_found{ false };
};
//! Searches for a file in the stack and retrieves the meta data. This may be slower than a file exists
//! check.
struct FileMetaDataRetrievalData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
explicit FileMetaDataRetrievalData(const RequestPath& path);
const RequestPath& m_path;
u64 m_fileSize{ 0 };
bool m_found{ false };
};
//! Cancels a request in the stream stack, if possible.
struct CancelData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHighest;
inline constexpr static bool s_failWhenUnhandled = false;
explicit CancelData(FileRequestPtr target);
FileRequestPtr m_target; //!< The request that will be canceled.
};
//! Updates the priority and deadline of a request that has not been queued yet.
struct RescheduleData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
RescheduleData(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
FileRequestPtr m_target; //!< The request that will be rescheduled.
AZStd::chrono::system_clock::time_point m_newDeadline; //!< The new deadline for the request.
IStreamerTypes::Priority m_newPriority; //!< The new priority for the request.
};
//! Flushes all references to the provided file in the streaming stack.
struct FlushData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
explicit FlushData(RequestPath path);
RequestPath m_path;
};
//! Flushes all caches in the streaming stack.
struct FlushAllData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
};
//! Creates a cache dedicated to a single file. This is best used for files where blocks are read from
//! periodically such as audio banks of video files.
struct CreateDedicatedCacheData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
CreateDedicatedCacheData(RequestPath path, const FileRange& range);
RequestPath m_path;
FileRange m_range;
};
//! Destroys a cache dedicated to a single file that was previously created by CreateDedicatedCache
struct DestroyDedicatedCacheData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityHigh;
inline constexpr static bool s_failWhenUnhandled = false;
DestroyDedicatedCacheData(RequestPath path, const FileRange& range);
RequestPath m_path;
FileRange m_range;
};
struct ReportData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityLow;
inline constexpr static bool s_failWhenUnhandled = false;
enum class ReportType
{
FileLocks
};
explicit ReportData(ReportType reportType);
ReportType m_reportType;
};
//! Data for a custom command. This can be used by nodes added extensions that need data that can't be stored
//! in the already provided data.
struct CustomData
{
inline constexpr static IStreamerTypes::Priority s_orderPriority = IStreamerTypes::s_priorityMedium;
CustomData(AZStd::any data, bool failWhenUnhandled);
AZStd::any m_data; //!< The data for the custom request.
bool m_failWhenUnhandled; //!< Whether or not the request is marked as failed or success when no node process it.
};
using CommandVariant = AZStd::variant<AZStd::monostate, ExternalRequestData, RequestPathStoreData, ReadRequestData, ReadData,
CompressedReadData, WaitData, FileExistsCheckData, FileMetaDataRetrievalData, CancelData, RescheduleData, FlushData,
FlushAllData, CreateDedicatedCacheData, DestroyDedicatedCacheData, ReportData, CustomData>;
using OnCompletionCallback = AZStd::function<void(FileRequest& request)>;
AZ_CLASS_ALLOCATOR(FileRequest, SystemAllocator, 0);
enum class Usage : u8
{
Internal,
External
};
void CreateRequestLink(FileRequestPtr&& request);
void CreateRequestPathStore(FileRequest* parent, RequestPath path);
void CreateReadRequest(RequestPath path, void* output, u64 outputSize, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
void CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
AZStd::chrono::system_clock::time_point deadline, IStreamerTypes::Priority priority);
void CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path, u64 offset, u64 size, bool sharedRead = false);
void CreateCompressedRead(FileRequest* parent, const CompressionInfo& compressionInfo, void* output,
u64 readOffset, u64 readSize);
void CreateCompressedRead(FileRequest* parent, CompressionInfo&& compressionInfo, void* output,
u64 readOffset, u64 readSize);
void CreateWait(FileRequest* parent);
void CreateFileExistsCheck(const RequestPath& path);
void CreateFileMetaDataRetrieval(const RequestPath& path);
void CreateCancel(FileRequestPtr target);
void CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline, IStreamerTypes::Priority newPriority);
void CreateFlush(RequestPath path);
void CreateFlushAll();
void CreateDedicatedCacheCreation(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
void CreateDedicatedCacheDestruction(RequestPath path, const FileRange& range = {}, FileRequest* parent = nullptr);
void CreateReport(ReportData::ReportType reportType);
void CreateCustom(AZStd::any data, bool failWhenUnhandled = true, FileRequest* parent = nullptr);
void SetCompletionCallback(OnCompletionCallback callback);
CommandVariant& GetCommand();
const CommandVariant& GetCommand() const;
IStreamerTypes::RequestStatus GetStatus() const;
void SetStatus(IStreamerTypes::RequestStatus newStatus);
FileRequest* GetParent();
const FileRequest* GetParent() const;
size_t GetNumDependencies() const;
static constexpr size_t GetMaxNumDependencies();
//! Whether or not this request should fail if no node in the chain has picked up the request.
bool FailsWhenUnhandled() const;
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
template<typename T> T* GetCommandFromChain();
//! Checks the chain of request for the provided command. Returns the command if found, otherwise null.
template<typename T> const T* GetCommandFromChain() const;
//! Determines if this request is contributing to the external request.
bool WorksOn(FileRequestPtr& request) const;
//! Returns the id that's assigned to the request when it was added to the pending queue.
//! The id will always increment so a smaller id means it was originally queued earlier.
size_t GetPendingId() const;
//! Set the estimated completion time for this request and it's immediate parent. The general approach
//! to getting the final estimation is to bubble up the estimation, with ever entry in the stack adding
//! it's own additional delay.
void SetEstimatedCompletion(AZStd::chrono::system_clock::time_point time);
AZStd::chrono::system_clock::time_point GetEstimatedCompletion() const;
private:
explicit FileRequest(Usage usage = Usage::Internal);
~FileRequest();
void Reset();
void SetOptionalParent(FileRequest* parent);
inline static void OnCompletionPlaceholder(const FileRequest& /*request*/) {}
//! Command and parameters for the request.
CommandVariant m_command;
//! Status of the request.
AZStd::atomic<IStreamerTypes::RequestStatus> m_status{ IStreamerTypes::RequestStatus::Pending };
//! Called once the request has completed. This will always be called from the Streamer thread
//! and thread safety is the responsibility of called function. When assigning a lambda avoid
//! capturing a FileRequestPtr by value as this will cause a circular reference which causes
//! the FileRequestPtr to never be released and causes a memory leak. This call will
//! block the main Streamer thread until it returns so callbacks should be kept short. If
//! a longer running task is needed consider using a job to do the work.
OnCompletionCallback m_onCompletion;
//! Estimated time this request will complete. This is an estimation and depends on many
//! factors which can cause it to change drastically from moment to moment.
AZStd::chrono::system_clock::time_point m_estimatedCompletion;
//! The file request that has a dependency on this one. This can be null if there are no
//! other request depending on this one to complete.
FileRequest* m_parent{ nullptr };
//! Id assigned when the request is added to the pending queue.
size_t m_pendingId{ 0 };
//! The number of dependent file request that need to complete before this one is done.
u16 m_dependencies{ 0 };
//! Internal request. If this is true the request is created inside the streaming stack and never
//! leaves it. If true it will automatically be maintained by the scheduler, if false than it's
//! up to the owner to recycle this request.
Usage m_usage{ Usage::Internal };
//! Whether or not this request is currently in a recycle bin. This allows detecting double deletes.
bool m_inRecycleBin{ false };
};
class StreamerContext;
class FileRequestHandle;
//! ExternalFileRequest is a wrapper around the FileRequest so it's safe to use outside the
//! Streaming Stack. The main differences are that ExternalFileRequest is used in a thread-safe
//! context and it doesn't get automatically destroyed upon completion. Instead intrusive_ptr is
//! used to handle clean up.
class ExternalFileRequest final
{
friend struct AZStd::IntrusivePtrCountPolicy<ExternalFileRequest>;
friend class FileRequestHandle;
friend class FileRequest;
friend class Streamer;
friend class StreamerContext;
friend class Scheduler;
friend class Device;
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
public:
AZ_CLASS_ALLOCATOR(ExternalFileRequest, SystemAllocator, 0);
explicit ExternalFileRequest(StreamerContext* owner);
private:
void add_ref();
void release();
FileRequest m_request;
AZStd::atomic_uint64_t m_refCount{ 0 };
StreamerContext* m_owner;
};
class FileRequestHandle
{
public:
friend class Streamer;
friend bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
// Intentional cast operator.
FileRequestHandle(FileRequest& request)
: m_request(&request)
{}
// Intentional cast operator.
FileRequestHandle(const FileRequestPtr& request)
: m_request(request ? &request->m_request : nullptr)
{}
private:
FileRequest* m_request;
};
bool operator==(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
bool operator==(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
bool operator!=(const FileRequestHandle& lhs, const FileRequestPtr& rhs);
bool operator!=(const FileRequestPtr& lhs, const FileRequestHandle& rhs);
} // namespace AZ::IO
#include <AzCore/IO/Streamer/FileRequest.inl>
@@ -19,118 +19,115 @@
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Statistics/RunningStatistic.h>
namespace AZ
namespace AZ::IO
{
namespace IO
struct FullFileDecompressorConfig final :
public IStreamerStackConfig
{
struct FullFileDecompressorConfig final :
public IStreamerStackConfig
AZ_RTTI(AZ::IO::FullFileDecompressorConfig, "{C96B7EC1-8C73-4493-A7CB-66F5D550FC3A}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(FullFileDecompressorConfig, AZ::SystemAllocator, 0);
~FullFileDecompressorConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
//! Maximum number of reads that are kept in flight.
u32 m_maxNumReads{ 2 };
//! Maximum number of decompression jobs that can run simultaneously.
u32 m_maxNumJobs{ 2 };
};
//! Entry in the streaming stack that decompresses files from an archive that are stored
//! as single files and without equally distributed seek points.
//! Because the target archive has compressed the entire file, it needs to be decompressed
//! completely, so even if the file is partially read, it needs to be fully loaded. This
//! also means that there's no upper limit to the memory so every decompression job will
//! need to allocate memory as a temporary buffer (in-place decompression is not supported).
//! Finally, the lack of an upper limit also means that the duration of the decompression job
//! can vary largely so a dedicated job system is used to decompress on to avoid blocking
//! the main job system from working.
class FullFileDecompressor
: public StreamStackEntry
{
public:
FullFileDecompressor(u32 maxNumReads, u32 maxNumJobs, u32 alignment);
~FullFileDecompressor() override = default;
void PrepareRequest(FileRequest* request) override;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void UpdateStatus(Status& status) const override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
private:
using Buffer = u8*;
enum class ReadBufferStatus : uint8_t
{
AZ_RTTI(AZ::IO::FullFileDecompressorConfig, "{C96B7EC1-8C73-4493-A7CB-66F5D550FC3A}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(FullFileDecompressorConfig, AZ::SystemAllocator, 0);
~FullFileDecompressorConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
//! Maximum number of reads that are kept in flight.
u32 m_maxNumReads{ 2 };
//! Maximum number of decompression jobs that can run simultaneously.
u32 m_maxNumJobs{ 2 };
Unused,
ReadInFlight,
PendingDecompression
};
//! Entry in the streaming stack that decompresses files from an archive that are stored
//! as single files and without equally distributed seek points.
//! Because the target archive has compressed the entire file, it needs to be decompressed
//! completely, so even if the file is partially read, it needs to be fully loaded. This
//! also means that there's no upper limit to the memory so every decompression job will
//! need to allocate memory as a temporary buffer (in-place decompression is not supported).
//! Finally, the lack of an upper limit also means that the duration of the decompression job
//! can vary largely so a dedicated job system is used to decompress on to avoid blocking
//! the main job system from working.
class FullFileDecompressor
: public StreamStackEntry
struct DecompressionInformation
{
public:
FullFileDecompressor(u32 maxNumReads, u32 maxNumJobs, u32 alignment);
~FullFileDecompressor() override = default;
bool IsProcessing() const;
void PrepareRequest(FileRequest* request) override;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void UpdateStatus(Status& status) const override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
AZStd::chrono::high_resolution_clock::time_point m_queueStartTime;
AZStd::chrono::high_resolution_clock::time_point m_jobStartTime;
Buffer m_compressedData{ nullptr };
FileRequest* m_waitRequest{ nullptr };
u32 m_alignmentOffset{ 0 };
};
private:
using Buffer = u8*;
bool IsIdle() const;
enum class ReadBufferStatus : uint8_t
{
Unused,
ReadInFlight,
PendingDecompression
};
void PrepareReadRequest(FileRequest* request, FileRequest::ReadRequestData& data);
void PrepareDedicatedCache(FileRequest* request, const RequestPath& path);
void FileExistsCheck(FileRequest* checkRequest);
struct DecompressionInformation
{
bool IsProcessing() const;
void EstimateCompressedReadRequest(FileRequest* request, AZStd::chrono::microseconds& cumulativeDelay,
AZStd::chrono::microseconds decompressionDelay, double totalDecompressionDurationUs, double totalBytesDecompressed) const;
AZStd::chrono::high_resolution_clock::time_point m_queueStartTime;
AZStd::chrono::high_resolution_clock::time_point m_jobStartTime;
Buffer m_compressedData{ nullptr };
FileRequest* m_waitRequest{ nullptr };
u32 m_alignmentOffset{ 0 };
};
void StartArchiveRead(FileRequest* compressedReadRequest);
void FinishArchiveRead(FileRequest* readRequest, u32 readSlot);
bool StartDecompressions();
void FinishDecompression(FileRequest* waitRequest, u32 jobSlot);
bool IsIdle() const;
static void FullDecompression(StreamerContext* context, DecompressionInformation& info);
static void PartialDecompression(StreamerContext* context, DecompressionInformation& info);
void PrepareReadRequest(FileRequest* request, FileRequest::ReadRequestData& data);
void PrepareDedicatedCache(FileRequest* request, const RequestPath& path);
void FileExistsCheck(FileRequest* checkRequest);
AZStd::deque<FileRequest*> m_pendingReads;
AZStd::deque<FileRequest*> m_pendingFileExistChecks;
void EstimateCompressedReadRequest(FileRequest* request, AZStd::chrono::microseconds& cumulativeDelay,
AZStd::chrono::microseconds decompressionDelay, double totalDecompressionDurationUs, double totalBytesDecompressed) const;
void StartArchiveRead(FileRequest* compressedReadRequest);
void FinishArchiveRead(FileRequest* readRequest, u32 readSlot);
bool StartDecompressions();
void FinishDecompression(FileRequest* waitRequest, u32 jobSlot);
static void FullDecompression(StreamerContext* context, DecompressionInformation& info);
static void PartialDecompression(StreamerContext* context, DecompressionInformation& info);
AZStd::deque<FileRequest*> m_pendingReads;
AZStd::deque<FileRequest*> m_pendingFileExistChecks;
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionJobDelayMicroSec;
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionDurationMicroSec;
AverageWindow<size_t, double, s_statisticsWindowSize> m_bytesDecompressed;
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionJobDelayMicroSec;
AverageWindow<size_t, double, s_statisticsWindowSize> m_decompressionDurationMicroSec;
AverageWindow<size_t, double, s_statisticsWindowSize> m_bytesDecompressed;
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
AZ::Statistics::RunningStatistic m_decompressionBoundStat;
AZ::Statistics::RunningStatistic m_readBoundStat;
AZ::Statistics::RunningStatistic m_decompressionBoundStat;
AZ::Statistics::RunningStatistic m_readBoundStat;
#endif
AZStd::unique_ptr<Buffer[]> m_readBuffers;
// Nullptr if not reading, the read request if reading the file and the wait request for decompression when waiting on decompression.
AZStd::unique_ptr<FileRequest*[]> m_readRequests;
AZStd::unique_ptr<ReadBufferStatus[]> m_readBufferStatus;
AZStd::unique_ptr<DecompressionInformation[]> m_processingJobs;
AZStd::unique_ptr<JobManager> m_decompressionJobManager;
AZStd::unique_ptr<JobContext> m_decompressionjobContext;
AZStd::unique_ptr<Buffer[]> m_readBuffers;
// Nullptr if not reading, the read request if reading the file and the wait request for decompression when waiting on decompression.
AZStd::unique_ptr<FileRequest*[]> m_readRequests;
AZStd::unique_ptr<ReadBufferStatus[]> m_readBufferStatus;
size_t m_memoryUsage{ 0 }; //!< Amount of memory used for buffers by the decompressor.
u32 m_maxNumReads{ 2 };
u32 m_numInFlightReads{ 0 };
u32 m_numPendingDecompression{ 0 };
u32 m_maxNumJobs{ 1 };
u32 m_numRunningJobs{ 0 };
u32 m_alignment{ 0 };
};
} // namespace IO
} // namespace AZ
AZStd::unique_ptr<DecompressionInformation[]> m_processingJobs;
AZStd::unique_ptr<JobManager> m_decompressionJobManager;
AZStd::unique_ptr<JobContext> m_decompressionjobContext;
size_t m_memoryUsage{ 0 }; //!< Amount of memory used for buffers by the decompressor.
u32 m_maxNumReads{ 2 };
u32 m_numInFlightReads{ 0 };
u32 m_numPendingDecompression{ 0 };
u32 m_maxNumJobs{ 1 };
u32 m_numRunningJobs{ 0 };
u32 m_alignment{ 0 };
};
} // namespace AZ::IO
@@ -227,7 +227,7 @@ namespace AZ::IO
{
auto parentReadRequest = next->GetCommandFromChain<FileRequest::ReadRequestData>();
AZ_Assert(parentReadRequest != nullptr, "The issued read request can't be found for the (compressed) read command.");
size_t size = parentReadRequest->m_size;
if (parentReadRequest->m_output == nullptr)
{
@@ -266,7 +266,7 @@ namespace AZ::IO
m_processingStartTime = AZStd::chrono::system_clock::now();
}
#endif
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
{
m_threadData.m_lastFilePath = args.m_path;
@@ -411,7 +411,7 @@ namespace AZ::IO
++pendingIt;
}
}
m_threadData.m_streamStack->QueueRequest(request);
}
@@ -23,7 +23,7 @@
namespace AZ::IO
{
class FileRequest;
class Scheduler final
{
public:
@@ -63,7 +63,7 @@ namespace AZ::IO
void Thread_ProcessTillIdle();
void Thread_ProcessCancelRequest(FileRequest* request, FileRequest::CancelData& data);
void Thread_ProcessRescheduleRequest(FileRequest* request, FileRequest::RescheduleData& data);
enum class Order
{
FirstRequest, //< The first request is the most important to process next.
@@ -16,454 +16,451 @@
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/typetraits/decay.h>
namespace AZ
namespace AZ::IO
{
namespace IO
AZStd::shared_ptr<StreamStackEntry> StorageDriveConfig::AddStreamStackEntry(
[[maybe_unused]] const HardwareInformation& hardware, [[maybe_unused]] AZStd::shared_ptr<StreamStackEntry> parent)
{
AZStd::shared_ptr<StreamStackEntry> StorageDriveConfig::AddStreamStackEntry(
[[maybe_unused]] const HardwareInformation& hardware, [[maybe_unused]] AZStd::shared_ptr<StreamStackEntry> parent)
{
return AZStd::make_shared<StorageDrive>(m_maxFileHandles);
}
return AZStd::make_shared<StorageDrive>(m_maxFileHandles);
}
void StorageDriveConfig::Reflect(AZ::ReflectContext* context)
void StorageDriveConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context); serializeContext != nullptr)
serializeContext->Class<StorageDriveConfig, IStreamerStackConfig>()
->Version(1)
->Field("MaxFileHandles", &StorageDriveConfig::m_maxFileHandles);
}
}
const AZStd::chrono::microseconds StorageDrive::s_averageSeekTime =
AZStd::chrono::milliseconds(9) + // Common average seek time for desktop hdd drives.
AZStd::chrono::milliseconds(3); // Rotational latency for a 7200RPM disk
StorageDrive::StorageDrive(u32 maxFileHandles)
: StreamStackEntry("Storage drive (generic)")
{
m_fileLastUsed.resize(maxFileHandles, AZStd::chrono::system_clock::time_point::min());
m_filePaths.resize(maxFileHandles);
m_fileHandles.resize(maxFileHandles);
// Add initial dummy values to the stats to avoid division by zero later on and avoid needing branches.
m_readSizeAverage.PushEntry(1);
m_readTimeAverage.PushEntry(AZStd::chrono::microseconds(1));
}
void StorageDrive::SetNext(AZStd::shared_ptr<StreamStackEntry> /*next*/)
{
AZ_Assert(false, "StorageDrive isn't allowed to have a node to forward requests to.");
}
void StorageDrive::PrepareRequest(FileRequest* request)
{
AZ_PROFILE_FUNCTION(AzCore);
AZ_Assert(request, "PrepareRequest was provided a null request.");
if (AZStd::holds_alternative<FileRequest::ReadRequestData>(request->GetCommand()))
{
auto& readRequest = AZStd::get<FileRequest::ReadRequestData>(request->GetCommand());
FileRequest* read = m_context->GetNewInternalRequest();
read->CreateRead(request, readRequest.m_output, readRequest.m_outputSize, readRequest.m_path,
readRequest.m_offset, readRequest.m_size);
m_context->PushPreparedRequest(read);
return;
}
StreamStackEntry::PrepareRequest(request);
}
void StorageDrive::QueueRequest(FileRequest* request)
{
AZ_Assert(request, "QueueRequest was provided a null request.");
AZStd::visit([this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData> ||
AZStd::is_same_v<Command, FileRequest::FileExistsCheckData> ||
AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
{
serializeContext->Class<StorageDriveConfig, IStreamerStackConfig>()
->Version(1)
->Field("MaxFileHandles", &StorageDriveConfig::m_maxFileHandles);
}
}
const AZStd::chrono::microseconds StorageDrive::s_averageSeekTime =
AZStd::chrono::milliseconds(9) + // Common average seek time for desktop hdd drives.
AZStd::chrono::milliseconds(3); // Rotational latency for a 7200RPM disk
StorageDrive::StorageDrive(u32 maxFileHandles)
: StreamStackEntry("Storage drive (generic)")
{
m_fileLastUsed.resize(maxFileHandles, AZStd::chrono::system_clock::time_point::min());
m_filePaths.resize(maxFileHandles);
m_fileHandles.resize(maxFileHandles);
// Add initial dummy values to the stats to avoid division by zero later on and avoid needing branches.
m_readSizeAverage.PushEntry(1);
m_readTimeAverage.PushEntry(AZStd::chrono::microseconds(1));
}
void StorageDrive::SetNext(AZStd::shared_ptr<StreamStackEntry> /*next*/)
{
AZ_Assert(false, "StorageDrive isn't allowed to have a node to forward requests to.");
}
void StorageDrive::PrepareRequest(FileRequest* request)
{
AZ_PROFILE_FUNCTION(AzCore);
AZ_Assert(request, "PrepareRequest was provided a null request.");
if (AZStd::holds_alternative<FileRequest::ReadRequestData>(request->GetCommand()))
{
auto& readRequest = AZStd::get<FileRequest::ReadRequestData>(request->GetCommand());
FileRequest* read = m_context->GetNewInternalRequest();
read->CreateRead(request, readRequest.m_output, readRequest.m_outputSize, readRequest.m_path,
readRequest.m_offset, readRequest.m_size);
m_context->PushPreparedRequest(read);
m_pendingRequests.push_back(request);
return;
}
StreamStackEntry::PrepareRequest(request);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::CancelData>)
{
CancelRequest(request, args.m_target);
return;
}
else
{
if constexpr (AZStd::is_same_v<Command, FileRequest::FlushData>)
{
FlushCache(args.m_path);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FlushAllData>)
{
FlushEntireCache();
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::ReportData>)
{
Report(args);
}
StreamStackEntry::QueueRequest(request);
}
}, request->GetCommand());
}
void StorageDrive::QueueRequest(FileRequest* request)
bool StorageDrive::ExecuteRequests()
{
if (!m_pendingRequests.empty())
{
AZ_Assert(request, "QueueRequest was provided a null request.");
FileRequest* request = m_pendingRequests.front();
AZStd::visit([this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData> ||
AZStd::is_same_v<Command, FileRequest::FileExistsCheckData> ||
AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
{
m_pendingRequests.push_back(request);
return;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::CancelData>)
{
CancelRequest(request, args.m_target);
return;
}
else
{
if constexpr (AZStd::is_same_v<Command, FileRequest::FlushData>)
{
FlushCache(args.m_path);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FlushAllData>)
{
FlushEntireCache();
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::ReportData>)
{
Report(args);
}
StreamStackEntry::QueueRequest(request);
}
}, request->GetCommand());
}
bool StorageDrive::ExecuteRequests()
{
if (!m_pendingRequests.empty())
{
FileRequest* request = m_pendingRequests.front();
AZStd::visit([this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
{
ReadFile(request);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileExistsCheckData>)
{
FileExistsRequest(request);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
{
FileMetaDataRetrievalRequest(request);
}
}, request->GetCommand());
m_pendingRequests.pop_front();
return true;
}
else
{
return false;
}
}
void StorageDrive::UpdateStatus(Status& status) const
{
// Only participate if there are actually any reads done.
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
{
s32 availableSlots = s_maxRequests - aznumeric_cast<s32>(m_pendingRequests.size());
StreamStackEntry::UpdateStatus(status);
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, availableSlots);
status.m_isIdle = status.m_isIdle && m_pendingRequests.empty();
}
else
{
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, s_maxRequests);
}
}
void StorageDrive::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now,
AZStd::vector<FileRequest*>& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin,
StreamerContext::PreparedQueue::iterator pendingEnd)
{
StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd);
const RequestPath* activeFile = nullptr;
if (m_activeCacheSlot != s_fileNotFound)
{
activeFile = &m_filePaths[m_activeCacheSlot];
}
u64 activeOffset = m_activeOffset;
// Estimate requests in this stack entry.
for (FileRequest* request : m_pendingRequests)
{
EstimateCompletionTimeForRequest(request, now, activeFile, activeOffset);
}
// Estimate internally pending requests. Because this call will go from the top of the stack to the bottom,
// but estimation is calculated from the bottom to the top, this list should be processed in reverse order.
for (auto requestIt = internalPending.rbegin(); requestIt != internalPending.rend(); ++requestIt)
{
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
}
// Estimate pending requests that have not been queued yet.
for (auto requestIt = pendingBegin; requestIt != pendingEnd; ++requestIt)
{
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
}
}
void StorageDrive::EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
const RequestPath*& activeFile, u64& activeOffset) const
{
u64 readSize = 0;
u64 offset = 0;
const RequestPath* targetFile = nullptr;
AZStd::visit([&](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
{
targetFile = &args.m_path;
readSize = args.m_size;
offset = args.m_offset;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::CompressedReadData>)
{
targetFile = &args.m_compressionInfo.m_archiveFilename;
readSize = args.m_compressionInfo.m_compressedSize;
offset = args.m_compressionInfo.m_offset;
ReadFile(request);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileExistsCheckData>)
{
readSize = 0;
AZStd::chrono::microseconds averageTime = m_getFileExistsTimeAverage.CalculateAverage();
startTime += averageTime;
FileExistsRequest(request);
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
{
readSize = 0;
AZStd::chrono::microseconds averageTime = m_getFileMetaDataTimeAverage.CalculateAverage();
startTime += averageTime;
FileMetaDataRetrievalRequest(request);
}
}, request->GetCommand());
m_pendingRequests.pop_front();
return true;
}
else
{
return false;
}
}
if (readSize > 0)
{
if (activeFile && activeFile != targetFile)
{
if (FindFileInCache(*targetFile) == s_fileNotFound)
{
AZStd::chrono::microseconds fileOpenCloseTimeAverage = m_fileOpenCloseTimeAverage.CalculateAverage();
startTime += fileOpenCloseTimeAverage;
}
startTime += s_averageSeekTime;
activeOffset = std::numeric_limits<u64>::max();
}
else if (activeOffset != offset)
{
startTime += s_averageSeekTime;
}
void StorageDrive::UpdateStatus(Status& status) const
{
// Only participate if there are actually any reads done.
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
{
s32 availableSlots = s_maxRequests - aznumeric_cast<s32>(m_pendingRequests.size());
StreamStackEntry::UpdateStatus(status);
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, availableSlots);
status.m_isIdle = status.m_isIdle && m_pendingRequests.empty();
}
else
{
status.m_numAvailableSlots = AZStd::min(status.m_numAvailableSlots, s_maxRequests);
}
}
u64 totalBytesRead = m_readSizeAverage.GetTotal();
double totalReadTimeUSec = aznumeric_caster(m_readTimeAverage.GetTotal().count());
startTime += AZStd::chrono::microseconds(aznumeric_cast<u64>((readSize * totalReadTimeUSec) / totalBytesRead));
activeOffset = offset + readSize;
}
request->SetEstimatedCompletion(startTime);
void StorageDrive::UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now,
AZStd::vector<FileRequest*>& internalPending, StreamerContext::PreparedQueue::iterator pendingBegin,
StreamerContext::PreparedQueue::iterator pendingEnd)
{
StreamStackEntry::UpdateCompletionEstimates(now, internalPending, pendingBegin, pendingEnd);
const RequestPath* activeFile = nullptr;
if (m_activeCacheSlot != s_fileNotFound)
{
activeFile = &m_filePaths[m_activeCacheSlot];
}
u64 activeOffset = m_activeOffset;
// Estimate requests in this stack entry.
for (FileRequest* request : m_pendingRequests)
{
EstimateCompletionTimeForRequest(request, now, activeFile, activeOffset);
}
void StorageDrive::ReadFile(FileRequest* request)
// Estimate internally pending requests. Because this call will go from the top of the stack to the bottom,
// but estimation is calculated from the bottom to the top, this list should be processed in reverse order.
for (auto requestIt = internalPending.rbegin(); requestIt != internalPending.rend(); ++requestIt)
{
AZ_PROFILE_FUNCTION(AzCore);
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
AZ_Assert(data, "FileRequest queued on StorageDrive to be read didn't contain read data.");
SystemFile* file = nullptr;
// If the file is already open, use that file handle and update it's last touched time.
size_t cacheIndex = FindFileInCache(data->m_path);
if (cacheIndex != s_fileNotFound)
{
file = m_fileHandles[cacheIndex].get();
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
}
// If the file is not open, eject the entry from the cache that hasn't been used for the longest time
// and open the file for reading.
if (!file)
{
AZStd::chrono::system_clock::time_point oldest = m_fileLastUsed[0];
cacheIndex = 0;
size_t numFiles = m_filePaths.size();
for (size_t i = 1; i < numFiles; ++i)
{
if (m_fileLastUsed[i] < oldest)
{
oldest = m_fileLastUsed[i];
cacheIndex = i;
}
}
TIMED_AVERAGE_WINDOW_SCOPE(m_fileOpenCloseTimeAverage);
AZStd::unique_ptr<SystemFile> newFile = AZStd::make_unique<SystemFile>();
bool isOpen = newFile->Open(data->m_path.GetAbsolutePath(), SystemFile::OpenMode::SF_OPEN_READ_ONLY);
if (!isOpen)
{
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
return;
}
file = newFile.get();
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
m_fileHandles[cacheIndex] = AZStd::move(newFile);
m_filePaths[cacheIndex] = data->m_path;
}
AZ_Assert(file, "While searching for file '%s' StorageDevice::ReadFile failed to detect a problem.", data->m_path.GetRelativePath());
u64 bytesRead = 0;
{
TIMED_AVERAGE_WINDOW_SCOPE(m_readTimeAverage);
if (file->Tell() != data->m_offset)
{
file->Seek(data->m_offset, SystemFile::SeekMode::SF_SEEK_BEGIN);
}
bytesRead = file->Read(data->m_size, data->m_output);
}
m_readSizeAverage.PushEntry(bytesRead);
m_activeCacheSlot = cacheIndex;
m_activeOffset = data->m_offset + bytesRead;
request->SetStatus(bytesRead == data->m_size ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
}
void StorageDrive::CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target)
// Estimate pending requests that have not been queued yet.
for (auto requestIt = pendingBegin; requestIt != pendingEnd; ++requestIt)
{
for (auto it = m_pendingRequests.begin(); it != m_pendingRequests.end();)
EstimateCompletionTimeForRequest(*requestIt, now, activeFile, activeOffset);
}
}
void StorageDrive::EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
const RequestPath*& activeFile, u64& activeOffset) const
{
u64 readSize = 0;
u64 offset = 0;
const RequestPath* targetFile = nullptr;
AZStd::visit([&](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, FileRequest::ReadData>)
{
if ((*it)->WorksOn(target))
{
(*it)->SetStatus(IStreamerTypes::RequestStatus::Canceled);
m_context->MarkRequestAsCompleted(*it);
it = m_pendingRequests.erase(it);
}
else
{
++it;
}
targetFile = &args.m_path;
readSize = args.m_size;
offset = args.m_offset;
}
cancelRequest->SetStatus(IStreamerTypes::RequestStatus::Completed);
m_context->MarkRequestAsCompleted(cancelRequest);
else if constexpr (AZStd::is_same_v<Command, FileRequest::CompressedReadData>)
{
targetFile = &args.m_compressionInfo.m_archiveFilename;
readSize = args.m_compressionInfo.m_compressedSize;
offset = args.m_compressionInfo.m_offset;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileExistsCheckData>)
{
readSize = 0;
AZStd::chrono::microseconds averageTime = m_getFileExistsTimeAverage.CalculateAverage();
startTime += averageTime;
}
else if constexpr (AZStd::is_same_v<Command, FileRequest::FileMetaDataRetrievalData>)
{
readSize = 0;
AZStd::chrono::microseconds averageTime = m_getFileMetaDataTimeAverage.CalculateAverage();
startTime += averageTime;
}
}, request->GetCommand());
if (readSize > 0)
{
if (activeFile && activeFile != targetFile)
{
if (FindFileInCache(*targetFile) == s_fileNotFound)
{
AZStd::chrono::microseconds fileOpenCloseTimeAverage = m_fileOpenCloseTimeAverage.CalculateAverage();
startTime += fileOpenCloseTimeAverage;
}
startTime += s_averageSeekTime;
activeOffset = std::numeric_limits<u64>::max();
}
else if (activeOffset != offset)
{
startTime += s_averageSeekTime;
}
u64 totalBytesRead = m_readSizeAverage.GetTotal();
double totalReadTimeUSec = aznumeric_caster(m_readTimeAverage.GetTotal().count());
startTime += AZStd::chrono::microseconds(aznumeric_cast<u64>((readSize * totalReadTimeUSec) / totalBytesRead));
activeOffset = offset + readSize;
}
request->SetEstimatedCompletion(startTime);
}
void StorageDrive::ReadFile(FileRequest* request)
{
AZ_PROFILE_FUNCTION(AzCore);
auto data = AZStd::get_if<FileRequest::ReadData>(&request->GetCommand());
AZ_Assert(data, "FileRequest queued on StorageDrive to be read didn't contain read data.");
SystemFile* file = nullptr;
// If the file is already open, use that file handle and update it's last touched time.
size_t cacheIndex = FindFileInCache(data->m_path);
if (cacheIndex != s_fileNotFound)
{
file = m_fileHandles[cacheIndex].get();
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
}
void StorageDrive::FileExistsRequest(FileRequest* request)
// If the file is not open, eject the entry from the cache that hasn't been used for the longest time
// and open the file for reading.
if (!file)
{
AZ_PROFILE_FUNCTION(AzCore);
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileExistsTimeAverage);
auto& fileExists = AZStd::get<FileRequest::FileExistsCheckData>(request->GetCommand());
size_t cacheIndex = FindFileInCache(fileExists.m_path);
if (cacheIndex != s_fileNotFound)
AZStd::chrono::system_clock::time_point oldest = m_fileLastUsed[0];
cacheIndex = 0;
size_t numFiles = m_filePaths.size();
for (size_t i = 1; i < numFiles; ++i)
{
fileExists.m_found = true;
if (m_fileLastUsed[i] < oldest)
{
oldest = m_fileLastUsed[i];
cacheIndex = i;
}
}
TIMED_AVERAGE_WINDOW_SCOPE(m_fileOpenCloseTimeAverage);
AZStd::unique_ptr<SystemFile> newFile = AZStd::make_unique<SystemFile>();
bool isOpen = newFile->Open(data->m_path.GetAbsolutePath(), SystemFile::OpenMode::SF_OPEN_READ_ONLY);
if (!isOpen)
{
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
return;
}
file = newFile.get();
m_fileLastUsed[cacheIndex] = AZStd::chrono::high_resolution_clock::now();
m_fileHandles[cacheIndex] = AZStd::move(newFile);
m_filePaths[cacheIndex] = data->m_path;
}
AZ_Assert(file, "While searching for file '%s' StorageDevice::ReadFile failed to detect a problem.", data->m_path.GetRelativePath());
u64 bytesRead = 0;
{
TIMED_AVERAGE_WINDOW_SCOPE(m_readTimeAverage);
if (file->Tell() != data->m_offset)
{
file->Seek(data->m_offset, SystemFile::SeekMode::SF_SEEK_BEGIN);
}
bytesRead = file->Read(data->m_size, data->m_output);
}
m_readSizeAverage.PushEntry(bytesRead);
m_activeCacheSlot = cacheIndex;
m_activeOffset = data->m_offset + bytesRead;
request->SetStatus(bytesRead == data->m_size ? IStreamerTypes::RequestStatus::Completed : IStreamerTypes::RequestStatus::Failed);
m_context->MarkRequestAsCompleted(request);
}
void StorageDrive::CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target)
{
for (auto it = m_pendingRequests.begin(); it != m_pendingRequests.end();)
{
if ((*it)->WorksOn(target))
{
(*it)->SetStatus(IStreamerTypes::RequestStatus::Canceled);
m_context->MarkRequestAsCompleted(*it);
it = m_pendingRequests.erase(it);
}
else
{
fileExists.m_found = SystemFile::Exists(fileExists.m_path.GetAbsolutePath());
++it;
}
m_context->MarkRequestAsCompleted(request);
}
cancelRequest->SetStatus(IStreamerTypes::RequestStatus::Completed);
m_context->MarkRequestAsCompleted(cancelRequest);
}
void StorageDrive::FileMetaDataRetrievalRequest(FileRequest* request)
void StorageDrive::FileExistsRequest(FileRequest* request)
{
AZ_PROFILE_FUNCTION(AzCore);
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileExistsTimeAverage);
auto& fileExists = AZStd::get<FileRequest::FileExistsCheckData>(request->GetCommand());
size_t cacheIndex = FindFileInCache(fileExists.m_path);
if (cacheIndex != s_fileNotFound)
{
AZ_PROFILE_FUNCTION(AzCore);
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileMetaDataTimeAverage);
fileExists.m_found = true;
}
else
{
fileExists.m_found = SystemFile::Exists(fileExists.m_path.GetAbsolutePath());
}
m_context->MarkRequestAsCompleted(request);
}
auto& command = AZStd::get<FileRequest::FileMetaDataRetrievalData>(request->GetCommand());
// If the file is already open, use the file handle which usually is cheaper than asking for the file by name.
size_t cacheIndex = FindFileInCache(command.m_path);
if (cacheIndex != s_fileNotFound)
void StorageDrive::FileMetaDataRetrievalRequest(FileRequest* request)
{
AZ_PROFILE_FUNCTION(AzCore);
TIMED_AVERAGE_WINDOW_SCOPE(m_getFileMetaDataTimeAverage);
auto& command = AZStd::get<FileRequest::FileMetaDataRetrievalData>(request->GetCommand());
// If the file is already open, use the file handle which usually is cheaper than asking for the file by name.
size_t cacheIndex = FindFileInCache(command.m_path);
if (cacheIndex != s_fileNotFound)
{
AZ_Assert(m_fileHandles[cacheIndex],
"File path '%s' doesn't have an associated file handle.", m_filePaths[cacheIndex].GetRelativePath());
command.m_fileSize = m_fileHandles[cacheIndex]->Length();
command.m_found = true;
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
}
else
{
// The file is not open yet, so try to get the file size by name.
u64 size = SystemFile::Length(command.m_path.GetAbsolutePath());
if (size != 0) // SystemFile::Length doesn't allow telling a zero-sized file apart from a invalid path.
{
AZ_Assert(m_fileHandles[cacheIndex],
"File path '%s' doesn't have an associated file handle.", m_filePaths[cacheIndex].GetRelativePath());
command.m_fileSize = m_fileHandles[cacheIndex]->Length();
command.m_fileSize = size;
command.m_found = true;
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
}
else
{
// The file is not open yet, so try to get the file size by name.
u64 size = SystemFile::Length(command.m_path.GetAbsolutePath());
if (size != 0) // SystemFile::Length doesn't allow telling a zero-sized file apart from a invalid path.
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
}
}
m_context->MarkRequestAsCompleted(request);
}
void StorageDrive::FlushCache(const RequestPath& filePath)
{
size_t cacheIndex = FindFileInCache(filePath);
if (cacheIndex != s_fileNotFound)
{
m_fileLastUsed[cacheIndex] = AZStd::chrono::system_clock::time_point();
m_fileHandles[cacheIndex].reset();
m_filePaths[cacheIndex].Clear();
}
}
void StorageDrive::FlushEntireCache()
{
size_t numFiles = m_filePaths.size();
for (size_t i = 0; i < numFiles; ++i)
{
m_fileLastUsed[i] = AZStd::chrono::system_clock::time_point();
m_fileHandles[i].reset();
m_filePaths[i].Clear();
}
}
size_t StorageDrive::FindFileInCache(const RequestPath& filePath) const
{
size_t numFiles = m_filePaths.size();
for (size_t i = 0; i < numFiles; ++i)
{
if (m_filePaths[i] == filePath)
{
return i;
}
}
return s_fileNotFound;
}
void StorageDrive::CollectStatistics(AZStd::vector<Statistic>& statistics) const
{
constexpr double bytesToMB = (1024.0 * 1024.0);
using DoubleSeconds = AZStd::chrono::duration<double>;
double totalBytesReadMB = m_readSizeAverage.GetTotal() / bytesToMB;
double totalReadTimeSec = AZStd::chrono::duration_cast<DoubleSeconds>(m_readTimeAverage.GetTotal()).count();
if (m_readSizeAverage.GetTotal() > 1) // A default value is always added.
{
statistics.push_back(Statistic::CreateFloat(m_name, "Read Speed (avg. mbps)", totalBytesReadMB / totalReadTimeSec));
}
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
{
statistics.push_back(Statistic::CreateInteger(m_name, "File Open & Close (avg. us)", m_fileOpenCloseTimeAverage.CalculateAverage().count()));
statistics.push_back(Statistic::CreateInteger(m_name, "Get file exists (avg. us)", m_getFileExistsTimeAverage.CalculateAverage().count()));
statistics.push_back(Statistic::CreateInteger(m_name, "Get file meta data (avg. us)", m_getFileMetaDataTimeAverage.CalculateAverage().count()));
statistics.push_back(Statistic::CreateInteger(m_name, "Available slots", s64{ s_maxRequests } - m_pendingRequests.size()));
}
}
void StorageDrive::Report(const FileRequest::ReportData& data) const
{
switch (data.m_reportType)
{
case FileRequest::ReportData::ReportType::FileLocks:
for (u32 i = 0; i < m_fileHandles.size(); ++i)
{
if (m_fileHandles[i] != nullptr)
{
command.m_fileSize = size;
command.m_found = true;
request->SetStatus(IStreamerTypes::RequestStatus::Completed);
}
else
{
request->SetStatus(IStreamerTypes::RequestStatus::Failed);
AZ_Printf("Streamer", "File lock in %s : '%s'.\n", m_name.c_str(), m_filePaths[i].GetRelativePath());
}
}
m_context->MarkRequestAsCompleted(request);
break;
default:
break;
}
void StorageDrive::FlushCache(const RequestPath& filePath)
{
size_t cacheIndex = FindFileInCache(filePath);
if (cacheIndex != s_fileNotFound)
{
m_fileLastUsed[cacheIndex] = AZStd::chrono::system_clock::time_point();
m_fileHandles[cacheIndex].reset();
m_filePaths[cacheIndex].Clear();
}
}
void StorageDrive::FlushEntireCache()
{
size_t numFiles = m_filePaths.size();
for (size_t i = 0; i < numFiles; ++i)
{
m_fileLastUsed[i] = AZStd::chrono::system_clock::time_point();
m_fileHandles[i].reset();
m_filePaths[i].Clear();
}
}
size_t StorageDrive::FindFileInCache(const RequestPath& filePath) const
{
size_t numFiles = m_filePaths.size();
for (size_t i = 0; i < numFiles; ++i)
{
if (m_filePaths[i] == filePath)
{
return i;
}
}
return s_fileNotFound;
}
void StorageDrive::CollectStatistics(AZStd::vector<Statistic>& statistics) const
{
constexpr double bytesToMB = (1024.0 * 1024.0);
using DoubleSeconds = AZStd::chrono::duration<double>;
double totalBytesReadMB = m_readSizeAverage.GetTotal() / bytesToMB;
double totalReadTimeSec = AZStd::chrono::duration_cast<DoubleSeconds>(m_readTimeAverage.GetTotal()).count();
if (m_readSizeAverage.GetTotal() > 1) // A default value is always added.
{
statistics.push_back(Statistic::CreateFloat(m_name, "Read Speed (avg. mbps)", totalBytesReadMB / totalReadTimeSec));
}
if (m_fileOpenCloseTimeAverage.GetNumRecorded() > 0)
{
statistics.push_back(Statistic::CreateInteger(m_name, "File Open & Close (avg. us)", m_fileOpenCloseTimeAverage.CalculateAverage().count()));
statistics.push_back(Statistic::CreateInteger(m_name, "Get file exists (avg. us)", m_getFileExistsTimeAverage.CalculateAverage().count()));
statistics.push_back(Statistic::CreateInteger(m_name, "Get file meta data (avg. us)", m_getFileMetaDataTimeAverage.CalculateAverage().count()));
statistics.push_back(Statistic::CreateInteger(m_name, "Available slots", s64{ s_maxRequests } - m_pendingRequests.size()));
}
}
void StorageDrive::Report(const FileRequest::ReportData& data) const
{
switch (data.m_reportType)
{
case FileRequest::ReportData::ReportType::FileLocks:
for (u32 i = 0; i < m_fileHandles.size(); ++i)
{
if (m_fileHandles[i] != nullptr)
{
AZ_Printf("Streamer", "File lock in %s : '%s'.\n", m_name.c_str(), m_filePaths[i].GetRelativePath());
}
}
break;
default:
break;
}
}
} // namespace IO
} // namespace AZ
}
} // namespace AZ::IO
@@ -16,85 +16,82 @@
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/chrono/clocks.h>
namespace AZ
namespace AZ::IO
{
namespace IO
struct StorageDriveConfig final :
public IStreamerStackConfig
{
struct StorageDriveConfig final :
public IStreamerStackConfig
{
AZ_RTTI(AZ::IO::StorageDriveConfig, "{3D568902-6C09-4E9E-A4DB-8B561481D298}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(StorageDriveConfig, AZ::SystemAllocator, 0);
AZ_RTTI(AZ::IO::StorageDriveConfig, "{3D568902-6C09-4E9E-A4DB-8B561481D298}", IStreamerStackConfig);
AZ_CLASS_ALLOCATOR(StorageDriveConfig, AZ::SystemAllocator, 0);
~StorageDriveConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
~StorageDriveConfig() override = default;
AZStd::shared_ptr<StreamStackEntry> AddStreamStackEntry(
const HardwareInformation& hardware, AZStd::shared_ptr<StreamStackEntry> parent) override;
static void Reflect(AZ::ReflectContext* context);
u32 m_maxFileHandles{1024};
};
u32 m_maxFileHandles{1024};
};
//! Platform agnostic version of a storage drive, such as hdd, ssd, dvd, etc.
//! This stream stack entry is responsible for accessing a storage drive to
//! retrieve file information and data.
//! This entry is designed as a catch-all for any reads that weren't handled
//! by platform specific implementations or the virtual file system. It should
//! by the last entry in the stack as it will not forward calls to the next entry.
class StorageDrive
: public StreamStackEntry
{
public:
explicit StorageDrive(u32 maxFileHandles);
~StorageDrive() override = default;
//! Platform agnostic version of a storage drive, such as hdd, ssd, dvd, etc.
//! This stream stack entry is responsible for accessing a storage drive to
//! retrieve file information and data.
//! This entry is designed as a catch-all for any reads that weren't handled
//! by platform specific implementations or the virtual file system. It should
//! by the last entry in the stack as it will not forward calls to the next entry.
class StorageDrive
: public StreamStackEntry
{
public:
explicit StorageDrive(u32 maxFileHandles);
~StorageDrive() override = default;
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
void SetNext(AZStd::shared_ptr<StreamStackEntry> next) override;
void PrepareRequest(FileRequest* request) override;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void PrepareRequest(FileRequest* request) override;
void QueueRequest(FileRequest* request) override;
bool ExecuteRequests() override;
void UpdateStatus(Status& status) const override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
void UpdateStatus(Status& status) const override;
void UpdateCompletionEstimates(AZStd::chrono::system_clock::time_point now, AZStd::vector<FileRequest*>& internalPending,
StreamerContext::PreparedQueue::iterator pendingBegin, StreamerContext::PreparedQueue::iterator pendingEnd) override;
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
void CollectStatistics(AZStd::vector<Statistic>& statistics) const override;
protected:
static const AZStd::chrono::microseconds s_averageSeekTime;
static constexpr s32 s_maxRequests = 1;
protected:
static const AZStd::chrono::microseconds s_averageSeekTime;
static constexpr s32 s_maxRequests = 1;
size_t FindFileInCache(const RequestPath& filePath) const;
void ReadFile(FileRequest* request);
void CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target);
void FileExistsRequest(FileRequest* request);
void FileMetaDataRetrievalRequest(FileRequest* request);
void FlushCache(const RequestPath& filePath);
void FlushEntireCache();
size_t FindFileInCache(const RequestPath& filePath) const;
void ReadFile(FileRequest* request);
void CancelRequest(FileRequest* cancelRequest, FileRequestPtr& target);
void FileExistsRequest(FileRequest* request);
void FileMetaDataRetrievalRequest(FileRequest* request);
void FlushCache(const RequestPath& filePath);
void FlushEntireCache();
void EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
const RequestPath*& activeFile, u64& activeOffset) const;
void EstimateCompletionTimeForRequest(FileRequest* request, AZStd::chrono::system_clock::time_point& startTime,
const RequestPath*& activeFile, u64& activeOffset) const;
void Report(const FileRequest::ReportData& data) const;
void Report(const FileRequest::ReportData& data) const;
TimedAverageWindow<s_statisticsWindowSize> m_fileOpenCloseTimeAverage;
TimedAverageWindow<s_statisticsWindowSize> m_getFileExistsTimeAverage;
TimedAverageWindow<s_statisticsWindowSize> m_getFileMetaDataTimeAverage;
TimedAverageWindow<s_statisticsWindowSize> m_readTimeAverage;
AverageWindow<u64, float, s_statisticsWindowSize> m_readSizeAverage;
//! File requests that are queued for processing.
AZStd::deque<FileRequest*> m_pendingRequests;
TimedAverageWindow<s_statisticsWindowSize> m_fileOpenCloseTimeAverage;
TimedAverageWindow<s_statisticsWindowSize> m_getFileExistsTimeAverage;
TimedAverageWindow<s_statisticsWindowSize> m_getFileMetaDataTimeAverage;
TimedAverageWindow<s_statisticsWindowSize> m_readTimeAverage;
AverageWindow<u64, float, s_statisticsWindowSize> m_readSizeAverage;
//! File requests that are queued for processing.
AZStd::deque<FileRequest*> m_pendingRequests;
//! The last time a file handle was used to access a file. The handle is stored in m_fileHandles.
AZStd::vector<AZStd::chrono::system_clock::time_point> m_fileLastUsed;
//! The file path to the file handle. The handle is stored in m_fileHandles.
AZStd::vector<RequestPath> m_filePaths;
//! A list of file handles that's being cached in case they're needed again in the future.
AZStd::vector<AZStd::unique_ptr<SystemFile>> m_fileHandles;
//! The last time a file handle was used to access a file. The handle is stored in m_fileHandles.
AZStd::vector<AZStd::chrono::system_clock::time_point> m_fileLastUsed;
//! The file path to the file handle. The handle is stored in m_fileHandles.
AZStd::vector<RequestPath> m_filePaths;
//! A list of file handles that's being cached in case they're needed again in the future.
AZStd::vector<AZStd::unique_ptr<SystemFile>> m_fileHandles;
//! The offset into the file that's cached by the active cache slot.
u64 m_activeOffset = 0;
//! The index into m_fileHandles for the file that's currently being read.
size_t m_activeCacheSlot = s_fileNotFound;
};
} // namespace IO
} // namespace AZ
//! The offset into the file that's cached by the active cache slot.
u64 m_activeOffset = 0;
//! The index into m_fileHandles for the file that's currently being read.
size_t m_activeCacheSlot = s_fileNotFound;
};
} // namespace AZ::IO
@@ -219,7 +219,7 @@ namespace AZ::IO
{
AZ_Assert(HasRequestCompleted(request), "Claiming memory from a read request that's still in progress. "
"This can lead to crashing if data is still being streamed to the request's buffer.");
// The caller has claimed the buffer and is now responsible for clearing it.
// The caller has claimed the buffer and is now responsible for clearing it.
readRequest->m_allocator->UnlockAllocator();
readRequest->m_allocator = nullptr;
}
@@ -293,7 +293,7 @@ namespace AZ::IO
request->m_request.CreateReport(reportType);
return request;
}
Streamer::Streamer(const AZStd::thread_desc& threadDesc, AZStd::unique_ptr<Scheduler> streamStack)
: m_streamStack(AZStd::move(streamStack))
{
@@ -17,116 +17,113 @@
#include <AzCore/std/containers/queue.h>
#include <AzCore/Statistics/RunningStatistic.h>
namespace AZ
namespace AZ::IO
{
namespace IO
class StreamerContext
{
class StreamerContext
{
public:
using PreparedQueue = AZStd::deque<FileRequest*>;
public:
using PreparedQueue = AZStd::deque<FileRequest*>;
~StreamerContext();
~StreamerContext();
//! Gets a new file request, either by creating a new instance or
//! picking one from the recycle bin. This version should only be used
//! by nodes on the streaming stack as it's not thread safe, but faster.
//! The scheduler will automatically recycle these requests.
FileRequest* GetNewInternalRequest();
//! Gets a new file request, either by creating a new instance or
//! picking one from the recycle bin. This version is for use by
//! any system outside the stream stack and is thread safe. Once the
//! reference count in the request hits zero it will automatically be recycled.
FileRequestPtr GetNewExternalRequest();
//! Gets a batch of new file requests, either by creating new instances or
//! picking from the recycle bin. This version is for use by
//! any system outside the stream stack and is thread safe. The owner
//! needs to manually recycle these requests once they're done. Requests
//! with a reference count of zero will automatically be recycled.
//! If multiple requests need to be create this is preferable as it only locks the
//! recycle bin once.
void GetNewExternalRequestBatch(AZStd::vector<FileRequestPtr>& requests, size_t count);
//! Gets a new file request, either by creating a new instance or
//! picking one from the recycle bin. This version should only be used
//! by nodes on the streaming stack as it's not thread safe, but faster.
//! The scheduler will automatically recycle these requests.
FileRequest* GetNewInternalRequest();
//! Gets a new file request, either by creating a new instance or
//! picking one from the recycle bin. This version is for use by
//! any system outside the stream stack and is thread safe. Once the
//! reference count in the request hits zero it will automatically be recycled.
FileRequestPtr GetNewExternalRequest();
//! Gets a batch of new file requests, either by creating new instances or
//! picking from the recycle bin. This version is for use by
//! any system outside the stream stack and is thread safe. The owner
//! needs to manually recycle these requests once they're done. Requests
//! with a reference count of zero will automatically be recycled.
//! If multiple requests need to be create this is preferable as it only locks the
//! recycle bin once.
void GetNewExternalRequestBatch(AZStd::vector<FileRequestPtr>& requests, size_t count);
//! Gets the number of prepared requests. Prepared requests are requests
//! that are ready to be queued up for further processing.
size_t GetNumPreparedRequests() const;
//! Gets the next prepared request that should be queued. Prepared requests
//! are requests that are ready to be queued up for further processing.
FileRequest* PopPreparedRequest();
//! Adds a prepared request for later queuing and processing.
void PushPreparedRequest(FileRequest* request);
//! Gets the prepared requests that are queued to be processed.
PreparedQueue& GetPreparedRequests();
//! Gets the prepared requests that are queued to be processed.
const PreparedQueue& GetPreparedRequests() const;
//! Gets the number of prepared requests. Prepared requests are requests
//! that are ready to be queued up for further processing.
size_t GetNumPreparedRequests() const;
//! Gets the next prepared request that should be queued. Prepared requests
//! are requests that are ready to be queued up for further processing.
FileRequest* PopPreparedRequest();
//! Adds a prepared request for later queuing and processing.
void PushPreparedRequest(FileRequest* request);
//! Gets the prepared requests that are queued to be processed.
PreparedQueue& GetPreparedRequests();
//! Gets the prepared requests that are queued to be processed.
const PreparedQueue& GetPreparedRequests() const;
//! Marks a request as completed so the main thread in Streamer can close it out.
//! This can be safely called from multiple threads.
void MarkRequestAsCompleted(FileRequest* request);
//! Rejects a request by removing it from the chain and recycling it.
//! Only requests without children can be rejected. If the rejected request has a parent it might need to be processed
//! further.
//! @param request The request to remove and recycle.
//! @return The parent request of the rejected request or null if there was no parent.
FileRequest* RejectRequest(FileRequest* request);
//! Adds an old request to the recycle bin so it can be reused later.
void RecycleRequest(FileRequest* request);
//! Adds an old external request to the recycle bin so it can be reused later.
void RecycleRequest(ExternalFileRequest* request);
//! Marks a request as completed so the main thread in Streamer can close it out.
//! This can be safely called from multiple threads.
void MarkRequestAsCompleted(FileRequest* request);
//! Rejects a request by removing it from the chain and recycling it.
//! Only requests without children can be rejected. If the rejected request has a parent it might need to be processed
//! further.
//! @param request The request to remove and recycle.
//! @return The parent request of the rejected request or null if there was no parent.
FileRequest* RejectRequest(FileRequest* request);
//! Adds an old request to the recycle bin so it can be reused later.
void RecycleRequest(FileRequest* request);
//! Adds an old external request to the recycle bin so it can be reused later.
void RecycleRequest(ExternalFileRequest* request);
//! Does the FinalizeRequest callback where appropriate and does some bookkeeping to finalize requests.
//! @return True if any requests were finalized, otherwise false.
bool FinalizeCompletedRequests();
//! Does the FinalizeRequest callback where appropriate and does some bookkeeping to finalize requests.
//! @return True if any requests were finalized, otherwise false.
bool FinalizeCompletedRequests();
//! Causes the main thread for streamer to wake up and process any pending requests. If the thread
//! is already awake, nothing happens.
void WakeUpSchedulingThread();
//! If there's no pending messages this will cause the main thread for streamer to go to sleep.
void SuspendSchedulingThread();
//! Returns the native primitive(s) used to suspend and wake up the scheduling thread and possibly other threads.
AZ::Platform::StreamerContextThreadSync& GetStreamerThreadSynchronizer();
//! Causes the main thread for streamer to wake up and process any pending requests. If the thread
//! is already awake, nothing happens.
void WakeUpSchedulingThread();
//! If there's no pending messages this will cause the main thread for streamer to go to sleep.
void SuspendSchedulingThread();
//! Returns the native primitive(s) used to suspend and wake up the scheduling thread and possibly other threads.
AZ::Platform::StreamerContextThreadSync& GetStreamerThreadSynchronizer();
//! Collects statistics recorded during processing. This will only return statistics for the
//! context. Use the CollectStatistics on AZ::IO::Streamer to get all statistics.
void CollectStatistics(AZStd::vector<Statistic>& statistics);
//! Collects statistics recorded during processing. This will only return statistics for the
//! context. Use the CollectStatistics on AZ::IO::Streamer to get all statistics.
void CollectStatistics(AZStd::vector<Statistic>& statistics);
private:
//! Gets a new FileRequestPtr. This version is for internal use only and is not thread-safe.
//! This will be called by GetNewExternalRequest or GetNewExternalRequestBatch which are responsible
//! for managing the lock to the recycle bin.
FileRequestPtr GetNewExternalRequestUnguarded();
private:
//! Gets a new FileRequestPtr. This version is for internal use only and is not thread-safe.
//! This will be called by GetNewExternalRequest or GetNewExternalRequestBatch which are responsible
//! for managing the lock to the recycle bin.
FileRequestPtr GetNewExternalRequestUnguarded();
inline static constexpr size_t s_initialRecycleBinSize = 64;
inline static constexpr size_t s_initialRecycleBinSize = 64;
AZStd::mutex m_externalRecycleBinGuard;
AZStd::vector<ExternalFileRequest*> m_externalRecycleBin;
AZStd::vector<FileRequest*> m_internalRecycleBin;
// The completion is guarded so other threads can perform async IO and safely mark requests as completed.
AZStd::recursive_mutex m_completedGuard;
AZStd::queue<FileRequest*> m_completed;
AZStd::mutex m_externalRecycleBinGuard;
AZStd::vector<ExternalFileRequest*> m_externalRecycleBin;
AZStd::vector<FileRequest*> m_internalRecycleBin;
// The prepared request queue is not guarded and should only be called from the main Streamer thread.
PreparedQueue m_preparedRequests;
// The completion is guarded so other threads can perform async IO and safely mark requests as completed.
AZStd::recursive_mutex m_completedGuard;
AZStd::queue<FileRequest*> m_completed;
// The prepared request queue is not guarded and should only be called from the main Streamer thread.
PreparedQueue m_preparedRequests;
#if AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
//! By how much time the prediction was off. This mostly covers the latter part of scheduling, which
//! gets more precise the closer the request gets to completion.
AZ::Statistics::RunningStatistic m_predictionAccuracyUsStat;
//! By how much time the prediction was off. This mostly covers the latter part of scheduling, which
//! gets more precise the closer the request gets to completion.
AZ::Statistics::RunningStatistic m_predictionAccuracyUsStat;
//! Tracks the percentage of requests with late predictions where the request completed earlier than expected,
//! versus the requests that completed later than predicted.
AZ::Statistics::RunningStatistic m_latePredictionsPercentageStat;
//! Tracks the percentage of requests with late predictions where the request completed earlier than expected,
//! versus the requests that completed later than predicted.
AZ::Statistics::RunningStatistic m_latePredictionsPercentageStat;
//! Percentage of requests that missed their deadline. If percentage is too high it can indicate that
//! there are too many file requests or the deadlines for requests are too tight.
AZ::Statistics::RunningStatistic m_missedDeadlinePercentageStat;
//! Percentage of requests that missed their deadline. If percentage is too high it can indicate that
//! there are too many file requests or the deadlines for requests are too tight.
AZ::Statistics::RunningStatistic m_missedDeadlinePercentageStat;
#endif // AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
//! Platform-specific synchronization object used to suspend the Streamer thread and wake it up to resume procesing.
AZ::Platform::StreamerContextThreadSync m_threadSync;
//! Platform-specific synchronization object used to suspend the Streamer thread and wake it up to resume procesing.
AZ::Platform::StreamerContextThreadSync m_threadSync;
size_t m_pendingIdCounter{ 0 };
};
} // namespace IO
} // namespace AZ
size_t m_pendingIdCounter{ 0 };
};
} // namespace AZ::IO
@@ -109,6 +109,7 @@ namespace AZ
*/
static EnvironmentVariable<T*> s_instance;
static AZStd::shared_mutex s_mutex;
static bool s_instanceAssigned;
};
template <typename T>
@@ -117,6 +118,9 @@ namespace AZ
template <typename T>
AZStd::shared_mutex Interface<T>::s_mutex;
template <typename T>
bool Interface<T>::s_instanceAssigned;
template <typename T>
void Interface<T>::Register(T* type)
{
@@ -135,18 +139,19 @@ namespace AZ
AZStd::unique_lock<AZStd::shared_mutex> lock(s_mutex);
s_instance = Environment::CreateVariable<T*>(GetVariableName());
s_instance.Get() = type;
s_instanceAssigned = true;
}
template <typename T>
void Interface<T>::Unregister(T* type)
{
if (!s_instance || !s_instance.Get())
if (!s_instanceAssigned)
{
AZ_Assert(false, "Interface '%s' not registered on this module!", AzTypeInfo<T>::Name());
return;
}
if (s_instance.Get() != type)
if (s_instance && s_instance.Get() != type)
{
AZ_Assert(false, "Interface '%s' is not the same instance that was registered! [Expected '%p', Found '%p']", AzTypeInfo<T>::Name(), type, s_instance.Get());
return;
@@ -156,6 +161,7 @@ namespace AZ
AZStd::unique_lock<AZStd::shared_mutex> lock(s_mutex);
*s_instance = nullptr;
s_instance.Reset();
s_instanceAssigned = false;
}
template <typename T>
@@ -165,9 +171,9 @@ namespace AZ
// This is the fast path which won't block.
{
AZStd::shared_lock<AZStd::shared_mutex> lock(s_mutex);
if (s_instance)
if (s_instanceAssigned)
{
return s_instance.Get();
return s_instance ? s_instance.Get() : nullptr;
}
}
@@ -175,6 +181,7 @@ namespace AZ
// take the full lock and request it.
AZStd::unique_lock<AZStd::shared_mutex> lock(s_mutex);
s_instance = Environment::FindVariable<T*>(GetVariableName());
s_instanceAssigned = true;
return s_instance ? s_instance.Get() : nullptr;
}
+12 -12
View File
@@ -94,7 +94,7 @@ namespace AZ
behaviorContext->Class<Aabb>()
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Script::Attributes::Module, "math")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Attribute(AZ::Script::Attributes::GenericConstructorOverride, &AabbDefaultConstructor)
->Property("min", &Aabb::GetMin, &Aabb::SetMin)
@@ -112,46 +112,46 @@ namespace AZ
->Method("GetCenter", &Aabb::GetCenter)
->Method("Set", &Aabb::Set)
->Attribute(AZ::Script::Attributes::MethodOverride, &AabbSetGeneric)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("CreateFromObb", &Aabb::CreateFromObb)
->Method("GetXExtent", &Aabb::GetXExtent)
->Method("GetYExtent", &Aabb::GetYExtent)
->Method("GetZExtent", &Aabb::GetZExtent)
->Method("GetAsSphere", &Aabb::GetAsSphere, nullptr, "() -> Vector3(center) and float(radius)")
->Attribute(AZ::Script::Attributes::MethodOverride, &AabbGetAsSphereMultipleReturn)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method<bool (Aabb::*)(const Aabb&) const>("Contains", &Aabb::Contains, nullptr, "const Vector3& or const Aabb&")
->Attribute(AZ::Script::Attributes::MethodOverride, &AabbContainsGeneric)
->Method<bool (Aabb::*)(const Vector3&) const>("ContainsVector3", &Aabb::Contains, nullptr, "const Vector3&")
->Attribute(AZ::Script::Attributes::Ignore, 0) // ignore for script since we already got the generic contains above
->Method("Overlaps", &Aabb::Overlaps)
->Method("Expand", &Aabb::Expand)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("GetExpanded", &Aabb::GetExpanded)
->Method("AddPoint", &Aabb::AddPoint)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("AddAabb", &Aabb::AddAabb)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("GetDistance", &Aabb::GetDistance)
->Method("GetClamped", &Aabb::GetClamped)
->Method("Clamp", &Aabb::Clamp)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("SetNull", &Aabb::SetNull)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("Translate", &Aabb::Translate)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("GetTranslated", &Aabb::GetTranslated)
->Method("GetSurfaceArea", &Aabb::GetSurfaceArea)
->Method("GetTransformedObb", static_cast<Obb(Aabb::*)(const Transform&) const>(&Aabb::GetTransformedObb))
->Method("GetTransformedAabb", static_cast<Aabb(Aabb::*)(const Transform&) const>(&Aabb::GetTransformedAabb))
->Method("ApplyTransform", &Aabb::ApplyTransform)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("Clone", [](const Aabb& rhs) -> Aabb { return rhs; })
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)
->Method("IsFinite", &Aabb::IsFinite)
->Method("Equal", &Aabb::operator==)
->Attribute(AZ::Script::Attributes::Operator, AZ::Script::Attributes::OperatorType::Equal)
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All);
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly);
}
}
+1 -1
View File
@@ -237,7 +237,7 @@ namespace AZ
behaviorContext->Class<Color>()->
Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)->
Attribute(AZ::Script::Attributes::Module, "math")->
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)->
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)->
Constructor<float>()->
Constructor<float, float, float, float>()->
File diff suppressed because it is too large Load Diff
@@ -260,7 +260,7 @@ namespace AZ
behaviorContext->Class<Matrix3x3>()->
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
Attribute(Script::Attributes::Module, "math")->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
Attribute(AZ::Script::Attributes::ConstructorOverride, &Internal::Matrix3x3ScriptConstructor)->
Attribute(Script::Attributes::GenericConstructorOverride, &Internal::Matrix3x3DefaultConstructor)->
@@ -280,7 +280,7 @@ namespace AZ
behaviorContext->Class<Matrix4x4>()->
Attribute(Script::Attributes::Scope, Script::Attributes::ScopeFlags::Common)->
Attribute(Script::Attributes::Module, "math")->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
Attribute(Script::Attributes::GenericConstructorOverride, &Internal::Matrix4x4DefaultConstructor)->
Property<Vector4(Matrix4x4::*)() const, void (Matrix4x4::*)(const Vector4&)>("basisX", &Matrix4x4::GetBasisX, &Matrix4x4::SetBasisX)->
+1 -1
View File
@@ -69,7 +69,7 @@ namespace AZ
if (behaviorContext)
{
behaviorContext->Class<Obb>()->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::All)->
Attribute(Script::Attributes::ExcludeFrom, Script::Attributes::ExcludeFlags::ListOnly)->
Attribute(Script::Attributes::Storage, Script::Attributes::StorageType::Value)->
Attribute(Script::Attributes::GenericConstructorOverride, &Internal::ObbDefaultConstructor)->
Property("position", &Obb::GetPosition, &Obb::SetPosition)->
+1 -1
View File
@@ -142,7 +142,7 @@ namespace AZ
if (behaviorContext)
{
behaviorContext->Class<Plane>()->
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)->
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)->
Attribute(AZ::Script::Attributes::GenericConstructorOverride, &Internal::PlaneDefaultConstructor)->
Method("ToString", &Internal::PlaneToString)->
@@ -19,12 +19,14 @@ namespace AZ
AZ_MATH_INLINE Plane Plane::CreateFromNormalAndPoint(const Vector3& normal, const Vector3& point)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is not normalized");
return Plane(Simd::Vec4::ConstructPlane(normal.GetSimdValue(), point.GetSimdValue()));
}
AZ_MATH_INLINE Plane Plane::CreateFromNormalAndDistance(const Vector3& normal, float dist)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is not normalized");
Plane result;
result.Set(normal, dist);
return result;
@@ -33,6 +35,7 @@ namespace AZ
AZ_MATH_INLINE Plane Plane::CreateFromCoefficients(const float a, const float b, const float c, const float d)
{
AZ_MATH_ASSERT(Vector3(a, b, c).IsNormalized(), "This normal is notormalized");
Plane result;
result.Set(a, b, c, d);
return result;
@@ -65,18 +68,21 @@ namespace AZ
AZ_MATH_INLINE void Plane::Set(const Vector3& normal, float d)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is notormalized");
m_plane.Set(normal, d);
}
AZ_MATH_INLINE void Plane::Set(float a, float b, float c, float d)
{
AZ_MATH_ASSERT(Vector3(a, b, c).IsNormalized(), "This normal is notormalized");
m_plane.Set(a, b, c, d);
}
AZ_MATH_INLINE void Plane::SetNormal(const Vector3& normal)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is notormalized");
m_plane.SetX(normal.GetX());
m_plane.SetY(normal.GetY());
m_plane.SetZ(normal.GetZ());
@@ -170,7 +170,7 @@ namespace AZ
behaviorContext->Class<Quaternion>()->
Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)->
Attribute(AZ::Script::Attributes::Module, "math")->
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All)->
Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::ListOnly)->
Constructor<float>()->
Constructor<float, float, float, float>()->
Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)->
@@ -254,13 +254,13 @@ namespace AZ
Method("CreateFromMatrix3x3", &Quaternion::CreateFromMatrix3x3)->
Method("CreateFromMatrix4x4", &Quaternion::CreateFromMatrix4x4)->
Method("CreateFromAxisAngle", &Quaternion::CreateFromAxisAngle)->
Method("CreateFromScaledAxisAngle", &Quaternion::CreateFromScaledAxisAngle)->
Method("CreateShortestArc", &Quaternion::CreateShortestArc)->
Method("CreateFromEulerAnglesDegrees", &Quaternion::CreateFromEulerAnglesDegrees)
;
}
}
Quaternion Quaternion::CreateFromMatrix3x3(const Matrix3x3& m)
{
return CreateFromBasis(m.GetBasisX(), m.GetBasisY(), m.GetBasisZ());
@@ -430,4 +430,24 @@ namespace AZ
outAngle = 0.0f;
}
}
Vector3 Quaternion::ConvertToScaledAxisAngle() const
{
// Take the log of the quaternion to convert it to the exponential map
// and multiply it by 2.0 to bring it into the scaled axis-angle representation.
const AZ::Vector3 imaginary = GetImaginary();
const float length = imaginary.GetLength();
if (length < AZ::Constants::FloatEpsilon)
{
return imaginary * 2.0f;
}
else
{
const float halfAngle = acosf(AZ::GetClamp(GetW(), -1.0f, 1.0f));
// Multiply by 2.0 to convert the half angle into the full one.
return halfAngle * 2.0f * (imaginary / length);
}
}
}

Some files were not shown because too many files have changed in this diff Show More