Compare commits

..

7 Commits

Author SHA1 Message Date
4a985c8285 Update depedencies
- Except ImGui due to massive changes required
- Except Vulkan dependent dependencies, wait for dxc spirv alloc fix
- Update manifest url
2026-02-20 22:52:58 +01:00
Jeslas Pravin
71e5bf859d Add Assimp importer with support for gltf, glb, fbx
89859ec5 Remove the GC wait on scene unload
04d40b65 AssetManager is now thread safe
a63a6e9e Fix model import with flipped texture coordinates
f15e30b5 Fix rendering bugs
b2d0772a Fix sampler issue in shaders and fixes in descriptor allocator
41aa6c80 Add material reconstruction support in assimp import
14a6fdfc Add engine material desc and assimp material mapping to importer
78190d91 Add Assimp importer fix for large mesh group
236bec8f Assimp works and tested on basic scenes
28008405 Add Prepare import implementation to assimp importer
0a447b4a Misc fixes
b66a3f3b Move Logger to cbe namespace to avoid collision with other libraries in this case with Assimp
e791b1fd Progress task messages do not have ... anymore
2026-02-19 19:05:26 +01:00
14e187fdc2 Add progress bar to async importer
18aeb32b Fix async asset importer crash
98ccfe5c Async asset importers
2026-02-09 23:05:50 +01:00
Jeslas Pravin
b70bd2b395 Add progress bar to engine and world async load
c55c44ee Fix crashes during editor world switch
b957d732 Add async scene load in editor
77e8436f Add progress tracker UI
07e63c48 Improve notification with custom callback based draw
0d92d5fb Add sticky notifications
66ec43d0 Add rfind to LinkedLists.h
9e82b95a Add Pause/Resume GC support for Async object processing
70d421b2 Make objects allocators thread safe with mutex
5e7c46db Add Construct Type enum to constructed function callbacks
580719b8 Rename CBESpinLock to cbe::SpinLock
0b53af56 Implement GC wait function that waits for GC in main thread
e750c7f2 Update Readme.md
b5ddc403 Fix random crashes at actor removal from world
2026-02-08 22:01:22 +01:00
Jeslas Pravin
a8cd543f81 Add initial actor prefab editor
2c001924 Implement make root component function
a9083191 Transact attachment changes
94da34b6 Fix actor prefab undo redo related crash
36949f8c Add transacted remove component from prefab editor helpers
3022dc1a Add ActorPrefabsBackupAction transaction action
7a42ca53 Add modal window for delete and rename component
3863f0cd Add support to update attachments of leaf after adding
758a57bb Add support to drag components from component list
82d73c45 Fix transform gizmo related crash and on actor prefab save crash
ebf3749e Support subselection trasforms gizmo in viewport
1db4aad9 Add support for subcomponent selection
5c875c9b Support component add to actor prefab editor
c8e0e25f Initial Add Component support
5f258bc8 Widget draw helper to draw class lists
bef04ae3 World edit outside editor shows dirty state correctly
e2c74cee Add Last chance destroy skip for Objects Refs and Collector Refs
8704c794 Fix loading object after GC marks an object for delete
1cf9589a Crashed app has less chance to hang
db224c67 Mock buttons to add and remove components to prefab
1904e060 Rework the actor prefab attachments propagation and override
c22204c5 Support editing actor prefab overrides and saving
b6122829 Add License details to main page
7d6cee09 Add options create actor prefab from actor type or another prefab
09d9931c Fix actor prefab rename related issues
2026-01-28 19:59:45 +01:00
Jeslas Pravin
fbde75de72 Add Funding 2025-12-26 16:34:05 +01:00
Jeslas Pravin
47be30fdf6 Update read 2025-12-25 23:07:01 +01:00
216 changed files with 10411 additions and 3626 deletions

View File

@@ -1,68 +1,75 @@
--- ---
Language: Cpp Language: Cpp
AccessModifierOffset: -4 AccessModifierOffset: -4
AlignAfterOpenBracket: BlockIndent AlignAfterOpenBracket: Align
AlignArrayOfStructures: Right AlignArrayOfStructures: Right
AlignConsecutiveAssignments: AlignConsecutiveAssignments:
Enabled: false Enabled: false
AcrossEmptyLines: false AcrossEmptyLines: false
AcrossComments: false AcrossComments: false
AlignCompound: false AlignCompound: false
AlignFunctionDeclarations: false
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: true PadOperators: true
AlignConsecutiveBitFields: AlignConsecutiveBitFields:
Enabled: false Enabled: true
AcrossEmptyLines: false AcrossEmptyLines: true
AcrossComments: false AcrossComments: true
AlignCompound: false AlignCompound: false
AlignFunctionDeclarations: false
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: false PadOperators: false
AlignConsecutiveDeclarations: AlignConsecutiveDeclarations:
Enabled: false Enabled: true
AcrossEmptyLines: false AcrossEmptyLines: false
AcrossComments: false AcrossComments: true
AlignCompound: false AlignCompound: false
AlignFunctionDeclarations: true
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: false PadOperators: false
AlignConsecutiveMacros: AlignConsecutiveMacros:
Enabled: false Enabled: true
AcrossEmptyLines: false AcrossEmptyLines: true
AcrossComments: false AcrossComments: true
AlignCompound: false AlignCompound: false
AlignFunctionDeclarations: false
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: false PadOperators: false
AlignConsecutiveShortCaseStatements: AlignConsecutiveShortCaseStatements:
Enabled: false Enabled: true
AcrossEmptyLines: false AcrossEmptyLines: true
AcrossComments: false AcrossComments: true
AlignCaseArrows: false AlignCaseArrows: false
AlignCaseColons: false AlignCaseColons: false
AlignConsecutiveTableGenBreakingDAGArgColons: AlignConsecutiveTableGenBreakingDAGArgColons:
Enabled: false Enabled: false
AcrossEmptyLines: false AcrossEmptyLines: false
AcrossComments: false AcrossComments: false
AlignCompound: false AlignCompound: false
AlignFunctionDeclarations: false
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: false PadOperators: false
AlignConsecutiveTableGenCondOperatorColons: AlignConsecutiveTableGenCondOperatorColons:
Enabled: false Enabled: false
AcrossEmptyLines: false AcrossEmptyLines: false
AcrossComments: false AcrossComments: false
AlignCompound: false AlignCompound: false
AlignFunctionDeclarations: false
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: false PadOperators: false
AlignConsecutiveTableGenDefinitionColons: AlignConsecutiveTableGenDefinitionColons:
Enabled: false Enabled: false
AcrossEmptyLines: false AcrossEmptyLines: false
AcrossComments: false AcrossComments: false
AlignCompound: false AlignCompound: false
AlignFunctionDeclarations: false
AlignFunctionPointers: false AlignFunctionPointers: false
PadOperators: false PadOperators: false
AlignEscapedNewlines: Right AlignEscapedNewlines: Right
AlignOperands: Align AlignOperands: Align
AlignTrailingComments: AlignTrailingComments:
Kind: Always Kind: Always
OverEmptyLines: 2 OverEmptyLines: 2
AllowAllArgumentsOnNextLine: true AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true
AllowBreakBeforeNoexceptSpecifier: Never AllowBreakBeforeNoexceptSpecifier: Never
@@ -75,57 +82,63 @@ AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: Never AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: Empty AllowShortLambdasOnASingleLine: Empty
AllowShortLoopsOnASingleLine: false AllowShortLoopsOnASingleLine: false
AllowShortNamespacesOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakBeforeMultilineStrings: false AlwaysBreakBeforeMultilineStrings: true
AttributeMacros: AttributeMacros:
- __capability - __capability
BinPackArguments: true BinPackArguments: true
BinPackParameters: true BinPackLongBracedList: true
BinPackParameters: BinPack
BitFieldColonSpacing: None BitFieldColonSpacing: None
BracedInitializerIndentWidth: -1
BraceWrapping: BraceWrapping:
AfterCaseLabel: true AfterCaseLabel: true
AfterClass: true AfterClass: true
AfterControlStatement: Always AfterControlStatement: Always
AfterEnum: true AfterEnum: true
AfterExternBlock: true AfterExternBlock: true
AfterFunction: true AfterFunction: true
AfterNamespace: true AfterNamespace: true
AfterObjCDeclaration: true AfterObjCDeclaration: true
AfterStruct: true AfterStruct: true
AfterUnion: true AfterUnion: true
BeforeCatch: true BeforeCatch: true
BeforeElse: true BeforeElse: true
BeforeLambdaBody: true BeforeLambdaBody: true
BeforeWhile: true BeforeWhile: true
IndentBraces: false IndentBraces: false
SplitEmptyFunction: false SplitEmptyFunction: false
SplitEmptyRecord: false SplitEmptyRecord: false
SplitEmptyNamespace: false SplitEmptyNamespace: false
BreakAdjacentStringLiterals: true BreakAdjacentStringLiterals: true
BreakAfterAttributes: Never BreakAfterAttributes: Leave
BreakAfterJavaFieldAnnotations: false BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: None BreakAfterReturnType: None
BreakArrays: false BreakArrays: false
BreakBeforeBinaryOperators: All BreakBeforeBinaryOperators: All
BreakBeforeConceptDeclarations: Always BreakBeforeConceptDeclarations: Always
BreakBeforeBraces: Custom BreakBeforeBraces: Custom
BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTemplateCloser: true
BreakBeforeTernaryOperators: true BreakBeforeTernaryOperators: true
BreakBinaryOperations: Never
BreakConstructorInitializers: BeforeComma BreakConstructorInitializers: BeforeComma
BreakFunctionDefinitionParameters: false BreakFunctionDefinitionParameters: false
BreakInheritanceList: BeforeComma BreakInheritanceList: BeforeComma
BreakStringLiterals: true BreakStringLiterals: true
BreakTemplateDeclarations: Yes BreakTemplateDeclarations: Yes
ColumnLimit: 142 ColumnLimit: 142
CommentPragmas: "^ IWYU pragma:" CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false CompactNamespaces: false
ConstructorInitializerIndentWidth: 4 ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4 ContinuationIndentWidth: 4
Cpp11BracedListStyle: false Cpp11BracedListStyle: false
DerivePointerAlignment: false DerivePointerAlignment: false
DisableFormat: false DisableFormat: false
EmptyLineAfterAccessModifier: Never EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock EmptyLineBeforeAccessModifier: LogicalBlock
EnumTrailingComma: Insert
ExperimentalAutoDetectBinPacking: false ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true FixNamespaceComments: true
ForEachMacros: ForEachMacros:
@@ -134,51 +147,53 @@ ForEachMacros:
- BOOST_FOREACH - BOOST_FOREACH
IfMacros: IfMacros:
- KJ_IF_MAYBE - KJ_IF_MAYBE
IncludeBlocks: Preserve IncludeBlocks: Preserve
IncludeCategories: IncludeCategories:
- Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Regex: '^"(llvm|llvm-c|clang|clang-c)/'
Priority: 2 Priority: 2
SortPriority: 0 SortPriority: 0
CaseSensitive: false CaseSensitive: false
- Regex: '^(<|"(gtest|gmock|isl|json)/)' - Regex: '^(<|"(gtest|gmock|isl|json)/)'
Priority: 3 Priority: 3
SortPriority: 0 SortPriority: 0
CaseSensitive: false CaseSensitive: false
- Regex: ".*" - Regex: '.*'
Priority: 1 Priority: 1
SortPriority: 0 SortPriority: 0
CaseSensitive: false CaseSensitive: false
IncludeIsMainRegex: "(Test)?$" IncludeIsMainRegex: '(Test)?$'
IncludeIsMainSourceRegex: "" IncludeIsMainSourceRegex: ''
IndentAccessModifiers: false IndentAccessModifiers: false
IndentCaseBlocks: false IndentCaseBlocks: false
IndentCaseLabels: false IndentCaseLabels: false
IndentExportBlock: false
IndentExternBlock: AfterExternBlock IndentExternBlock: AfterExternBlock
IndentGotoLabels: true IndentGotoLabels: true
IndentPPDirectives: None IndentPPDirectives: None
IndentRequiresClause: true IndentRequiresClause: true
IndentWidth: 4 IndentWidth: 4
IndentWrappedFunctionNames: false IndentWrappedFunctionNames: false
InsertBraces: true InsertBraces: true
InsertNewlineAtEOF: false InsertNewlineAtEOF: true
InsertTrailingCommas: None InsertTrailingCommas: None
IntegerLiteralSeparator: IntegerLiteralSeparator:
Binary: 0 Binary: 0
BinaryMinDigits: 0 BinaryMinDigits: 0
Decimal: 0 Decimal: 0
DecimalMinDigits: 0 DecimalMinDigits: 0
Hex: 0 Hex: 0
HexMinDigits: 0 HexMinDigits: 0
JavaScriptQuotes: Leave JavaScriptQuotes: Leave
JavaScriptWrapImports: true JavaScriptWrapImports: true
KeepEmptyLines: KeepEmptyLines:
AtEndOfFile: false AtEndOfFile: true
AtStartOfBlock: true AtStartOfBlock: true
AtStartOfFile: true AtStartOfFile: true
KeepFormFeed: false
LambdaBodyIndentation: Signature LambdaBodyIndentation: Signature
LineEnding: CRLF LineEnding: DeriveLF
MacroBlockBegin: "^GAL_GPU_STRUCT_BEGIN|GAL_GPU_VERTEX_BEGIN|CBE_MATERIAL_STRUCT_BEGIN$" MacroBlockBegin: '^GAL_GPU_STRUCT_BEGIN|GAL_GPU_VERTEX_BEGIN|CBE_MATERIAL_STRUCT_BEGIN$'
MacroBlockEnd: "^GAL_GPU_STRUCT_END|GAL_GPU_VERTEX_END|CBE_MATERIAL_STRUCT_END$" MacroBlockEnd: '^GAL_GPU_STRUCT_END|GAL_GPU_VERTEX_END|CBE_MATERIAL_STRUCT_END$'
Macros: Macros:
- GAL_RESOURCE_HND(x)=x - GAL_RESOURCE_HND(x)=x
- AUDIO_RESOURCE_HND(x)=x - AUDIO_RESOURCE_HND(x)=x
@@ -190,9 +205,11 @@ ObjCBlockIndentWidth: 2
ObjCBreakBeforeNestedBlockParam: true ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true ObjCSpaceBeforeProtocolList: true
OneLineFormatOffRegex: ''
PackConstructorInitializers: BinPack PackConstructorInitializers: BinPack
PenaltyBreakAssignment: 2 PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 19 PenaltyBreakBeforeFirstCallParameter: 19
PenaltyBreakBeforeMemberAccess: 150
PenaltyBreakComment: 300 PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120 PenaltyBreakFirstLessLess: 120
PenaltyBreakOpenParenthesis: 0 PenaltyBreakOpenParenthesis: 0
@@ -203,11 +220,12 @@ PenaltyExcessCharacter: 1000000
PenaltyIndentedWhitespace: 0 PenaltyIndentedWhitespace: 0
PenaltyReturnTypeOnItsOwnLine: 60 PenaltyReturnTypeOnItsOwnLine: 60
PointerAlignment: Right PointerAlignment: Right
PPIndentWidth: -1 PPIndentWidth: -1
QualifierAlignment: Leave QualifierAlignment: Leave
ReferenceAlignment: Pointer ReferenceAlignment: Pointer
ReflowComments: true ReflowComments: IndentOnly
RemoveBracesLLVM: false RemoveBracesLLVM: false
RemoveEmptyLinesInUnwrappedLines: true
RemoveParentheses: Leave RemoveParentheses: Leave
RemoveSemicolon: true RemoveSemicolon: true
RequiresClausePosition: WithPreceding RequiresClausePosition: WithPreceding
@@ -215,11 +233,14 @@ RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Leave SeparateDefinitionBlocks: Leave
ShortNamespaceLines: 1 ShortNamespaceLines: 1
SkipMacroDefinitionBody: false SkipMacroDefinitionBody: false
SortIncludes: Never SortIncludes:
Enabled: false
IgnoreCase: false
SortJavaStaticImport: Before SortJavaStaticImport: Before
SortUsingDeclarations: LexicographicNumeric SortUsingDeclarations: LexicographicNumeric
SpaceAfterCStyleCast: false SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false SpaceAfterLogicalNot: false
SpaceAfterOperatorKeyword: false
SpaceAfterTemplateKeyword: true SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: After SpaceAroundPointerQualifiers: After
SpaceBeforeAssignmentOperators: true SpaceBeforeAssignmentOperators: true
@@ -234,7 +255,8 @@ SpaceBeforeParensOptions:
AfterForeachMacros: true AfterForeachMacros: true
AfterFunctionDefinitionName: false AfterFunctionDefinitionName: false
AfterFunctionDeclarationName: false AfterFunctionDeclarationName: false
AfterIfMacros: true AfterIfMacros: true
AfterNot: false
AfterOverloadedOperator: true AfterOverloadedOperator: true
AfterPlacementOperator: true AfterPlacementOperator: true
AfterRequiresInClause: true AfterRequiresInClause: true
@@ -244,20 +266,20 @@ SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false SpaceBeforeSquareBrackets: false
SpaceInEmptyBlock: false SpaceInEmptyBlock: false
SpacesBeforeTrailingComments: 1 SpacesBeforeTrailingComments: 1
SpacesInAngles: Never SpacesInAngles: Never
SpacesInContainerLiterals: true SpacesInContainerLiterals: true
SpacesInLineCommentPrefix: SpacesInLineCommentPrefix:
Minimum: 1 Minimum: 1
Maximum: -1 Maximum: -1
SpacesInParens: Never SpacesInParens: Never
SpacesInParensOptions: SpacesInParensOptions:
ExceptDoubleParentheses: false ExceptDoubleParentheses: false
InCStyleCasts: false InCStyleCasts: false
InConditionalStatements: false InConditionalStatements: false
InEmptyParentheses: false InEmptyParentheses: false
Other: false Other: false
SpacesInSquareBrackets: false SpacesInSquareBrackets: false
Standard: Latest Standard: Latest
StatementAttributeLikeMacros: StatementAttributeLikeMacros:
- Q_EMIT - Q_EMIT
- GAL_RESOURCE_HND - GAL_RESOURCE_HND
@@ -274,8 +296,8 @@ StatementMacros:
- LastMacroName - LastMacroName
- OpMacro - OpMacro
TableGenBreakInsideDAGArg: DontBreak TableGenBreakInsideDAGArg: DontBreak
TabWidth: 4 TabWidth: 4
UseTab: Never UseTab: Never
VerilogBreakBetweenInstancePorts: true VerilogBreakBetweenInstancePorts: true
WhitespaceSensitiveMacros: WhitespaceSensitiveMacros:
- MAKE_STRID - MAKE_STRID
@@ -283,4 +305,6 @@ WhitespaceSensitiveMacros:
- NS_SWIFT_NAME - NS_SWIFT_NAME
- PP_STRINGIZE - PP_STRINGIZE
- STRINGIZE - STRINGIZE
--- WrapNamespaceBodyWithEmptyLines: Always
...

2
.github/funding.yml vendored Normal file
View File

@@ -0,0 +1,2 @@
github: [jeslaspravin]
custom:

View File

@@ -1,6 +1,6 @@
repos: repos:
- repo: https://github.com/pre-commit/mirrors-clang-format - repo: https://github.com/pre-commit/mirrors-clang-format
rev: "v19.1.7" rev: "v21.1.8"
hooks: hooks:
- id: clang-format - id: clang-format
exclude_types: exclude_types:

View File

@@ -1,14 +1,13 @@
# Agents.md # Agents.md
## 📁 Repository Overview ## 📁 Repository Overview
The **Cranberry Engine** is a C++ 3D engine built with CMake3.26+. The source tree follows a classic monorepo layout:
The **Cranberry Engine** is a C++ 3D engine that is built using **CMake 3.26+**. The source tree follows a classic *monorepo* layout:
``` ```
Cranberry/ # Root (CMakeLists.txt, global config) Cranberry/
├─ Scripts/ # Helpers and CMake modules ├─ Scripts/ # Helpers and CMake modules
│ ├─ CMake/ # GlobalConfig, EngineProjectConfig, utilities │ ├─ CMake/ # GlobalConfig, EngineProjectConfig, utilities
│ └─ ... # Other scripts │ └─ ...
├─ Source/ # Actual engine code ├─ Source/ # Actual engine code
│ ├─ Runtime/ # Core engine modules & runtime executables │ ├─ Runtime/ # Core engine modules & runtime executables
│ │ ├─ EngineModules/ │ │ ├─ EngineModules/
@@ -20,10 +19,9 @@ Cranberry/ # Root (CMakeLists.txt, global config)
└─ External/ # 3rdparty libraries (Jolt, SPIRVTools, etc.) └─ External/ # 3rdparty libraries (Jolt, SPIRVTools, etc.)
``` ```
All CMake configuration is centralised in `Scripts/CMake/GlobalConfig.cmake` and `EngineProjectConfig.cmake`. The root `CMakeLists.txt` simply includes these files and then pulls in the `Source/` and `Scripts/` subdirectories. All CMake configuration is centralised in `Scripts/CMake/GlobalConfig.cmake` and `EngineProjectConfig.cmake`. The root `CMakeLists.txt` simply includes these files and then pulls in the `Source/` and `Scripts/` subdirectories.
## 🏗️ Build Process ## 🏗️ Build Process
```bash ```bash
# 1. Configure # 1. Configure
cmake -S . -B build \ cmake -S . -B build \
@@ -38,17 +36,16 @@ cmake --build build --config Release --parallel
ctest --test-dir build -V ctest --test-dir build -V
``` ```
> **Note** The project supports multiple configurations (`Debug`, `Development`, `Release`, `RelWithDebInfo`). The `Cranberry_ENABLE_CLANG_TIDY` option automatically turns on `clang-tidy` and generates `compile_commands.json`. > **Note** The project supports multiple configurations (`Debug`, `Development`, `Release`, `RelWithDebInfo`). The `Cranberry_ENABLE_CLANG_TIDY` option automatically turns on `clang-tidy` and generates `compile_commands.json`.
## 🧪 Testing ## 🧪 Testing
Tests are written using **Doctest**. They are enabled by default (`Cranberry_ENABLE_TESTS=ON`). To run the test suite after building:
Tests are written using **Doctest**. They are enabled by default (`Cranberry_ENABLE_TESTS=ON`). To run the test suite after building:
```bash ```bash
ctest --test-dir build -V ctest --test-dir build -V
``` ```
The test tree mirrors the source layout. New tests should be placed under `Source/Tests/<module>/` and added to the corresponding CMakeLists. The test tree mirrors the source layout. New tests should be placed under `Source/Tests/<module>/` and added to the corresponding CMakeLists.
## 🔍 Code Review Guidelines (used by the **codereviewer** agent) ## 🔍 Code Review Guidelines (used by the **codereviewer** agent)
@@ -59,20 +56,18 @@ The test tree mirrors the source layout. New tests should be placed under `Sour
5. **Documentation** Each module should have a brief comment block at the top of the file. 5. **Documentation** Each module should have a brief comment block at the top of the file.
6. **Tests** Every new feature must have at least one unit test. 6. **Tests** Every new feature must have at least one unit test.
The **codereviewer** agent will automatically run these checks on each commit. If any rule is violated, the PR will be marked **Failed**. The **codereviewer** agent will automatically run these checks on each commit. If any rule is violated, the PR will be marked **Failed**.
## 🤖 Agent Responsibilities ## 🤖 Agent Responsibilities
| Agent | Responsibility | Trigger |
| Agent | Responsibility | Trigger | |----------------|--------------------------------------------------------------|-------------------------------------------|
|-------|----------------|---------| | `code-reviewer`| Validate CMake, compile flags, formatting, tests | On pull request / commit |
| `code-reviewer` | Validate CMake, compile flags, formatting, tests | On pull request / commit | | `code-assistant`| Assist in adding new modules, generating boilerplate, updating CMake | On issue or PR requesting new feature |
| `code-assistant` | Assist in adding new modules, generating boilerplate, updating CMake | On issue or PR requesting new feature | | `ci-agent` | Run CI pipeline (build, tests, clangtidy) | On push to main or PR |
| `ci-agent` | Run CI pipeline (build, tests, clangtidy) | On push to main or PR |
> **Tip** When creating a new module, place the code under `Source/Runtime/<ModuleName>/` and add a `CMakeLists.txt` that uses `add_cmake_subdirectories()` to pull in subdirectories. > **Tip** When creating a new module, place the code under `Source/Runtime/<ModuleName>/` and add a `CMakeLists.txt` that uses `add_cmake_subdirectories()` to pull in subdirectories.
## 📦 CI Integration ## 📦 CI Integration
A minimal GitHub Actions workflow (`ci.yml`) can be added to the `.github/workflows` directory to automatically build and test on each push: A minimal GitHub Actions workflow (`ci.yml`) can be added to the `.github/workflows` directory to automatically build and test on each push:
```yaml ```yaml
@@ -100,4 +95,3 @@ Feel free to extend this file to include clangtidy or clangformat checks.
--- ---
*This file serves as a quick reference for developers and the automated agents working on the project.* *This file serves as a quick reference for developers and the automated agents working on the project.*

View File

@@ -1,6 +1,6 @@
cmake_minimum_required( VERSION 3.26 ) cmake_minimum_required( VERSION 3.26 )
set( deps_manifest_url "https://drive.jeslaspravin.com/seafhttp/f/563b857bc80b4ed8babd/?op=view" ) set( deps_manifest_url "https://drive.jeslaspravin.com/seafhttp/f/4c67c4d663d84fd28d98/?op=view" )
set( deps_version "0.1" ) set( deps_version "0.1" )
set( deps_path ${CMAKE_CURRENT_LIST_DIR} ) set( deps_path ${CMAKE_CURRENT_LIST_DIR} )
set( deps_setup_status_file ${deps_path}/DepsSetupSuccess.txt ) set( deps_setup_status_file ${deps_path}/DepsSetupSuccess.txt )

View File

@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2014-2025 Omar Cornut Copyright (c) 2014-2026 Omar Cornut
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
Open Asset Import Library (assimp) Open Asset Import Library (assimp)
Copyright (c) 2006-2021, assimp team Copyright (c) 2006-2026, assimp team
All rights reserved. All rights reserved.
Redistribution and use of this software in source and binary forms, Redistribution and use of this software in source and binary forms,

View File

@@ -1,7 +1,7 @@
Tracy Profiler (https://github.com/wolfpld/tracy) is licensed under the Tracy Profiler (https://github.com/wolfpld/tracy) is licensed under the
3-clause BSD license. 3-clause BSD license.
Copyright (c) 2017-2025, Bartosz Taudul <wolf@nereid.pl> Copyright (c) 2017-2026, Bartosz Taudul <wolf@nereid.pl>
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without

View File

@@ -2,17 +2,18 @@
![CranberryLogo] ![CranberryLogo]
**Cranberry Game Engine** is my Personal hobby game engine. I work on this as best as I could during my holidays. **Cranberry Game Engine** is my Personal hobby game engine. I work on this as best as I could during my holidays. It is MIT Licensed open source engine with high code quality.
The goal of the engine is to have a set of tools required to develop a game without too much dependencies. The goal of the engine is to have a set of tools required to develop a game without too much dependencies.
In the renderer side I aim to be as efficient as possible given the little time I invest in the engine. In the renderer side I aim to be as efficient as possible given the little time I invest in the engine.
Physics will be optional for any game so I intend to keep the module and its integration into game objects as decoupled as possible. Physics will be optional for any game so I intend to keep the module and its integration into game objects as decoupled as possible.
Audio will always be needed so that will be tightly integrated with other objects. Audio will always be needed so that will be tightly integrated with other objects.
Some features are over engineered and I am fine with it. So if you want to contribute it is okay to over engineer as long as the changes itself is self contained.
Join the discord server at <https://discord.gg/2xfVG4QPt8> if you want to make some meaningful impact. Join the discord server at <https://discord.gg/2xfVG4QPt8> if you want to make some meaningful impact.
All of the engine code is written without using AI, if that matters at all. I assure you not many engines can say that.
I am a single person right now working on this engine with very little time to spend in it while spending time with my family, so there are few places AI is used like to comment code, generate build scripts. However all such usage was heavily reviewed/modified to keep the quality high.
## Requirement ## Requirement
* Vulkan 1.3 supported hardware is required. Last time I checked the engine runs even on Intel 7th Gen GPU device/driver, I would like to keep it that way as long as possible. * Vulkan 1.3 supported hardware is required. Last time I checked the engine runs even on Intel 7th Gen GPU device/driver, I would like to keep it that way as long as possible.
@@ -26,25 +27,29 @@ Can be found at [Best Practices]
### Contribution ### Contribution
If you are looking to contribute, try to pick a task or issue from [Cranberry Project] If you are looking to contribute, try to pick a task or issue from issues list.
Right now there is no contributor guideline. Regular workflow of forking the engine and creating a merge/pull request with description is good enough. Right now there is no contributor guideline. Regular workflow of forking the engine and creating a merge/pull request with description is good enough.
I will add guidelines as nice people starts contributing. I will add guidelines as nice people starts contributing.
Project is in early stage so if you contribute you will get opportunity to be a maintainer or lead of certain module as project grows. So now is the best time to contribute. There are some points I would like to highlight
1. Project is in early stage so if you contribute you will get opportunity to be a maintainer or lead of certain module as project grows. So now is the best time to contribute.
2. If you are a student you have so much to gain by working on this project.
3. Some features are over engineered and I am fine with it. So if you want to contribute it is okay to over engineer as long as the changes itself is self contained.
### Getting started with first build ### Getting started with first build
* Install CMake from [CMake] * Install CMake from [CMake]
* Install Visual Studio 2022/2026 from [VisualStudio] * Install Visual Studio 2022/2026 from [VisualStudio]
* Install Vulkan SDK from [VulkanSDK]. Necessary because I am using `dxc` built for SPIR-V. * Install Vulkan SDK from [VulkanSDK]. Necessary because I am using `dxc` built for SPIR-V.
* Run GenerateProject.ps1 to generate Visual Studio solution. This will by default generate solution for `Editor-DynamicLinked-VS2022` preset under `Build_VS2022` folder * Run GenerateProject.ps1 to generate Visual Studio solution. This will by default generate solution for `Editor-DynamicLinked-VS2026` preset under `Build_VS2026` folder
***<p align="center">(or)</p>*** ***<p align="center">(or)</p>***
* You can also run `GenerateProject.ps1` with one of following presets(eg., `GenerateProject.ps1` or `GenerateProject.ps1 <preset> [Some Cmake arguments]..`) * You can also run `GenerateProject.ps1` with one of following presets(eg., `GenerateProject.ps1` or `GenerateProject.ps1 <preset> [Some Cmake arguments]..`)
Note that if using preset and not going to change library dependencies run `SetupDeps.ps1` first(It will download archive with necessary libraries) Note that if using preset and not going to change library dependencies run `SetupDeps.ps1` first(It will download archive with necessary libraries)
* `Editor-DynamicLinked-VS2022` preset creates command `cmake -B Build_VS2022 -G "Visual Studio 17 2022" -A x64 -Thost=x64 -DCMAKE_INSTALL_PREFIX:STRING=${sourceDir}/Installed -DCranberry_STATIC_MODULES:BOOL=OFF` * `Editor-DynamicLinked-VS2026` preset creates command `cmake -B Build_VS2026 -G "Visual Studio 18 2026" -A x64 -Thost=x64 -DCMAKE_INSTALL_PREFIX:STRING=${sourceDir}/Installed -DCranberry_STATIC_MODULES:BOOL=OFF`
* `Runtime-DynamicLinked-VS2022` preset creates command `cmake -B RuntimeBuild_VS2022 -G "Visual Studio 17 2022" -A x64 -Thost=x64 -DCMAKE_INSTALL_PREFIX:STRING=${sourceDir}/Installed -DCranberry_STATIC_MODULES:BOOL=OFF -DCranberry_EDITOR_BUILD:BOOL=OFF` * `Runtime-DynamicLinked-VS2026` preset creates command `cmake -B RuntimeBuild_VS2026 -G "Visual Studio 18 2026" -A x64 -Thost=x64 -DCMAKE_INSTALL_PREFIX:STRING=${sourceDir}/Installed -DCranberry_STATIC_MODULES:BOOL=OFF -DCranberry_EDITOR_BUILD:BOOL=OFF`
### Optional ### Optional
@@ -64,18 +69,20 @@ Many features listed below are supported but tooling still needs to be developed
* Reflection generator for C++ * Reflection generator for C++
* Reflection supports metadata classes and flags * Reflection supports metadata classes and flags
* Shaders are reflected and Parameters can be addressed with names * Shaders are assembled with input structure defined in C++. Offers compile time type check for inputs.
* PBR Materials * PBR Materials
* Image base lighting * Image base lighting
* Point, Spot, Directional lights * Point, Spot, Directional lights
* Shadows, Cube mapped shadows and Cascaded shadows * Shadows, Cube mapped shadows and Cascaded shadows
* Prototype garbage collector * Mark and sweep garbage collector with reference list
* Object tree actions like deep copy, traversal etc., * Object tree actions like deep copy, traversal etc.,
* Multiwindow widgets and Input handling * Multiwindow widgets and Input handling
* Supported inputs mouse and keyboard * Supported inputs mouse and keyboard
* World/Actor/Components * World/Actor/Components
* Unity style prefabs * Unity style prefabs
* Job system using [CoPaT] * Job system using [CoPaT]
* World editor and any asset editor(Some are Actor Prefab editor and Material Instance editor)
* Full fledged editor with complete Undo/Redo integration for actions and data
## Third parties ## Third parties
@@ -116,9 +123,11 @@ Licenses for third party packages used is placed under `Licenses` folder
![CranberryActorPrefabEdSS] ![CranberryActorPrefabEdSS]
## PS ## ❤️ Support this project
If you found any piece of this software helpful or used it yourself, Please feel free to share it with your circle. I had invested substantial amount of my personal time in this project and would love some feedback in return😄 If you found any piece of this software helpful or used it yourself, please feel free to share it with your circle or support me. I had invested substantial amount of my personal time in this project.
* [Github Sponsor]
[//]: # (Below are link reference definitions) [//]: # (Below are link reference definitions)
[CMake]: https://cmake.org/download/ [CMake]: https://cmake.org/download/
@@ -130,7 +139,8 @@ If you found any piece of this software helpful or used it yourself, Please feel
[TestEngineSS]: <https://blogs.jeslaspravin.com/assets/images/CranberryEngine/TestEngine(08-01-2023).jpg> "Test Engine screenshot of PBR rendering with shadows and tone mapping" [TestEngineSS]: <https://blogs.jeslaspravin.com/assets/images/CranberryEngine/TestEngine(08-01-2023).jpg> "Test Engine screenshot of PBR rendering with shadows and tone mapping"
[CranberryEdSS]: <https://blogs.jeslaspravin.com/assets/images/CranberryEngine/CranberryEditor(24-12-2025).jpg> "Cranberry engine editor main window screenshot" [CranberryEdSS]: <https://blogs.jeslaspravin.com/assets/images/CranberryEngine/CranberryEditor(24-12-2025).jpg> "Cranberry engine editor main window screenshot"
[CranberryActorPrefabEdSS]: <https://blogs.jeslaspravin.com/assets/images/CranberryEngine/ActorPrefabEditor(24-12-2025).jpg> "Actor prefab editor with two actor prefab being edited side by side" [CranberryActorPrefabEdSS]: <https://blogs.jeslaspravin.com/assets/images/CranberryEngine/ActorPrefabEditor(24-12-2025).jpg> "Actor prefab editor with two actor prefab being edited side by side"
[Best Practices]: <https://git.jeslaspravin.com/jeslaspravin/Cranberry/raw/branch/main/Docs/BestPractises.md> [Best Practices]: <https://git.jeslaspravin.com/jeslaspravin/Cranberry/src/branch/main/Docs/BestPractices.md>
[Cranberry Project]: <https://git.jeslaspravin.com/jeslaspravin/Cranberry/projects/2>
[pre-commit webpage]: https://pre-commit.com/ [pre-commit webpage]: https://pre-commit.com/
[Github Sponsor]: <https://github.com/sponsors/jeslaspravin>

View File

@@ -4,7 +4,7 @@
# \author Jeslas Pravin # \author Jeslas Pravin
# \date June 2022 # \date June 2022
# \copyright # \copyright
# Copyright (C) Jeslas Pravin, 2022-2025 # Copyright (C) Jeslas Pravin, 2022-2026
# @jeslaspravin pravinjeslas@gmail.com # @jeslaspravin pravinjeslas@gmail.com
# License can be read in LICENSE file at this repository's root # License can be read in LICENSE file at this repository's root
# #
@@ -148,3 +148,41 @@ function( copy_targets_to_thirdparty )
) )
endif( ${Cranberry_STATIC_MODULES} AND ${Cranberry_STATIC_AS_OBJS} ) endif( ${Cranberry_STATIC_MODULES} AND ${Cranberry_STATIC_AS_OBJS} )
endfunction() endfunction()
# Use on target with editor only dependency of dynamic linked shared libraries from several external targets.
# Adds necessary delay load target lists and compiler definitions to easily delay load the dependencies.
#
# TARGET - Dependent target.
# TARGETS_DEPS - Dependency targets that has shared lib or executable output.
# TARGET_DEPS_PREFIX - Prefixes to add to TARGET compile definitions for delay loading at runtime. Must have same length as TARGETS_DEPS
# OUT_DELAY_LOAD_LIST - Delay load list to fill with data.
#
function( add_editor_dep_delay_loaded )
set( one_value_args TARGET OUT_DELAY_LOAD_LIST )
set( multi_value_args TARGET_DEPS TARGET_DEPS_PREFIX )
cmake_parse_arguments( ed_dep "" "${one_value_args}" "${multi_value_args}" ${ARGN} )
set( local_delay_loaded_dlls )
# Add existing list to local list
if( DEFINED ed_dep_OUT_DELAY_LOAD_LIST AND DEFINED ${ed_dep_OUT_DELAY_LOAD_LIST} )
list( APPEND local_delay_loaded_dlls ${${ed_dep_OUT_DELAY_LOAD_LIST}} )
endif()
foreach( dep_target dep_prefix IN ZIP_LISTS ed_dep_TARGET_DEPS ed_dep_TARGET_DEPS_PREFIX )
# Add to delay loaded list, cannot use TARGET_FILE_BASE_NAME on imported or aliased targets so remove extension manually here.
list( APPEND local_delay_loaded_dlls "$<PATH:REMOVE_EXTENSION,$<TARGET_FILE_NAME:${dep_target}>>" )
target_compile_definitions( ${ed_dep_TARGET}
PRIVATE
CBE_${dep_prefix}_BIN_PATH="$<TARGET_FILE_DIR:${dep_target}>"
CBE_${dep_prefix}_LIB_NAME="$<TARGET_FILE_NAME:${dep_target}>"
)
endforeach()
# Set the updated list to parent
if( DEFINED ed_dep_OUT_DELAY_LOAD_LIST AND local_delay_loaded_dlls )
set( ${ed_dep_OUT_DELAY_LOAD_LIST} ${local_delay_loaded_dlls} PARENT_SCOPE )
endif()
endfunction( add_editor_dep_delay_loaded )

View File

@@ -4,7 +4,7 @@
# \author Jeslas Pravin # \author Jeslas Pravin
# \date January 2022 # \date January 2022
# \copyright # \copyright
# Copyright (C) Jeslas Pravin, 2022-2025 # Copyright (C) Jeslas Pravin, 2022-2026
# @jeslaspravin pravinjeslas@gmail.com # @jeslaspravin pravinjeslas@gmail.com
# License can be read in LICENSE file at this repository's root # License can be read in LICENSE file at this repository's root
# #
@@ -44,6 +44,9 @@ set( Cranberry_SPIRV_TOOLS_PATH "${Cranberry_CPP_LIBS_PATH}/SPIRV-Tools" CACHE P
# LLVM installed path # LLVM installed path
set( Cranberry_LLVM_INSTALL_PATH "${Cranberry_CPP_LIBS_PATH}/llvm" CACHE PATH "LLVM installed path(For libclang)" ) set( Cranberry_LLVM_INSTALL_PATH "${Cranberry_CPP_LIBS_PATH}/llvm" CACHE PATH "LLVM installed path(For libclang)" )
# Assimp installed path
set (Cranberry_ASSIMP_INSTALL_PATH "${Cranberry_CPP_LIBS_PATH}/assimp" CACHE PATH "Assimp installed path" )
option( Cranberry_ENABLE_MIMALLOC "Compile with mimalloc?" ON ) option( Cranberry_ENABLE_MIMALLOC "Compile with mimalloc?" ON )
set( Cranberry_MIMALLOC_INSTALL_PATH "${Cranberry_CPP_LIBS_PATH}/mimalloc" CACHE PATH "mimalloc installed path" ) set( Cranberry_MIMALLOC_INSTALL_PATH "${Cranberry_CPP_LIBS_PATH}/mimalloc" CACHE PATH "mimalloc installed path" )

View File

@@ -43,6 +43,7 @@ ignore_file_pattern = [
".*\\.natstepfilter", ".*\\.natstepfilter",
".*\\.natvis", ".*\\.natvis",
".*\\.md", ".*\\.md",
".*\\.yml",
] ]
# Overrides ignore files pattern # Overrides ignore files pattern

View File

@@ -16,11 +16,35 @@ set( private_includes
${Cranberry_CPP_LIBS_PATH}/tinyobjloader ${Cranberry_CPP_LIBS_PATH}/tinyobjloader
${Cranberry_CPP_LIBS_PATH}/stb ${Cranberry_CPP_LIBS_PATH}/stb
) )
set( private_libraries )
set( delay_load_dlls )
# Finding Assimp libraries
make_find_package_hints( assimp ${Cranberry_ASSIMP_INSTALL_PATH} clang_hints )
find_package( assimp 6
REQUIRED CONFIG
HINTS ${clang_hints} )
if( DEFINED assimp_FOUND AND ${assimp_FOUND} AND TARGET assimp::assimp )
message( STATUS "Assimp found" )
map_imported_targets_config( TARGETS assimp::assimp ENABLE_DEBUG )
list( APPEND private_libraries assimp::assimp )
else()
message( FATAL_ERROR "Assimp package not found, Make sure to configure \"Cranberry_ASSIMP_INSTALL_PATH\" config CACHE with path to assimp install" )
endif()
generate_engine_editor_library() generate_engine_editor_library()
target_compile_options( ${target_name} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/MP> )
generate_reflection() generate_reflection()
add_editor_dep_delay_loaded( TARGET ${target_name}
OUT_DELAY_LOAD_LIST delay_load_dlls
TARGET_DEPS assimp::assimp
TARGET_DEPS_PREFIX ASSIMP
)
mark_delay_loaded_dlls( IGNORE_MODULES )
target_compile_options( ${target_name} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/MP> )
# Add shader folders # Add shader folders
add_shader_directories( TARGET_NAME ${target_name} add_shader_directories( TARGET_NAME ${target_name}

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date December 2025 * \date December 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -16,11 +16,14 @@
#include <AssetEditors/IEngineAssetEditor.hpp> #include <AssetEditors/IEngineAssetEditor.hpp>
#include <Widgets/WgViewportImGuiLayer.h> #include <Widgets/WgViewportImGuiLayer.h>
#include <Widgets/WgDetailsImGuiLayer.h> #include <Widgets/WgDetailsImGuiLayer.h>
#include <Widgets/ImGui/CbeImGui.hpp>
#include <IApplicationModule.h>
#include <EditorEngine.hpp> #include <EditorEngine.hpp>
#include <Widgets/ImGui/WgImGui.h> #include <Widgets/ImGui/WgImGui.h>
#include <CBEPackage.hpp> #include <CBEPackage.hpp>
#include <EditorHelpers.h> #include <EditorHelpers.h>
#include <EditorHelpersTransacted.hpp> #include <EditorHelpersTransacted.hpp>
#include <CranberryEngineApp.h>
namespace cbe namespace cbe
{ {
@@ -40,7 +43,8 @@ public:
void onCloseEditor() final; void onCloseEditor() final;
MessageResponse sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType) final; MessageResponse sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType) final;
void refreshEditor() final; void refreshEditor() final;
void onObjectsEdited(MAYBE_UNUSED ArrayView<Object *> editedObjs) final; void onObjectsEdited(MAYBE_UNUSED ArrayView<WeakObjectPtr> editedObjs) final;
void drawMenuBar() final;
/* Overrides ends */ /* Overrides ends */
private: private:
@@ -67,9 +71,10 @@ ActorPrefabAssetEditor::ActorPrefabAssetEditor(Object *inAsset, Object *inEditin
.edRoot = this, .edRoot = this,
.viewportFlags = EViewportFlags::EnableAll | EViewportFlags::NoWorldEdit, .viewportFlags = EViewportFlags::EnableAll | EViewportFlags::NoWorldEdit,
}); });
detailsLayer = std::make_shared<WgDetailsImGuiLayer>(WgDetailsCreateInfo{ detailsLayer = std::make_shared<WgDetailsImGuiLayer>(cbe::WgDetailsCreateInfo{
.edRoot = this, .edRoot = this,
.detailWndName = detailsWndName, .detailWndName = detailsWndName,
.flags = EDetailsFlags::SendCompSelMsgs,
}); });
WgImGui *wgImGui = gCBEditorEngine->getImGuiWidget(); WgImGui *wgImGui = gCBEditorEngine->getImGuiWidget();
@@ -114,45 +119,142 @@ void ActorPrefabAssetEditor::onCloseEditor()
cbe::MessageResponse ActorPrefabAssetEditor::sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType) cbe::MessageResponse ActorPrefabAssetEditor::sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType)
{ {
MessageResponse response{ .state = MessageResponse::None }; MessageResponse response{ .state = MessageResponse::None };
const bool bDetailsWgIssuer = msgDat.issuerData == std::bit_cast<uint64>(detailsLayer.get());
const bool bViewportWgIssuer = msgDat.issuerData == std::bit_cast<uint64>(viewportLayer.get());
switch (msgType) switch (msgType)
{ {
case cbe::CoreEdMessage_RefreshEd: case cbe::CoreEdMessage_RefreshEd:
if (msgDat.issuerData != std::bit_cast<uint64>(detailsLayer.get()) && std::holds_alternative<MessageData::ObjectsList>(msgDat.msg)) {
if (std::holds_alternative<MessageData::ObjectsList>(msgDat.msg))
{ {
detailsLayer->refreshDetailsDrawers(std::get<MessageData::ObjectsList>(msgDat.msg)); if (!bDetailsWgIssuer)
{
detailsLayer->refreshDetailsDrawers(std::get<MessageData::ObjectsList>(msgDat.msg));
}
response.state = MessageResponse::Remove;
}
else if (std::holds_alternative<EEdRefreshType>(msgDat.msg))
{
switch (std::get<EEdRefreshType>(msgDat.msg))
{
case EEdRefreshType::VisualOnly:
if (!bDetailsWgIssuer)
{
detailsLayer->refreshDetailsDrawers({});
}
break;
case EEdRefreshType::FullReset:
if (!bDetailsWgIssuer)
{
detailsLayer->resetDetails();
}
if (!bViewportWgIssuer)
{
viewportLayer->resetViewport();
}
break;
default:
break;
}
response.state = MessageResponse::Remove;
}
else if (std::holds_alternative<EmptyType>(msgDat.msg))
{
refreshEditor();
response.state = MessageResponse::Remove; response.state = MessageResponse::Remove;
} }
break; break;
}
case cbe::CoreEdMessage_SelectionChanged: case cbe::CoreEdMessage_SelectionChanged:
{ {
const std::vector<WeakObjPtr<Object>> &newSelObjs = viewportLayer->getWorldViewport().getSelectedObjects(); ActorPrefab *editingPrefab = static_cast<cbe::ActorPrefab *>(getEditingAsset());
WeakObjPtr<TransformComponent> rootTfComp = editingPrefab->getActorTemplate()->getRootComponent();
std::vector<cbe::WeakObjectPtr> newSelObjs = viewportLayer->getWorldViewport().getSelectedObjects();
const std::unordered_set<WorldSelectionProxyRef> &newSelProxies = viewportLayer->getWorldViewport().getSelectedProxies(); const std::unordered_set<WorldSelectionProxyRef> &newSelProxies = viewportLayer->getWorldViewport().getSelectedProxies();
/* Erase the root component from selected objects list */
debugAssert(rootTfComp.isValid());
std::erase_if(
newSelObjs,
[rootTfComp](const cbe::WeakObjectPtr &selObjPtr)
{
/* For actor prefab we do not have to select actor so remove anything other than required components. */
Object *selObj = selObjPtr.get();
CBEClass selType = selObj->getType();
return rootTfComp == selObj
|| !(
selType == TransformComponent::staticType()
|| PropertyHelper::isChildOf(selType, TransformLeafComponent::staticType())
|| PropertyHelper::isChildOf(selType, LogicComponent::staticType())
);
}
);
/* If nothing was selected. Select the actor we are editing */ /* If nothing was selected. Select the actor we are editing */
if (newSelProxies.empty() && newSelObjs.empty()) if (newSelProxies.empty() && newSelObjs.empty())
{ {
viewportLayer->getWorldViewport().selectFromObjects(static_cast<cbe::ActorPrefab *>(getEditingAsset())->getActorTemplate()); viewportLayer->getWorldViewport().selectFromObjects(editingPrefab->getActorTemplate());
detailsLayer->onSelectionChanged();
viewportLayer->onSelectionChanged();
} }
else
{
std::vector<cbe::Object *> finalSelObjs;
std::vector<WorldSelectionProxyRef> finalSelProxies;
finalSelObjs.reserve(newSelObjs.size());
finalSelProxies.reserve(newSelProxies.size());
for (const cbe::WeakObjectPtr &ptr : newSelObjs)
{
if (ptr)
{
finalSelObjs.emplace_back(ptr.get());
}
}
for (const WorldSelectionProxyRef &proxyRef : newSelProxies)
{
finalSelProxies.emplace_back(proxyRef);
}
viewportLayer->getWorldViewport().selectManual(finalSelObjs, finalSelProxies);
// TODO(ASAP) : Update selection. cbe::MessageData::SubSelectionData msg{
detailsLayer->onSelectionChanged(); .objs = newSelObjs,
viewportLayer->onSelectionChanged(); .proxies = newSelProxies,
};
pushMessage(
cbe::MessageData{
.msg = msg,
},
cbe::CoreEdMessage_SubSelectionChanged
);
}
response.state = MessageResponse::Remove; response.state = MessageResponse::Remove;
break; break;
} }
case cbe::CoreEdMessage_SubSelectionChanged: case cbe::CoreEdMessage_SubSelectionChanged:
// TODO(ASAP) : Invoke sub selection update logics {
debugAssert(std::holds_alternative<cbe::MessageData::SubSelectionData>(msgDat.msg));
const cbe::MessageData::SubSelectionData &subSelDat = std::get<cbe::MessageData::SubSelectionData>(msgDat.msg);
if (!bViewportWgIssuer)
{
viewportLayer->onSubselectionChanged(subSelDat.objs, subSelDat.proxies);
}
if (!bDetailsWgIssuer)
{
detailsLayer->onSubselectionChanged(subSelDat.objs, subSelDat.proxies);
}
response.state = MessageResponse::Remove; response.state = MessageResponse::Remove;
break; break;
}
case cbe::CoreEdMessage_SelectionTransformed: case cbe::CoreEdMessage_SelectionTransformed:
/* Must be skipped if the issuer is same. As always refreshing makes transacting hard. */ /* Must be skipped if the issuer is same. As always refreshing makes transacting hard. */
if (msgDat.issuerData != std::bit_cast<uint64>(viewportLayer.get())) if (!bViewportWgIssuer)
{ {
viewportLayer->onSelectionTransformed(); viewportLayer->onSelectionTransformed();
} }
if (msgDat.issuerData != std::bit_cast<uint64>(detailsLayer.get())) if (!bDetailsWgIssuer)
{ {
detailsLayer->onSelectionTransformed(); detailsLayer->onSelectionTransformed();
} }
@@ -170,13 +272,29 @@ cbe::MessageResponse ActorPrefabAssetEditor::sendMessage(MAYBE_UNUSED const Mess
void ActorPrefabAssetEditor::refreshEditor() { detailsLayer->refreshDetailsDrawers({}); } void ActorPrefabAssetEditor::refreshEditor() { detailsLayer->refreshDetailsDrawers({}); }
void ActorPrefabAssetEditor::onObjectsEdited(MAYBE_UNUSED ArrayView<Object *> editedObjs) void ActorPrefabAssetEditor::onObjectsEdited(MAYBE_UNUSED ArrayView<WeakObjectPtr> editedObjs)
{ {
/* Since we need the transform gizmo updated as well on external edit of objects */ /* Since we need the transform gizmo updated as well on external edit of objects */
pushMessage(CoreEdMessage_SelectionTransformed); pushMessage(CoreEdMessage_SelectionTransformed);
IEngineAssetEditor::onObjectsEdited(editedObjs); IEngineAssetEditor::onObjectsEdited(editedObjs);
} }
void ActorPrefabAssetEditor::drawMenuBar()
{
if (ImGui::BeginMenu("Edit"))
{
if (ImGui::BeginMenu("Change Parent"))
{
const std::variant newParent = static_cast<ActorPrefabAssetFactory *>(getOwningFactory())->drawParentPrefabSelector();
cbe::ignoreUnused(newParent);
// TODO(ASAP) : Update prefab parent.
ImGui::EndMenu();
}
ImGui::EndMenu();
}
}
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// ActorPrefabAssetFactory implementation // ActorPrefabAssetFactory implementation
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@@ -186,16 +304,87 @@ ActorPrefabAssetFactory::ActorPrefabAssetFactory()
.assetClass = cbe::ActorPrefab::staticType(), .assetClass = cbe::ActorPrefab::staticType(),
.assetCategory = "Prefabs", .assetCategory = "Prefabs",
}) })
, parentPrefabCntx{ .baseClassType = cbe::ActorPrefab::staticType() }
{} {}
ActorPrefabAssetFactory::~ActorPrefabAssetFactory() {} ActorPrefabAssetFactory::~ActorPrefabAssetFactory() {}
Object *ActorPrefabAssetFactory::createAsset(const AssetCreateInfo &ci) std::variant<NullType, CBEClass, String> ActorPrefabAssetFactory::drawParentPrefabSelector()
{
std::variant<NullType, CBEClass, String> retValue;
EdClassCacheList &actorClasses = gCBEditorEngine->getActorClassesCache();
if (!actorClasses.classList.empty() && ImGui::BeginMenu("Parent Class"))
{
if (CBEClass clazz = cbe::EditorWidgetsHelper::drawClassMenuList(actorClasses, wg_consts::MENUITEM_WIDTH, true))
{
retValue = clazz;
}
ImGui::EndMenu();
}
constexpr const AChar *parentPrefabMenu = "Parent Prefab";
const bool bParentPrefabOpen = cbe::ImGuiHelpers::isPopupOpen(cbe::ImGuiHelpers::getPopupId(parentPrefabMenu));
if (ImGui::BeginMenu(parentPrefabMenu))
{
if (!bParentPrefabOpen)
{
parentPrefabCntx.bListUptoDate = false;
}
CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());
AssetManager *assetManager = application->getAssetManager();
debugAssert(assetManager != nullptr);
const std::optional selPrefab
= GenericFieldCustomizer::drawWidgetClassPointerCntxMenuList(parentPrefabCntx, *assetManager, 200.0f, false);
if (selPrefab)
{
retValue = selPrefab->first;
}
ImGui::EndMenu();
}
return retValue;
}
static Object *createApFromClass(const AssetCreateInfo &ci, CBEClass parentClazz)
{ {
cbe::ActorPrefab *prefab cbe::ActorPrefab *prefab
= cbe::create<cbe::ActorPrefab, StringID, String>(ci.assetName, ci.outer, ci.flags, Actor::staticType()->name, ci.assetName); = cbe::create<cbe::ActorPrefab, StringID, String>(ci.assetName, ci.outer, ci.flags, parentClazz->name, ci.assetName);
return prefab; return prefab;
} }
static Object *createApFromPrefab(const AssetCreateInfo &ci, String prefabPath)
{
cbe::ActorPrefab *parentPrefab = cbe::getOrLoad<cbe::ActorPrefab>(prefabPath);
cbe::ActorPrefab *prefab
= cbe::create<cbe::ActorPrefab, cbe::ActorPrefab *, String>(ci.assetName, ci.outer, ci.flags, parentPrefab, ci.assetName);
return prefab;
}
Object *ActorPrefabAssetFactory::createAsset(const AssetCreateInfo &ci) { return createApFromClass(ci, Actor::staticType()); }
cbe::AssetCreateCustomCallback ActorPrefabAssetFactory::drawCustomCreateMenus()
{
return std::visit(
[]<typename T>(const T &parent)
{
cbe::AssetCreateCustomCallback createCb;
if constexpr (std::same_as<CBEClass, T>)
{
createCb.bindStatic(&createApFromClass, parent);
}
else if constexpr (std::same_as<String, T>)
{
createCb.bindStatic(&createApFromPrefab, parent);
}
return createCb;
},
drawParentPrefabSelector()
);
}
ICoreAssetEditorRef ActorPrefabAssetFactory::createAssetEditorInternal(const AssetEditorCreateInfo &ci) ICoreAssetEditorRef ActorPrefabAssetFactory::createAssetEditorInternal(const AssetEditorCreateInfo &ci)
{ {
cbe::Package *pkg = cbe::cast<cbe::Package>(ci.asset->getOuterMost()); cbe::Package *pkg = cbe::cast<cbe::Package>(ci.asset->getOuterMost());
@@ -212,17 +401,18 @@ ICoreAssetEditorRef ActorPrefabAssetFactory::createAssetEditorInternal(const Ass
/* Now add the actor prefab to world and copy it */ /* Now add the actor prefab to world and copy it */
ActorPrefab *orgPrefab = static_cast<ActorPrefab *>(ci.asset); ActorPrefab *orgPrefab = static_cast<ActorPrefab *>(ci.asset);
ActorPrefab *edPrefab = nullptr; ActorPrefab *edPrefab = nullptr;
/* Prefab cannot be transient since it has to be transacted */
if (orgPrefab->getParentPrefab() != nullptr) if (orgPrefab->getParentPrefab() != nullptr)
{ {
edPrefab = ActorPrefab::prefabFromActor(EditorHelpers::addActorToWorld( edPrefab = ActorPrefab::prefabFromActor(
world, orgPrefab->getParentPrefab(), orgPrefab->getActorTemplate()->getObjectData().name, cbe::ObjFlag_Transient EditorHelpers::addActorToWorld(world, orgPrefab->getParentPrefab(), orgPrefab->getActorTemplate()->getObjectData().name, 0)
)); );
} }
else else
{ {
edPrefab = ActorPrefab::prefabFromActor(EditorHelpers::addActorToWorld( edPrefab = ActorPrefab::prefabFromActor(
world, orgPrefab->getActorClass(), orgPrefab->getActorTemplate()->getObjectData().name, cbe::ObjFlag_Transient EditorHelpers::addActorToWorld(world, orgPrefab->getActorClass(), orgPrefab->getActorTemplate()->getObjectData().name, 0)
)); );
} }
edPrefab->copyFrom(orgPrefab); edPrefab->copyFrom(orgPrefab);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date December 2025 * \date December 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -12,9 +12,11 @@
#pragma once #pragma once
#include <ICoreAssetEditor.hpp> #include <ICoreAssetEditor.hpp>
#include <DetailCustomizers/GenericFieldCustomizer.hpp>
namespace cbe namespace cbe
{ {
class ActorPrefab;
class ActorPrefabAssetFactory : public ICoreAssetFactory class ActorPrefabAssetFactory : public ICoreAssetFactory
{ {
@@ -22,8 +24,13 @@ public:
ActorPrefabAssetFactory(); ActorPrefabAssetFactory();
~ActorPrefabAssetFactory(); ~ActorPrefabAssetFactory();
protected: std::variant<NullType, CBEClass, String> drawParentPrefabSelector();
/* ICoreAssetFactory overrides */ /* ICoreAssetFactory overrides */
public:
AssetCreateCustomCallback drawCustomCreateMenus() final;
protected:
bool canCreateAsset() const final { return true; } bool canCreateAsset() const final { return true; }
Object *createAsset(const AssetCreateInfo &ci) final; Object *createAsset(const AssetCreateInfo &ci) final;
@@ -36,6 +43,8 @@ private:
/* Necessary to keep the actor prefab list updated. */ /* Necessary to keep the actor prefab list updated. */
DelegateHandle packageScannedEventHnd; DelegateHandle packageScannedEventHnd;
DelegateHandle packageDeletedEventHnd; DelegateHandle packageDeletedEventHnd;
GenericFieldCustomizer::ClassObjectContext parentPrefabCntx;
}; };
} // namespace cbe } // namespace cbe

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date September 2025 * \date September 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -75,7 +75,7 @@ void GenericAssetEditor::drawImGui()
} }
} }
void GenericAssetEditor::onObjectsEdited(ArrayView<Object *> editedObjs) void GenericAssetEditor::onObjectsEdited(ArrayView<WeakObjectPtr> editedObjs)
{ {
#if DEBUG_VALIDATIONS_ENABLED #if DEBUG_VALIDATIONS_ENABLED
auto editingAssetItr = std::find(editedObjs.cbegin(), editedObjs.cend(), editingAsset); auto editingAssetItr = std::find(editedObjs.cbegin(), editedObjs.cend(), editingAsset);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date September 2025 * \date September 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -33,7 +33,7 @@ public:
/* IEngineAssetEditor overrides */ /* IEngineAssetEditor overrides */
void buildEditorLayout(ImGuiID dockId) final; void buildEditorLayout(ImGuiID dockId) final;
void drawImGui() final; void drawImGui() final;
void onObjectsEdited(ArrayView<Object *> editedObjs) final; void onObjectsEdited(ArrayView<WeakObjectPtr> editedObjs) final;
void refreshEditor() final; void refreshEditor() final;
/* ICoreAssetEditor overrides */ /* ICoreAssetEditor overrides */

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date September 2025 * \date September 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -122,7 +122,7 @@ MessageResponse IEngineAssetEditor::sendMessage(MAYBE_UNUSED const MessageData &
break; break;
case cbe::CoreEdMessage_ObjsEdited: case cbe::CoreEdMessage_ObjsEdited:
{ {
ArrayView<Object *> editedObjs; ArrayView<WeakObjectPtr> editedObjs;
if (std::holds_alternative<MessageData::ObjectsList>(msgDat.msg)) if (std::holds_alternative<MessageData::ObjectsList>(msgDat.msg))
{ {
editedObjs = std::get<MessageData::ObjectsList>(msgDat.msg); editedObjs = std::get<MessageData::ObjectsList>(msgDat.msg);
@@ -148,8 +148,15 @@ void IEngineAssetEditor::undo()
return; return;
} }
debugAssert(!editedObjs.empty()); {
onObjectsEdited(editedObjs); debugAssert(!editedObjs.empty());
std::vector<WeakObjectPtr> weakObjPtrs{ editedObjs.size() };
for (SizeT i = 0; i < editedObjs.size(); ++i)
{
weakObjPtrs[i] = editedObjs[i];
}
onObjectsEdited(weakObjPtrs);
}
cbe::NotificationInfo notifyInfo{ .name = STR_FORMAT("Undo: {}", std::move(str)), .durrInSeconds = 2 }; cbe::NotificationInfo notifyInfo{ .name = STR_FORMAT("Undo: {}", std::move(str)), .durrInSeconds = 2 };
gCBEditorEngine->addNotification(notifyInfo); gCBEditorEngine->addNotification(notifyInfo);
@@ -165,8 +172,15 @@ void IEngineAssetEditor::redo()
return; return;
} }
debugAssert(!editedObjs.empty()); {
onObjectsEdited(editedObjs); debugAssert(!editedObjs.empty());
std::vector<WeakObjectPtr> weakObjPtrs{ editedObjs.size() };
for (SizeT i = 0; i < editedObjs.size(); ++i)
{
weakObjPtrs[i] = editedObjs[i];
}
onObjectsEdited(weakObjPtrs);
}
cbe::NotificationInfo notifyInfo{ .name = STR_FORMAT("Redo: {}", std::move(str)), .durrInSeconds = 2 }; cbe::NotificationInfo notifyInfo{ .name = STR_FORMAT("Redo: {}", std::move(str)), .durrInSeconds = 2 };
gCBEditorEngine->addNotification(notifyInfo); gCBEditorEngine->addNotification(notifyInfo);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date September 2025 * \date September 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -50,7 +50,10 @@ public:
/* Shortcuts must be handled here. */ /* Shortcuts must be handled here. */
virtual void editorShortcuts() {} virtual void editorShortcuts() {}
/* Must be called if object gets edited outside the editor itself. For example undo/redo */ /* Must be called if object gets edited outside the editor itself. For example undo/redo */
virtual void onObjectsEdited(MAYBE_UNUSED ArrayView<Object *> editedObjs) { pushMessage(CoreEdMessage_RefreshEd); } virtual void onObjectsEdited(MAYBE_UNUSED ArrayView<WeakObjectPtr> editedObjs)
{
pushMessage(cbe::MessageData{ .msg = std::vector<WeakObjectPtr>(editedObjs.cbegin(), editedObjs.cend()) }, CoreEdMessage_RefreshEd);
}
virtual void refreshEditor() {} virtual void refreshEditor() {}
/* Interface ends */ /* Interface ends */

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date October 2025 * \date October 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -24,6 +24,7 @@ struct MaterialTextureEntry
{ {
String paramName; String paramName;
ObjectPath texPath; ObjectPath texPath;
uint32 offset;
}; };
struct MaterialSaveTransaction struct MaterialSaveTransaction
{ {
@@ -51,6 +52,7 @@ static void matInstToTransaction(MaterialSaveTransaction &outTransaction, Materi
outTransaction.texture2dRefs[i] = MaterialTextureEntry{ outTransaction.texture2dRefs[i] = MaterialTextureEntry{
.paramName = matInst->texture2dRefs[i].paramName, .paramName = matInst->texture2dRefs[i].paramName,
.texPath = ObjectPath(matInst->texture2dRefs[i].texture.get()), .texPath = ObjectPath(matInst->texture2dRefs[i].texture.get()),
.offset = matInst->texture2dRefs[i].layoutOffset,
}; };
} }
} }
@@ -65,6 +67,7 @@ static void transactionToMatInst(MaterialInstance *matInst, const MaterialSaveTr
matInst->texture2dRefs[i] = MaterialTexture2DParam{ matInst->texture2dRefs[i] = MaterialTexture2DParam{
.paramName = transaction.texture2dRefs[i].paramName, .paramName = transaction.texture2dRefs[i].paramName,
.texture = cast<Texture2D>(transaction.texture2dRefs[i].texPath.getObject()), .texture = cast<Texture2D>(transaction.texture2dRefs[i].texPath.getObject()),
.layoutOffset = transaction.texture2dRefs[i].offset,
}; };
} }
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -15,9 +15,10 @@
#include <Widgets/WgEditorImGuiLayer.h> #include <Widgets/WgEditorImGuiLayer.h>
#include <IEditorCore.h> #include <IEditorCore.h>
/* Importers */ /* Importers */
#include <StaticMeshImporter.hpp> #include <Importers/StaticMeshImporter.hpp>
#include <AudioImporter.hpp> #include <Importers/AssimpImporter.hpp>
#include <Texture2DImporter.hpp> #include <Importers/AudioImporter.hpp>
#include <Importers/Texture2DImporter.hpp>
/* Viewport drawers */ /* Viewport drawers */
#include <ViewportDrawers/TransformComponentViewportDrawer.hpp> #include <ViewportDrawers/TransformComponentViewportDrawer.hpp>
/* Details Customizers */ /* Details Customizers */
@@ -44,6 +45,7 @@ class CBEEditorModule final : public ICBEEditor
private: private:
/* Importers */ /* Importers */
ObjStaticMeshImporter smFromObjImporter; ObjStaticMeshImporter smFromObjImporter;
AssimpImporter assimpImporter;
StbTexture2DImporter stbTextureImporter; StbTexture2DImporter stbTextureImporter;
@@ -82,6 +84,7 @@ void CBEEditorModule::init()
IEditorCore *editorCore = ModuleManager::get()->getOrLoadModulePtr<IEditorCore>(TCHAR("EditorCore")); IEditorCore *editorCore = ModuleManager::get()->getOrLoadModulePtr<IEditorCore>(TCHAR("EditorCore"));
debugAssert(editorCore); debugAssert(editorCore);
editorCore->registerAssetImporter(&smFromObjImporter); editorCore->registerAssetImporter(&smFromObjImporter);
editorCore->registerAssetImporter(&assimpImporter);
editorCore->registerAssetImporter(&stbTextureImporter); editorCore->registerAssetImporter(&stbTextureImporter);
editorCore->registerAssetImporter(&audioModuleImporter); editorCore->registerAssetImporter(&audioModuleImporter);
@@ -99,6 +102,7 @@ void CBEEditorModule::release()
if (IEditorCore *editorCore = IEditorCore::get()) if (IEditorCore *editorCore = IEditorCore::get())
{ {
editorCore->unregisterAssetImporter(&smFromObjImporter); editorCore->unregisterAssetImporter(&smFromObjImporter);
editorCore->unregisterAssetImporter(&assimpImporter);
editorCore->unregisterAssetImporter(&stbTextureImporter); editorCore->unregisterAssetImporter(&stbTextureImporter);
editorCore->unregisterAssetImporter(&audioModuleImporter); editorCore->unregisterAssetImporter(&audioModuleImporter);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -186,8 +186,6 @@ void GenericFieldCustomizer::initSelectionDetails(ClassObjectContext &inOutCntx,
return; return;
} }
} }
// TODO(Jeslas) : Right now not considering the world actors or child of selected object's actor prefab.
} }
GenericFieldCustomizer::DrawFieldArgs GenericFieldCustomizer::preDrawFieldWidget(const SelectionFieldCustomizationArgs &args) GenericFieldCustomizer::DrawFieldArgs GenericFieldCustomizer::preDrawFieldWidget(const SelectionFieldCustomizationArgs &args)
@@ -2487,8 +2485,6 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetbool(Fundam
return editState; return editState;
} }
constexpr float ASSET_SELECTOR_ICON_SIZE = 40;
constexpr float ASSET_SELECTOR_ICON_FONT_SCALE = 1.5f;
GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointerCntx(ClassObjectContext &cntx) GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointerCntx(ClassObjectContext &cntx)
{ {
CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication()); CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());
@@ -2512,7 +2508,7 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
ImGui::BeginGroup(); ImGui::BeginGroup();
const bool bSelectionValid = !cntx.objPath.getObjectName().empty(); const bool bSelectionValid = !cntx.objPath.getObjectName().empty();
/* First draw the Icon */ /* First draw the Icon */
const Rect iconRect{ ImGui::GetCursorScreenPos(), Vector2(ImGui::GetCursorScreenPos()) + Vector2(ASSET_SELECTOR_ICON_SIZE) }; const Rect iconRect{ ImGui::GetCursorScreenPos(), Vector2(ImGui::GetCursorScreenPos()) + Vector2(wg_consts::ASSET_SELECTOR_ICON_SIZE) };
std::string_view iconTxt = "NUL"; std::string_view iconTxt = "NUL";
Color iconBgColor{ ImGui::GetColorU32(ImGuiCol_FrameBg) }; Color iconBgColor{ ImGui::GetColorU32(ImGuiCol_FrameBg) };
if (bSelectionValid) if (bSelectionValid)
@@ -2520,19 +2516,20 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
iconBgColor = cntx.selDetails.classColor; iconBgColor = cntx.selDetails.classColor;
iconTxt = &cntx.selDetails.classInitials[0]; iconTxt = &cntx.selDetails.classInitials[0];
} }
EditorWidgetsHelper::drawPackageIcon(iconRect, iconTxt, iconBgColor, ASSET_SELECTOR_ICON_FONT_SCALE); EditorWidgetsHelper::drawPackageIcon(iconRect, iconTxt, iconBgColor, wg_consts::ASSET_SELECTOR_ICON_FONT_SCALE);
/* Used to do the selection focus in the first open frame. Do before any chance of popup being open. */ /* Used to do the selection focus in the first open frame. Do before any chance of popup being open. */
const bool bPopupOpenLastFrame = cbe::ImGuiHelpers::isPopupOpen(assetListPopupId); const bool bPopupOpenLastFrame = cbe::ImGuiHelpers::isPopupOpen(assetListPopupId);
const bool bIconClicked = ImGui::Selectable( const bool bIconClicked = ImGui::Selectable(
"###ObjectIcon", false, ImGuiSelectableFlags_AllowDoubleClick, ImVec2(ASSET_SELECTOR_ICON_SIZE, ASSET_SELECTOR_ICON_SIZE) "###ObjectIcon", false, ImGuiSelectableFlags_AllowDoubleClick,
ImVec2(wg_consts::ASSET_SELECTOR_ICON_SIZE, wg_consts::ASSET_SELECTOR_ICON_SIZE)
); );
const bool bIconDoubleClicked = bIconClicked && ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left); const bool bIconDoubleClicked = bIconClicked && ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left);
if (bIconDoubleClicked) if (bIconDoubleClicked)
{ {
if (bSelectionValid) if (bSelectionValid)
{ {
gCBEditorEngine->openAssetEditor(cntx.objPath.getObject()); gCBEditorEngine->openAssetEditor(cntx.objPath);
} }
} }
else if (bIconClicked) else if (bIconClicked)
@@ -2551,157 +2548,16 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
ImGui::SetNextItemWidth(popupItemWidth - (ImGuiHelpers::calcButtonSize(mat_icon::MOP).x * 3)); ImGui::SetNextItemWidth(popupItemWidth - (ImGuiHelpers::calcButtonSize(mat_icon::MOP).x * 3));
if (ImGui::BeginCombo(assetListPopup, comboPreview, ImGuiComboFlags_HeightLarge)) if (ImGui::BeginCombo(assetListPopup, comboPreview, ImGuiComboFlags_HeightLarge))
{ {
if (!cntx.bListUptoDate) const std::optional newSel
= drawWidgetClassPointerCntxMenuList(cntx, *assetManager, popupItemWidth, !bPopupOpenLastFrame && bSelectionValid);
if (newSel)
{ {
generateObjectListForContext(cntx, *assetManager); cntx.objPath = newSel->first.getChar();
cntx.selDetails = cntx.objsList[newSel->second];
debugAssertf(editState == Edit_None, "Two selection cannot be made in same frame!");
editState = Edit_Committed;
} }
const float filterStartPos = ImGui::GetCursorStartPos().y;
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
if (cbe::ImGuiHelpers::inputTextWithHint(
"###AssetFilterText", "Filter Asset", &cntx.filter,
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_NoHorizontalScroll
))
{
/* Scroll back to filter every time filter is modified. */
ImGui::SetScrollFromPosY(filterStartPos, 0.0f);
const String filter = cntx.filter.toLowerCopy();
for (SizeT i = 0; i < cntx.objsList.size(); ++i)
{
const ObjectPackageDetail &objDetail = cntx.objsList[i];
const AssetManager::PackageAllocator::AllocHandle &allocHnd
= *reinterpret_cast<const AssetManager::PackageAllocator::AllocHandle *>(&objDetail.packageAllocHnd);
const AssetManager::AssetPackage *assetPack = assetManager->getAssetPackage(allocHnd);
cntx.filteredBits[i] = filter.empty() || assetPack->rootObjectPath.toLowerCopy().find(filter) != String::npos;
}
}
/* Scroll to text filter the moment it becomes active. */
if (ImGui::IsItemActivated())
{
ImGui::SetScrollFromPosY(filterStartPos, 0.0f);
}
ImDrawList *drawList = ImGui::GetWindowDrawList();
const ImGuiStyle &imguiStyle = ImGui::GetStyle();
const Vector2 listStartPos = ImGui::GetCursorScreenPos();
const Vector2 selectableSize{ popupItemWidth,
Math::max(ASSET_SELECTOR_ICON_SIZE, 2 * ImGui::GetTextLineHeight()) + (2 * imguiStyle.FramePadding.y) };
ImGuiListClipper clipper;
clipper.Begin(static_cast<int32>(cntx.filteredBits.countOnes()), selectableSize.y);
/* Try to focus to the current selections */
if (!bPopupOpenLastFrame && bSelectionValid)
{
int64 lineIdx = -1;
for (uint64 entryIdx = 0, line = 0; entryIdx < cntx.objsList.size(); ++entryIdx)
{
if (!cntx.filteredBits[entryIdx])
{
continue;
}
const ObjectPackageDetail &objDetail = cntx.objsList[entryIdx];
const bool bContentSelected = cntx.selDetails.packageAllocHnd == objDetail.packageAllocHnd;
if (bContentSelected)
{
lineIdx = std::bit_cast<int64>(line);
break;
}
line++;
}
if (lineIdx >= 0)
{
const float lineStartPosY = listStartPos.y + (static_cast<float>(lineIdx) * selectableSize.y);
/* Pos must be set in window local space. */
ImGui::SetScrollFromPosY(lineStartPosY - ImGui::GetWindowPos().y);
}
}
/* Find the first filtered index */
uint64 entryIdx = 0;
uint64 setBitIdx = 0;
for (; entryIdx < cntx.objsList.size() && !cntx.filteredBits[entryIdx]; ++entryIdx)
{}
while (clipper.Step())
{
for (int32 lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx)
{
/* Iterate until reaching setBitIdx that matches line idx. entryIdx will point to actual entry in objList */
for (; entryIdx < cntx.objsList.size() && setBitIdx < static_cast<uint64>(lineIdx); ++entryIdx)
{
setBitIdx += cntx.filteredBits[entryIdx] ? 1 : 0;
}
const ObjectPackageDetail &objDetail = cntx.objsList[entryIdx];
const AssetManager::PackageAllocator::AllocHandle &allocHnd
= *reinterpret_cast<const AssetManager::PackageAllocator::AllocHandle *>(&objDetail.packageAllocHnd);
const AssetManager::AssetPackage *assetPack = assetManager->getAssetPackage(allocHnd);
const Vector2 contentStartPos = listStartPos + Vector2(0, (static_cast<float>(lineIdx) * selectableSize.y));
ImGui::PushID(std::format("{}.{}", entryIdx, objDetail.name).c_str());
ImGui::SetCursorScreenPos(contentStartPos);
const bool bContentSelected = cntx.selDetails.packageAllocHnd == objDetail.packageAllocHnd;
const bool bContentVisible = ImGui::IsRectVisible(selectableSize);
const bool bContentClicked = ImGui::Selectable("", bContentSelected, ImGuiSelectableFlags_None, selectableSize);
if (bContentSelected)
{
ImGui::SetItemDefaultFocus();
}
if (ImGui::BeginItemTooltip())
{
ImGuiHelpers::textUnformatted(assetPack->rootObjectPath);
ImGui::EndTooltip();
}
if (bContentVisible)
{
/* Draw Package icon, center align in Y */
const float iconAlignOffsetY = (selectableSize.y - (2 * imguiStyle.FramePadding.y) - ASSET_SELECTOR_ICON_SIZE) * 0.5f;
const Vector2 iconRectStartPos = contentStartPos + Vector2(imguiStyle.FramePadding) + Vector2(0, iconAlignOffsetY);
const Vector2 iconRectEndPos = iconRectStartPos + ASSET_SELECTOR_ICON_SIZE;
cbe::EditorWidgetsHelper::drawPackageIcon(
{ iconRectStartPos, iconRectEndPos }, objDetail.classInitials, objDetail.classColor, ASSET_SELECTOR_ICON_FONT_SCALE
);
/* Center align text */
const float labelStartX = iconRectEndPos.x + imguiStyle.ItemInnerSpacing.x;
const float labelWidth = selectableSize.x - (labelStartX - contentStartPos.x) - imguiStyle.FramePadding.x;
const float labelEndX = labelStartX + labelWidth;
const float labelHeight
= ImGui::CalcTextSize(objDetail.name.c_str(), objDetail.name.c_str() + objDetail.name.length(), false, labelWidth).y;
const float labelAlignOffsetY = (selectableSize.y - (2 * imguiStyle.FramePadding.y) - labelHeight) * 0.5f;
const float labelStartY = contentStartPos.y + imguiStyle.FramePadding.y + labelAlignOffsetY;
const float labelEndY = labelStartY + labelHeight;
const ImVec4 clipRect{
labelStartX,
labelStartY,
labelEndX,
labelEndY,
};
drawList->AddText(
ImGui::GetFont(), ImGui::GetFontSize(), ImVec2(clipRect.x, clipRect.y), ImGui::GetColorU32(ImGuiCol_Text),
objDetail.name.c_str(), objDetail.name.c_str() + objDetail.name.length(), labelWidth, &clipRect
);
}
if (bContentClicked)
{
cntx.objPath = assetPack->rootObjectPath.getChar();
cntx.selDetails = objDetail;
debugAssertf(editState == Edit_None, "Two selection cannot be made in same frame!");
editState = Edit_Committed;
}
ImGui::PopID();
}
}
clipper.End();
ImGui::EndCombo(); ImGui::EndCombo();
} }
ImGui::EndGroup(); ImGui::EndGroup();
@@ -2774,6 +2630,167 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
return editState; return editState;
} }
std::optional<std::pair<String, uint64>> GenericFieldCustomizer::drawWidgetClassPointerCntxMenuList(
ClassObjectContext &cntx, AssetManager &assetManager, float popupItemWidth, bool bFocusSelDetails
)
{
std::optional<std::pair<String, uint64>> retVal;
if (!cntx.bListUptoDate)
{
generateObjectListForContext(cntx, assetManager);
}
const float filterStartPos = ImGui::GetCursorStartPos().y;
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
if (cbe::ImGuiHelpers::inputTextWithHint(
"###AssetFilterText", "Filter Asset", &cntx.filter,
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_NoHorizontalScroll
))
{
/* Scroll back to filter every time filter is modified. */
ImGui::SetScrollFromPosY(filterStartPos, 0.0f);
const String filter = cntx.filter.toLowerCopy();
for (SizeT i = 0; i < cntx.objsList.size(); ++i)
{
const ObjectPackageDetail &objDetail = cntx.objsList[i];
const AssetManager::PackageAllocator::AllocHandle &allocHnd
= *reinterpret_cast<const AssetManager::PackageAllocator::AllocHandle *>(&objDetail.packageAllocHnd);
const AssetManager::AssetPackage *assetPack = assetManager.getAssetPackage(allocHnd);
cntx.filteredBits[i] = filter.empty() || assetPack->rootObjectPath.toLowerCopy().find(filter) != String::npos;
}
}
/* Scroll to text filter the moment it becomes active. */
if (ImGui::IsItemActivated())
{
ImGui::SetScrollFromPosY(filterStartPos, 0.0f);
}
ImDrawList *drawList = ImGui::GetWindowDrawList();
const ImGuiStyle &imguiStyle = ImGui::GetStyle();
const Vector2 listStartPos = ImGui::GetCursorScreenPos();
const Vector2 selectableSize{
popupItemWidth,
Math::max(wg_consts::ASSET_SELECTOR_ICON_SIZE, 2 * ImGui::GetTextLineHeight()) + (2 * imguiStyle.FramePadding.y),
};
ImGuiListClipper clipper;
clipper.Begin(static_cast<int32>(cntx.filteredBits.countOnes()), selectableSize.y);
/* Try to focus to the current selections */
if (bFocusSelDetails)
{
int64 lineIdx = -1;
for (uint64 entryIdx = 0, line = 0; entryIdx < cntx.objsList.size(); ++entryIdx)
{
if (!cntx.filteredBits[entryIdx])
{
continue;
}
const ObjectPackageDetail &objDetail = cntx.objsList[entryIdx];
const bool bContentSelected = cntx.selDetails.packageAllocHnd == objDetail.packageAllocHnd;
if (bContentSelected)
{
lineIdx = std::bit_cast<int64>(line);
break;
}
line++;
}
if (lineIdx >= 0)
{
const float lineStartPosY = listStartPos.y + (static_cast<float>(lineIdx) * selectableSize.y);
/* Pos must be set in window local space. */
ImGui::SetScrollFromPosY(lineStartPosY - ImGui::GetWindowPos().y);
}
}
/* Find the first filtered index */
uint64 entryIdx = 0;
uint64 setBitIdx = 0;
for (; entryIdx < cntx.objsList.size() && !cntx.filteredBits[entryIdx]; ++entryIdx)
{}
while (clipper.Step())
{
for (int32 lineIdx = clipper.DisplayStart; lineIdx < clipper.DisplayEnd; ++lineIdx)
{
/* Iterate until reaching setBitIdx that matches line idx. entryIdx will point to actual entry in objList */
for (; entryIdx < cntx.objsList.size() && setBitIdx < static_cast<uint64>(lineIdx); ++entryIdx)
{
setBitIdx += cntx.filteredBits[entryIdx] ? 1 : 0;
}
const ObjectPackageDetail &objDetail = cntx.objsList[entryIdx];
const AssetManager::PackageAllocator::AllocHandle &allocHnd
= *reinterpret_cast<const AssetManager::PackageAllocator::AllocHandle *>(&objDetail.packageAllocHnd);
const AssetManager::AssetPackage *assetPack = assetManager.getAssetPackage(allocHnd);
const Vector2 contentStartPos = listStartPos + Vector2(0, (static_cast<float>(lineIdx) * selectableSize.y));
ImGui::PushID(std::format("{}.{}", entryIdx, objDetail.name).c_str());
ImGui::SetCursorScreenPos(contentStartPos);
const bool bContentSelected = cntx.selDetails.packageAllocHnd == objDetail.packageAllocHnd;
const bool bContentVisible = ImGui::IsRectVisible(selectableSize);
const bool bContentClicked = ImGui::Selectable("", bContentSelected, ImGuiSelectableFlags_None, selectableSize);
if (bContentSelected)
{
ImGui::SetItemDefaultFocus();
}
if (ImGui::BeginItemTooltip())
{
ImGuiHelpers::textUnformatted(assetPack->rootObjectPath);
ImGui::EndTooltip();
}
if (bContentVisible)
{
/* Draw Package icon, center align in Y */
const float iconAlignOffsetY
= (selectableSize.y - (2 * imguiStyle.FramePadding.y) - wg_consts::ASSET_SELECTOR_ICON_SIZE) * 0.5f;
const Vector2 iconRectStartPos = contentStartPos + Vector2(imguiStyle.FramePadding) + Vector2(0, iconAlignOffsetY);
const Vector2 iconRectEndPos = iconRectStartPos + wg_consts::ASSET_SELECTOR_ICON_SIZE;
cbe::EditorWidgetsHelper::drawPackageIcon(
{ iconRectStartPos, iconRectEndPos }, objDetail.classInitials, objDetail.classColor,
wg_consts::ASSET_SELECTOR_ICON_FONT_SCALE
);
/* Center align text, some logic can be moved out of loop */
const float labelStartX = iconRectEndPos.x + imguiStyle.ItemInnerSpacing.x;
const float labelWidth = selectableSize.x - (labelStartX - contentStartPos.x) - imguiStyle.FramePadding.x;
const float labelEndX = labelStartX + labelWidth;
const float labelHeight
= ImGui::CalcTextSize(objDetail.name.c_str(), objDetail.name.c_str() + objDetail.name.length(), false, labelWidth).y;
const float labelAlignOffsetY = (selectableSize.y - (2 * imguiStyle.FramePadding.y) - labelHeight) * 0.5f;
const float labelStartY = contentStartPos.y + imguiStyle.FramePadding.y + labelAlignOffsetY;
const float labelEndY = labelStartY + labelHeight;
const ImVec4 clipRect{
labelStartX,
labelStartY,
labelEndX,
labelEndY,
};
drawList->AddText(
ImGui::GetFont(), ImGui::GetFontSize(), ImVec2(clipRect.x, clipRect.y), ImGui::GetColorU32(ImGuiCol_Text),
objDetail.name.c_str(), objDetail.name.c_str() + objDetail.name.length(), labelWidth, &clipRect
);
}
if (bContentClicked)
{
retVal.emplace(std::make_pair(assetPack->rootObjectPath, entryIdx));
}
ImGui::PopID();
}
}
clipper.End();
return retVal;
}
bool GenericFieldCustomizer::drawWidgetClassObjectPath(SelectionFieldCustomizationArgs args) bool GenericFieldCustomizer::drawWidgetClassObjectPath(SelectionFieldCustomizationArgs args)
{ {
GenericFieldCustomizer *thisPtr = static_cast<GenericFieldCustomizer *>(args.customizer); GenericFieldCustomizer *thisPtr = static_cast<GenericFieldCustomizer *>(args.customizer);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -12,10 +12,13 @@
#pragma once #pragma once
#include <ClassDetailsCustomizer.hpp> #include <ClassDetailsCustomizer.hpp>
#include <bitset> #include <Widgets/WgEditorConstants.hpp>
#include <Memory/ArenaAllocator.h> #include <Memory/ArenaAllocator.h>
#include <StructDetailsCustomizer.hpp> #include <StructDetailsCustomizer.hpp>
#include <bitset>
#include <optional>
namespace cbe namespace cbe
{ {
class Transaction; class Transaction;
@@ -192,7 +195,7 @@ public:
std::bitset<ELEM_COUNT> multipleValues; std::bitset<ELEM_COUNT> multipleValues;
}; };
/* Max 3 letters */ /* Max 3 letters */
constexpr static const uint32 CLASS_INITIALS_COUNT = 3; constexpr static const uint32 CLASS_INITIALS_COUNT = wg_consts::CLASS_INITIALS_COUNT;
struct ObjectPackageDetail struct ObjectPackageDetail
{ {
std::string name; std::string name;
@@ -201,7 +204,7 @@ public:
/* Object pointer will be valid only if the package detail is either part of world or one of sub object of actor. */ /* Object pointer will be valid only if the package detail is either part of world or one of sub object of actor. */
WeakObjectPtr objPtr; WeakObjectPtr objPtr;
TChar classInitials[CLASS_INITIALS_COUNT + 1] = {}; AChar classInitials[CLASS_INITIALS_COUNT + 1] = {};
Color classColor; Color classColor;
}; };
/* Same context type for Raw pointer and ObjectPtr */ /* Same context type for Raw pointer and ObjectPtr */
@@ -408,6 +411,9 @@ public:
EEditState drawWidgetClassRawPointer(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs); EEditState drawWidgetClassRawPointer(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs);
EEditState drawWidgetClassObjPointer(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs); EEditState drawWidgetClassObjPointer(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs);
static EEditState drawWidgetClassPointerCntx(ClassObjectContext &cntx); static EEditState drawWidgetClassPointerCntx(ClassObjectContext &cntx);
/* When selected returns object path and index into objsList in context passed into */
static std::optional<std::pair<String, uint64>>
drawWidgetClassPointerCntxMenuList(ClassObjectContext &cntx, AssetManager &assetManager, float popupItemWidth, bool bFocusSelDetails);
EEditState drawWidgetEnum(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs); EEditState drawWidgetEnum(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs);
static EEditState drawWidgetEnum(EnumContext &cntx); static EEditState drawWidgetEnum(EnumContext &cntx);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date October 2025 * \date October 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -43,9 +43,12 @@ bool MaterialInstanceCustomizer::shouldCustomizeObjects(ArrayView<WeakObjectPtr>
struct LocalLayoutStack struct LocalLayoutStack
{ {
gal::GPUStructLayout dataLayout; gal::GPUStructLayout dataLayout;
uint32 fieldIdx; uint32 fieldIdx = 0;
/* Used by the inner struct array */ /* Used by the inner struct array */
uint32 arrayIdx; uint32 arrayIdx = 0;
/* Always points to start offset of this struct. Not field offset. Field offset must be added to this.
* Used only to patch material texture refs offset. */
uint32 offset = 0;
}; };
static void popLocalLayoutsStack(std::vector<LocalLayoutStack> &inOutLayoutsStack) static void popLocalLayoutsStack(std::vector<LocalLayoutStack> &inOutLayoutsStack)
{ {
@@ -69,6 +72,7 @@ static void popLocalLayoutsStack(std::vector<LocalLayoutStack> &inOutLayoutsStac
{ {
/* Restart the stack. */ /* Restart the stack. */
stackEntry.fieldIdx = 0; stackEntry.fieldIdx = 0;
stackEntry.offset += parentField.stride;
} }
else else
{ {
@@ -97,6 +101,7 @@ void MaterialInstanceCustomizer::patchMaterialInstTexturesList(MaterialInstance
const gal::GPUStructLayout &layout = layoutsStack.back().dataLayout; const gal::GPUStructLayout &layout = layoutsStack.back().dataLayout;
const uint32 fieldIdx = layoutsStack.back().fieldIdx++; const uint32 fieldIdx = layoutsStack.back().fieldIdx++;
const gal::GPUStructField &field = layout.fields[fieldIdx]; const gal::GPUStructField &field = layout.fields[fieldIdx];
const uint32 fieldAbsOffset = layoutsStack.back().offset + field.offset;
if (field.metaData.bTextureIdx) if (field.metaData.bTextureIdx)
{ {
@@ -110,6 +115,7 @@ void MaterialInstanceCustomizer::patchMaterialInstTexturesList(MaterialInstance
MaterialTexture2DParam{ MaterialTexture2DParam{
.paramName = field.name, .paramName = field.name,
.texture = nullptr, .texture = nullptr,
.layoutOffset = fieldAbsOffset + (field.stride * i),
} }
); );
} }
@@ -120,7 +126,11 @@ void MaterialInstanceCustomizer::patchMaterialInstTexturesList(MaterialInstance
{ {
auto layoutItr = allStructLayouts.find(field.metaData.structTypeName); auto layoutItr = allStructLayouts.find(field.metaData.structTypeName);
debugAssert(layoutItr != allStructLayouts.cend()); debugAssert(layoutItr != allStructLayouts.cend());
layoutsStack.emplace_back(layoutItr->second, 0); layoutsStack.emplace_back(LocalLayoutStack{
.dataLayout = layoutItr->second,
.fieldIdx = 0,
.offset = fieldAbsOffset,
});
} }
popLocalLayoutsStack(layoutsStack); popLocalLayoutsStack(layoutsStack);
@@ -711,6 +721,7 @@ void MaterialInstanceCustomizer::generateWidgetDrawEntries(SelectionClassCustomi
gal::GPUStructLayout layout = layoutsStack.back().dataLayout; gal::GPUStructLayout layout = layoutsStack.back().dataLayout;
const uint32 fieldIdx = layoutsStack.back().fieldIdx++; const uint32 fieldIdx = layoutsStack.back().fieldIdx++;
const gal::GPUStructField &field = layout.fields[fieldIdx]; const gal::GPUStructField &field = layout.fields[fieldIdx];
const uint32 fieldAbsOffset = layoutsStack.back().offset + field.offset;
if (field.metaData.bTextureIdx) if (field.metaData.bTextureIdx)
{ {
@@ -727,7 +738,11 @@ void MaterialInstanceCustomizer::generateWidgetDrawEntries(SelectionClassCustomi
{ {
auto layoutItr = allStructLayouts.find(field.metaData.structTypeName); auto layoutItr = allStructLayouts.find(field.metaData.structTypeName);
debugAssert(layoutItr != allStructLayouts.cend()); debugAssert(layoutItr != allStructLayouts.cend());
layoutsStack.emplace_back(layoutItr->second, 0, 0); layoutsStack.emplace_back(LocalLayoutStack{
.dataLayout = layoutItr->second,
.fieldIdx = 0,
.offset = fieldAbsOffset,
});
WidgetEntry &entry = outInfo.widgetDrawEntries.emplace_back(WidgetEntry{ WidgetEntry &entry = outInfo.widgetDrawEntries.emplace_back(WidgetEntry{
.customData = (0ull << 32ull) | fieldIdx, .customData = (0ull << 32ull) | fieldIdx,
@@ -781,6 +796,8 @@ void MaterialInstanceCustomizer::generateWidgetDrawEntries(SelectionClassCustomi
{ {
/* Insert next array entry begin and restart the stack. */ /* Insert next array entry begin and restart the stack. */
stackEntry.fieldIdx = 0; stackEntry.fieldIdx = 0;
stackEntry.offset += parentField.stride;
outInfo.widgetDrawEntries.emplace_back(WidgetEntry{ outInfo.widgetDrawEntries.emplace_back(WidgetEntry{
.customData = (static_cast<uint64>(stackEntry.arrayIdx) << 32ull) | parentFieldIdx, .customData = (static_cast<uint64>(stackEntry.arrayIdx) << 32ull) | parentFieldIdx,
.name = parentField.name, .name = parentField.name,
@@ -933,6 +950,7 @@ void MaterialInstanceCustomizer::fillCustomizationCntxFromMat(ClassCustomization
const gal::GPUStructLayout &layout = layoutsStack.back().dataLayout; const gal::GPUStructLayout &layout = layoutsStack.back().dataLayout;
const uint32 fieldIdx = layoutsStack.back().fieldIdx++; const uint32 fieldIdx = layoutsStack.back().fieldIdx++;
const gal::GPUStructField &field = layout.fields[fieldIdx]; const gal::GPUStructField &field = layout.fields[fieldIdx];
const uint32 fieldAbsOffset = layoutsStack.back().offset + field.offset;
if (field.metaData.bTextureIdx) if (field.metaData.bTextureIdx)
{ {
@@ -951,7 +969,11 @@ void MaterialInstanceCustomizer::fillCustomizationCntxFromMat(ClassCustomization
{ {
auto layoutItr = allStructLayouts.find(field.metaData.structTypeName); auto layoutItr = allStructLayouts.find(field.metaData.structTypeName);
debugAssert(layoutItr != allStructLayouts.cend()); debugAssert(layoutItr != allStructLayouts.cend());
layoutsStack.emplace_back(layoutItr->second, 0); layoutsStack.emplace_back(LocalLayoutStack{
.dataLayout = layoutItr->second,
.fieldIdx = 0,
.offset = fieldAbsOffset,
});
} }
popLocalLayoutsStack(layoutsStack); popLocalLayoutsStack(layoutsStack);
@@ -974,6 +996,7 @@ void MaterialInstanceCustomizer::setCustomizationCntxToMat(const ClassCustomizat
const gal::GPUStructLayout &layout = layoutsStack.back().dataLayout; const gal::GPUStructLayout &layout = layoutsStack.back().dataLayout;
const uint32 fieldIdx = layoutsStack.back().fieldIdx++; const uint32 fieldIdx = layoutsStack.back().fieldIdx++;
const gal::GPUStructField &field = layout.fields[fieldIdx]; const gal::GPUStructField &field = layout.fields[fieldIdx];
const uint32 fieldAbsOffset = layoutsStack.back().offset + field.offset;
if (field.metaData.bTextureIdx) if (field.metaData.bTextureIdx)
{ {
@@ -992,7 +1015,11 @@ void MaterialInstanceCustomizer::setCustomizationCntxToMat(const ClassCustomizat
{ {
auto layoutItr = allStructLayouts.find(field.metaData.structTypeName); auto layoutItr = allStructLayouts.find(field.metaData.structTypeName);
debugAssert(layoutItr != allStructLayouts.cend()); debugAssert(layoutItr != allStructLayouts.cend());
layoutsStack.emplace_back(layoutItr->second, 0); layoutsStack.emplace_back(LocalLayoutStack{
.dataLayout = layoutItr->second,
.fieldIdx = 0,
.offset = fieldAbsOffset,
});
} }
popLocalLayoutsStack(layoutsStack); popLocalLayoutsStack(layoutsStack);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -139,7 +139,7 @@ bool TransformComponentCustomizer::drawTransform3dWidget(SelectionClassCustomiza
} }
debugAssert(args.ownerEd != nullptr); debugAssert(args.ownerEd != nullptr);
args.ownerEd->pushMessage( args.ownerEd->pushMessageOverwrite(
cbe::MessageData{ cbe::MessageData{
.issuerData = std::bit_cast<uint64>(args.drawer), .issuerData = std::bit_cast<uint64>(args.drawer),
.msg = EmptyType{}, .msg = EmptyType{},

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,145 @@
/*!
* \file AssimpImporter.hpp
*
* \author Jeslas Pravin
* \date February 2026
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
#pragma once
#include <AssetImporter.hpp>
#include <Math/CoreMathTypes.h>
#include "AssimpImporter.gen.hpp"
struct AssimpImporterContext;
namespace Assimp
{
class Importer;
}
struct AssimpTextureOptions
{
GENERATED_CODES()
META_ANNOTATE()
bool bFlipY = false;
} META_ANNOTATE(NoExport);
struct AssimpImportOptions
{
GENERATED_CODES()
META_ANNOTATE()
bool bImportAsScene = false;
META_ANNOTATE()
float scale = 1.0f;
META_ANNOTATE()
bool bFromYUp = true;
/* If the mesh is already left handed */
META_ANNOTATE()
bool bIsLeftHanded = false;
META_ANNOTATE()
bool bFlipUvs = true;
META_ANNOTATE()
bool bFlipWindingOrder = true;
META_ANNOTATE()
bool bFixInwardsNormals = false;
META_ANNOTATE()
bool bSmoothNormals = false;
META_ANNOTATE()
float smoothingAngle = 35.0f;
META_ANNOTATE()
bool bRemoveDegenerates = false;
META_ANNOTATE()
bool bKeepAreaDegenerates = false;
META_ANNOTATE()
bool bMergeMeshes = false;
META_ANNOTATE()
bool bMergeMaterials = false;
/* Skips both texture and materials */
META_ANNOTATE()
bool bSkipMaterials = false;
/* Skips both texture and materials */
META_ANNOTATE()
bool bSkipSkeletons = false;
META_ANNOTATE()
bool bSkipNonMeshNodes = true;
META_ANNOTATE()
AssimpTextureOptions textureOptions;
} META_ANNOTATE(NoExport);
struct AssimpPerMeshOptions
{
GENERATED_CODES()
/* Use heuristics to get this name which will end up being the name of the mesh. */
META_ANNOTATE()
String meshName;
META_ANNOTATE()
bool bSkipImport;
} META_ANNOTATE(NoExport);
struct AssimpContextOptions
{
GENERATED_CODES()
META_ANNOTATE()
String sceneName;
META_ANNOTATE()
std::vector<AssimpPerMeshOptions> meshes;
} META_ANNOTATE(NoExport);
class AssimpImporter : public AssetImporterBase
{
public:
AssimpImporter();
MAKE_TYPE_NONCOPY_NONMOVE(AssimpImporter)
~AssimpImporter();
/* AssetImporterBase overrides */
public:
bool supportsImporting(ImportOption &inOutOptions) final;
ImportContext prepareContext(ImportOption &inOutOptions) final;
ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final;
protected:
void destructImportContext(void *cntx) final;
/* Override ends */
public:
AssimpImportOptions options;
LibHandle assimpLibHnd;
Assimp::Importer *assimpImporter;
/* For progress reporting */
uint32 ppStepsCount = 16;
private:
void processAiScene(AssimpImporterContext *cntx, ImportOption &inOutOptions) const;
};

View File

@@ -4,12 +4,12 @@
* \author Jeslas * \author Jeslas
* \date March 2025 * \date March 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
#include <AudioImporter.hpp> #include <Importers/AudioImporter.hpp>
#include <Classes/AudioSource.hpp> #include <Classes/AudioSource.hpp>
#include <ObjectPathHelpers.h> #include <ObjectPathHelpers.h>
#include <EditorHelpers.h> #include <EditorHelpers.h>
@@ -31,6 +31,8 @@ bool AudioModuleDecodedImporter::supportsImporting(ImportOption &inOutOptions)
inOutOptions.optionsStruct = &options; inOutOptions.optionsStruct = &options;
inOutOptions.optionsStructType = AudioImportOptions::staticType(); inOutOptions.optionsStructType = AudioImportOptions::staticType();
inOutOptions.prepareProgressCount = 3;
return true; return true;
} }
return false; return false;
@@ -54,6 +56,10 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
audio_module_decoded::ImportContext *cntx = new audio_module_decoded::ImportContext(); audio_module_decoded::ImportContext *cntx = new audio_module_decoded::ImportContext();
AssetImporterBase::ImportContext ret{ cntx, ImportContextDtor{ this } }; AssetImporterBase::ImportContext ret{ cntx, ImportContextDtor{ this } };
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Read file"));
}
std::vector<uint8> data; std::vector<uint8> data;
if (!FileHelper::readBytes(data, inOutOptions.filePath)) if (!FileHelper::readBytes(data, inOutOptions.filePath))
{ {
@@ -80,6 +86,11 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
encoding = cbe::EAudioSrcEncoding::Vorbis; encoding = cbe::EAudioSrcEncoding::Vorbis;
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Decode audio"));
}
ICbeAudioModule *audioModule = ICbeAudioModule::get(); ICbeAudioModule *audioModule = ICbeAudioModule::get();
const cbe::audio::DecodeInfo decodeInfo{ .sourceData = data, .encoding = encoding }; const cbe::audio::DecodeInfo decodeInfo{ .sourceData = data, .encoding = encoding };
cbe::audio::DecodedOutput decodedInfo; cbe::audio::DecodedOutput decodedInfo;
@@ -107,6 +118,11 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
return ret; return ret;
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Setup Context"));
}
cntx->data = std::move(decodedInfo.pcmFrames.empty() ? data : decodedInfo.pcmFrames); cntx->data = std::move(decodedInfo.pcmFrames.empty() ? data : decodedInfo.pcmFrames);
cntx->createInfo = { cntx->createInfo = {
.encodedData = cntx->data, .encodedData = cntx->data,
@@ -123,6 +139,7 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
}; };
inOutOptions.cntxOptionsStruct = &cntx->cntxOptions; inOutOptions.cntxOptionsStruct = &cntx->cntxOptions;
inOutOptions.cntxOptionsStructType = audio_module_decoded::ContextOptions::staticType(); inOutOptions.cntxOptionsStructType = audio_module_decoded::ContextOptions::staticType();
inOutOptions.importProgressCount = 1;
return ret; return ret;
} }
@@ -137,6 +154,11 @@ AssetImporterBase::ImportResult AudioModuleDecodedImporter::importAssets(const I
return err; return err;
} }
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR("Importing"));
}
/* Now create the package and the asset */ /* Now create the package and the asset */
String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR; String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR;
packageRelPath.append(importOptions.engName); packageRelPath.append(importOptions.engName);
@@ -151,58 +173,3 @@ AssetImporterBase::ImportResult AudioModuleDecodedImporter::importAssets(const I
} }
void AudioModuleDecodedImporter::destructImportContext(void *cntx) { delete std::bit_cast<audio_module_decoded::ImportContext *>(cntx); } void AudioModuleDecodedImporter::destructImportContext(void *cntx) { delete std::bit_cast<audio_module_decoded::ImportContext *>(cntx); }
std::optional<std::vector<cbe::Object *>> AudioModuleDecodedImporter::tryImporting(const ImportOption &importOptions) const
{
std::vector<uint8> data;
if (FileHelper::readBytes(data, importOptions.filePath))
{
const String extension = importOptions.fileExt;
cbe::EAudioSrcEncoding encoding = cbe::EAudioSrcEncoding::Unknown;
if (extension.isEqual(TCHAR("WAV"), false))
{
encoding = cbe::EAudioSrcEncoding::Wav;
}
else if (extension.isEqual(TCHAR("MP3"), false))
{
encoding = cbe::EAudioSrcEncoding::Mp3;
}
else if (extension.isEqual(TCHAR("FLAC"), false))
{
encoding = cbe::EAudioSrcEncoding::Flac;
}
else if (extension.isEqual(TCHAR("OGG"), false))
{
encoding = cbe::EAudioSrcEncoding::Vorbis;
}
ICbeAudioModule *audioModule = ICbeAudioModule::get();
const cbe::audio::DecodeInfo decodeInfo{ .sourceData = data, .encoding = encoding };
const cbe::audio::DecodedOutput decodedInfo = audioModule->decodeAudioInfo(decodeInfo);
if (decodedInfo.dataFormat == cbe::audio::ESampleDataFormat::Invalid)
{
return {};
}
cbe::AudioSourceCreateInfo createInfo{
.encodedData = std::move(data),
.framesCount = decodedInfo.framesCount,
.sampleRate = decodedInfo.sampleRate,
.dataFormat = decodedInfo.dataFormat,
.encoding = encoding,
.numOfChannels = decodedInfo.numChannels,
};
/* Now create the package and the asset */
String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR;
packageRelPath.append(importOptions.engName);
EditorHelpers::makePackageUnique(packageRelPath);
std::vector<cbe::Object *> audioSrcs{
EditorHelpers::createAudioSource(packageRelPath, importOptions.importContentPath, importOptions.engName, std::move(createInfo))
};
return audioSrcs;
}
return {};
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date March 2025 * \date March 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -58,8 +58,6 @@ public:
ImportContext prepareContext(ImportOption &inOutOptions) final; ImportContext prepareContext(ImportOption &inOutOptions) final;
ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final; ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final;
std::optional<std::vector<cbe::Object *>> tryImporting(const ImportOption &importOptions) const override;
protected: protected:
void destructImportContext(void *cntx) final; void destructImportContext(void *cntx) final;
/* Override ends */ /* Override ends */

View File

@@ -4,12 +4,12 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
#include <StaticMeshImporter.hpp> #include <Importers/StaticMeshImporter.hpp>
#include <Types/Platform/LFS/PathFunctions.h> #include <Types/Platform/LFS/PathFunctions.h>
#include <CBEPackage.hpp> #include <CBEPackage.hpp>
#include <CBEObjectHelpers.h> #include <CBEObjectHelpers.h>
@@ -62,16 +62,16 @@ static void printErrors(uint32 errorCount, EImportErrorCodes errorCode)
switch (errorCode) switch (errorCode)
{ {
case DegenerateTextureCoords: case DegenerateTextureCoords:
LOG_WARN("ObjStaticMeshImporter", "Incorrect texture coordinate, using world x, y as tangents[{}]", errorCount); CBE_LOG_WARN("ObjStaticMeshImporter", "Incorrect texture coordinate, using world x, y as tangents[{}]", errorCount);
break; break;
case DegenerateNormal: case DegenerateNormal:
LOG_WARN("ObjStaticMeshImporter", "Degenerate normals, Tangents might be invalid. Expect visual artifacts[{}]", errorCount); CBE_LOG_WARN("ObjStaticMeshImporter", "Degenerate normals, Tangents might be invalid. Expect visual artifacts[{}]", errorCount);
break; break;
case DegenerateTriangle: case DegenerateTriangle:
LOG_WARN("ObjStaticMeshImporter", "Degenerate triangles found and they are removed[{}]", errorCount); CBE_LOG_WARN("ObjStaticMeshImporter", "Degenerate triangles found and they are removed[{}]", errorCount);
break; break;
case InvalidFace: case InvalidFace:
LOG_WARN("ObjStaticMeshImporter", "Invalid face/index data found and they are removed[{}]", errorCount); CBE_LOG_WARN("ObjStaticMeshImporter", "Invalid face/index data found and they are removed[{}]", errorCount);
break; break;
case ErrorsCount: case ErrorsCount:
default: default:
@@ -203,11 +203,13 @@ static void calcTangent(
static void transformVertices(tinyobj::attrib_t &attrib, const StaticMeshImportOptions &options) static void transformVertices(tinyobj::attrib_t &attrib, const StaticMeshImportOptions &options)
{ {
const uint64 vertEndIdx = attrib.vertices.size() / 3;
const uint64 normEndIdx = attrib.normals.size() / 3;
if (options.bFromYUp) if (options.bFromYUp)
{ {
/* Rotating 90.0 is equivalent to doing v.y = -v.z and v.z = v.y followed by negating to flip along Y Axis to left hand space. */
static const Quat ROTATION_Y2Z_UP = Quat(90.f, Vector3::FWD); static const Quat ROTATION_Y2Z_UP = Quat(90.f, Vector3::FWD);
const uint64 vertEndIdx = attrib.vertices.size() / 3;
for (uint64 vertIdx = 0; vertIdx != vertEndIdx; ++vertIdx) for (uint64 vertIdx = 0; vertIdx != vertEndIdx; ++vertIdx)
{ {
const uint64 vertXIdx = vertIdx * 3; const uint64 vertXIdx = vertIdx * 3;
@@ -218,7 +220,6 @@ static void transformVertices(tinyobj::attrib_t &attrib, const StaticMeshImportO
attrib.vertices[vertXIdx + 2] = v.z; attrib.vertices[vertXIdx + 2] = v.z;
} }
const uint64 normEndIdx = attrib.normals.size() / 3;
for (uint64 normIdx = 0; normIdx != normEndIdx; ++normIdx) for (uint64 normIdx = 0; normIdx != normEndIdx; ++normIdx)
{ {
const uint64 normXIdx = normIdx * 3; const uint64 normXIdx = normIdx * 3;
@@ -230,6 +231,21 @@ static void transformVertices(tinyobj::attrib_t &attrib, const StaticMeshImportO
} }
} }
if (!options.bIsLeftHanded)
{
/* Left Handedness: Flip along Y */
for (uint64 vertIdx = 0; vertIdx != vertEndIdx; ++vertIdx)
{
const uint64 vertXIdx = vertIdx * 3;
attrib.vertices[vertXIdx + 1] *= -1.f;
}
for (uint64 normIdx = 0; normIdx != normEndIdx; ++normIdx)
{
const uint64 normXIdx = normIdx * 3;
attrib.normals[normXIdx + 1] *= -1.f;
}
}
if (!Math::isEqual(options.scale, 1.0f)) if (!Math::isEqual(options.scale, 1.0f))
{ {
for (float &elem : attrib.vertices) for (float &elem : attrib.vertices)
@@ -238,6 +254,14 @@ static void transformVertices(tinyobj::attrib_t &attrib, const StaticMeshImportO
} }
} }
} }
static void transformIndices(ArrayRange<tinyobj::index_t> idxs, const StaticMeshImportOptions &options)
{
if (!options.bIsLeftHanded)
{
/* Left Handedness: Reverse the winding */
std::reverse(idxs.begin(), idxs.end());
}
}
static void fillVertexInfo(ImporterSMVertex &vertexData, const tinyobj::attrib_t &attrib, const tinyobj::index_t &index) static void fillVertexInfo(ImporterSMVertex &vertexData, const tinyobj::attrib_t &attrib, const tinyobj::index_t &index)
{ {
@@ -245,13 +269,15 @@ static void fillVertexInfo(ImporterSMVertex &vertexData, const tinyobj::attrib_t
Vector3 normal = {}; Vector3 normal = {};
if (index.texcoord_index != -1) if (index.texcoord_index != -1)
{ {
// Inverting Y since UV origin is at left bottom of image and Graphics API's UV origin is at left top uvCoord = { attrib.texcoords[(index.texcoord_index * 2u) + 0u], attrib.texcoords[(index.texcoord_index * 2u) + 1u] };
uvCoord = { attrib.texcoords[(index.texcoord_index * 2u) + 0u], (1.0f - attrib.texcoords[(index.texcoord_index * 2u) + 1u]) };
} }
if (index.normal_index != -1) if (index.normal_index != -1)
{ {
normal = { attrib.normals[(index.normal_index * 3u) + 0u], attrib.normals[(index.normal_index * 3u) + 1u], normal = {
attrib.normals[(index.normal_index * 3u) + 2u] }; attrib.normals[(index.normal_index * 3u) + 0u],
attrib.normals[(index.normal_index * 3u) + 1u],
attrib.normals[(index.normal_index * 3u) + 2u],
};
} }
uvCoord = Math::clamp(uvCoord, Vector2::ZERO, Vector2::ONE); uvCoord = Math::clamp(uvCoord, Vector2::ZERO, Vector2::ONE);
@@ -355,9 +381,12 @@ static void load(
{ {
debugAssert(FACE_MAX_VERTS == 3 && FACE_MAX_VERTS == mesh.mesh.num_face_vertices[faceIdx]); debugAssert(FACE_MAX_VERTS == 3 && FACE_MAX_VERTS == mesh.mesh.num_face_vertices[faceIdx]);
const std::array<tinyobj::index_t, FACE_MAX_VERTS> idxs std::array<tinyobj::index_t, FACE_MAX_VERTS> idxs = {
= { mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 0u], mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 1u], mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 0u],
mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 2u] }; mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 1u],
mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 2u],
};
transformIndices(idxs, outImportData.options);
if (idxs[0].vertex_index == -1 || idxs[1].vertex_index == -1 || idxs[2].vertex_index == -1) if (idxs[0].vertex_index == -1 || idxs[1].vertex_index == -1 || idxs[2].vertex_index == -1)
{ {
outImportData.errorsCounter[EImportErrorCodes::InvalidFace]++; outImportData.errorsCounter[EImportErrorCodes::InvalidFace]++;
@@ -481,9 +510,12 @@ static void smoothAndLoad(
{ {
debugAssert(FACE_MAX_VERTS == 3 && FACE_MAX_VERTS == mesh.mesh.num_face_vertices[faceIdx]); debugAssert(FACE_MAX_VERTS == 3 && FACE_MAX_VERTS == mesh.mesh.num_face_vertices[faceIdx]);
const std::array<tinyobj::index_t, FACE_MAX_VERTS> idxs std::array<tinyobj::index_t, FACE_MAX_VERTS> idxs = {
= { mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 0u], mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 1u], mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 0u],
mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 2u] }; mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 1u],
mesh.mesh.indices[(faceIdx * FACE_MAX_VERTS) + 2u],
};
transformIndices(idxs, outImportData.options);
if (idxs[0].vertex_index == -1 || idxs[1].vertex_index == -1 || idxs[2].vertex_index == -1) if (idxs[0].vertex_index == -1 || idxs[1].vertex_index == -1 || idxs[2].vertex_index == -1)
{ {
outImportData.errorsCounter[EImportErrorCodes::InvalidFace]++; outImportData.errorsCounter[EImportErrorCodes::InvalidFace]++;
@@ -759,6 +791,7 @@ bool ObjStaticMeshImporter::supportsImporting(ImportOption &inOutOptions)
{ {
inOutOptions.optionsStruct = &options; inOutOptions.optionsStruct = &options;
inOutOptions.optionsStructType = StaticMeshImportOptions::staticType(); inOutOptions.optionsStructType = StaticMeshImportOptions::staticType();
inOutOptions.prepareProgressCount = 6;
return true; return true;
} }
@@ -770,6 +803,11 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
obj_sm::ImportContext *cntx = new obj_sm::ImportContext(); obj_sm::ImportContext *cntx = new obj_sm::ImportContext();
ImportContext ret{ cntx, ImportContextDtor{ this } }; ImportContext ret{ cntx, ImportContextDtor{ this } };
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Load Obj"));
}
tinyobj::attrib_t attrib; tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> meshes; std::vector<tinyobj::shape_t> meshes;
std::vector<tinyobj::material_t> materials; std::vector<tinyobj::material_t> materials;
@@ -782,7 +820,7 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
); );
if (!warning.empty()) if (!warning.empty())
{ {
LOG_WARN("ObjStaticMeshImporter", "Tiny obj loader {}", UTF8_TO_TCHAR(warning)); CBE_LOG_WARN("ObjStaticMeshImporter", "Tiny obj loader {}", UTF8_TO_TCHAR(warning));
} }
if (!error.empty()) if (!error.empty())
{ {
@@ -801,8 +839,16 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
return ret; return ret;
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Transform vertices"));
}
obj_sm::transformVertices(attrib, options); obj_sm::transformVertices(attrib, options);
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Load to intermediate"));
}
obj_sm::IntermediateImportData meshIntermediate; obj_sm::IntermediateImportData meshIntermediate;
meshIntermediate.options = options; meshIntermediate.options = options;
CBEMemory::memZero(&meshIntermediate.errorsCounter, sizeof(meshIntermediate.errorsCounter)); CBEMemory::memZero(&meshIntermediate.errorsCounter, sizeof(meshIntermediate.errorsCounter));
@@ -810,12 +856,13 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
{ {
const bool hasSmoothing = obj_sm::hasSmoothedNormals(mesh); const bool hasSmoothing = obj_sm::hasSmoothedNormals(mesh);
if (options.bLoadSmoothed && !hasSmoothing) if (options.bSmoothNormals && !hasSmoothing)
{ {
obj_sm::smoothAndLoad(meshIntermediate, mesh, attrib, materials); obj_sm::smoothAndLoad(meshIntermediate, mesh, attrib, materials);
} }
else else
{ {
obj_sm::load(meshIntermediate, mesh, attrib, materials); obj_sm::load(meshIntermediate, mesh, attrib, materials);
} }
@@ -824,6 +871,11 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
break; break;
} }
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Normalize vertices"));
}
// Normalizing all the vertex normals // Normalizing all the vertex normals
for (ImporterSMVertex &vertex : meshIntermediate.vertices) for (ImporterSMVertex &vertex : meshIntermediate.vertices)
{ {
@@ -841,7 +893,7 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
} }
if (bHadAnyErrors) if (bHadAnyErrors)
{ {
LOG_WARN("ObjStaticMeshImporter", "Errors when loading mesh {}", inOutOptions.filePath); CBE_LOG_WARN("ObjStaticMeshImporter", "Errors when loading mesh {}", inOutOptions.filePath);
for (uint32 i = 0; i != obj_sm::ErrorsCount; ++i) for (uint32 i = 0; i != obj_sm::ErrorsCount; ++i)
{ {
if (meshIntermediate.errorsCounter[i] > 0) if (meshIntermediate.errorsCounter[i] > 0)
@@ -851,6 +903,10 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
} }
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Gather CreateInfos"));
}
/* Split each mesh and it's vertices */ /* Split each mesh and it's vertices */
std::unordered_map<String, obj_sm::CreateData> createInfoSMs; std::unordered_map<String, obj_sm::CreateData> createInfoSMs;
createInfoSMs.reserve(meshIntermediate.loadedMeshes.size()); createInfoSMs.reserve(meshIntermediate.loadedMeshes.size());
@@ -906,6 +962,10 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
} }
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Setup Context"));
}
/* Fill the context information and context options */ /* Fill the context information and context options */
cntx->sms.reserve(createInfoSMs.size()); cntx->sms.reserve(createInfoSMs.size());
cntx->contextOpts.importingMeshes.reserve(createInfoSMs.size()); cntx->contextOpts.importingMeshes.reserve(createInfoSMs.size());
@@ -919,6 +979,9 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
} }
inOutOptions.cntxOptionsStruct = &cntx->contextOpts; inOutOptions.cntxOptionsStruct = &cntx->contextOpts;
inOutOptions.cntxOptionsStructType = obj_sm::ContextOptions::staticType(); inOutOptions.cntxOptionsStructType = obj_sm::ContextOptions::staticType();
inOutOptions.importProgressCount = static_cast<uint32>(createInfoSMs.size());
/* +1 scene creation */
inOutOptions.importProgressCount += 1;
return ret; return ret;
} }
@@ -958,6 +1021,11 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
const String &meshName = cntxPtr->contextOpts.importingMeshes[i].meshName; const String &meshName = cntxPtr->contextOpts.importingMeshes[i].meshName;
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, STR_FORMAT("Create mesh {}", meshName));
}
String packageName = packageRelDir + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + meshName; String packageName = packageRelDir + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + meshName;
importedObjs.emplace_back(createStaticMesh(packageName, meshName, std::move(cntxPtr->sms[i].smCi))); importedObjs.emplace_back(createStaticMesh(packageName, meshName, std::move(cntxPtr->sms[i].smCi)));
Transform3D actorTf; Transform3D actorTf;
@@ -976,6 +1044,11 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
continue; continue;
} }
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, STR_FORMAT("Create mesh {}", i));
}
importedObjs.emplace_back(createStaticMesh(packageName, fileName, std::move(cntxPtr->sms[i].smCi))); importedObjs.emplace_back(createStaticMesh(packageName, fileName, std::move(cntxPtr->sms[i].smCi)));
break; break;
} }
@@ -983,6 +1056,11 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
if (options.bImportAsScene && !importedObjs.empty()) if (options.bImportAsScene && !importedObjs.empty())
{ {
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR("Create world"));
}
const String packageName = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + fileName; const String packageName = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + fileName;
cbe::Package *worldPackage = cbe::Package::createPackage(packageName, importOptions.importContentPath, false); cbe::Package *worldPackage = cbe::Package::createPackage(packageName, importOptions.importContentPath, false);
debugAssert(worldPackage); debugAssert(worldPackage);
@@ -999,200 +1077,15 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
importedObjs.insert(importedObjs.begin(), world); importedObjs.insert(importedObjs.begin(), world);
} }
else
{
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR(""));
}
}
return ImportSuccess{ .objs = std::move(importedObjs) }; return ImportSuccess{ .objs = std::move(importedObjs) };
} }
void ObjStaticMeshImporter::destructImportContext(void *cntx) { delete std::bit_cast<obj_sm::ImportContext *>(cntx); } void ObjStaticMeshImporter::destructImportContext(void *cntx) { delete std::bit_cast<obj_sm::ImportContext *>(cntx); }
std::optional<std::vector<cbe::Object *>> ObjStaticMeshImporter::tryImporting(const ImportOption &importOptions) const
{
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> meshes;
std::vector<tinyobj::material_t> materials;
std::string warning;
std::string error;
const String fileDir = importOptions.fileDirectory;
const bool bIsSuccessful = tinyobj::LoadObj(
&attrib, &meshes, &materials, &warning, &error, TCHAR_TO_ANSI(importOptions.filePath).data(), TCHAR_TO_ANSI(fileDir).data()
);
if (!warning.empty())
{
LOG_WARN("ObjStaticMeshImporter", "Tiny obj loader {}", UTF8_TO_TCHAR(warning));
}
if (!error.empty())
{
LOG_ERROR("ObjStaticMeshImporter", "Tiny obj loader {}", UTF8_TO_TCHAR(error));
return {};
}
if (!bIsSuccessful)
{
LOG_ERROR("ObjStaticMeshImporter", "Loading {} with ObjStaticMeshImporter failed!", importOptions.filePath);
return {};
}
if (meshes.empty())
{
LOG_WARN("ObjStaticMeshImporter", "No mesh found while loading {} with ObjStaticMeshImporter!", importOptions.filePath);
return {};
}
obj_sm::transformVertices(attrib, options);
obj_sm::IntermediateImportData meshIntermediate;
meshIntermediate.options = options;
CBEMemory::memZero(&meshIntermediate.errorsCounter, sizeof(meshIntermediate.errorsCounter));
for (const tinyobj::shape_t &mesh : meshes)
{
const bool hasSmoothing = obj_sm::hasSmoothedNormals(mesh);
if (options.bLoadSmoothed && !hasSmoothing)
{
obj_sm::smoothAndLoad(meshIntermediate, mesh, attrib, materials);
}
else
{
obj_sm::load(meshIntermediate, mesh, attrib, materials);
}
if (!(options.bImportAllMesh || options.bImportAsScene))
{
break;
}
}
// Normalizing all the vertex normals
for (ImporterSMVertex &vertex : meshIntermediate.vertices)
{
vertex.normal = vertex.normal.normalized();
}
// Print errors
bool bHadAnyErrors = false;
for (uint32 i = 0; i != obj_sm::ErrorsCount; ++i)
{
if (meshIntermediate.errorsCounter[i] > 0)
{
bHadAnyErrors = true;
break;
}
}
if (bHadAnyErrors)
{
LOG_WARN("ObjStaticMeshImporter", "Errors when loading mesh {}", importOptions.filePath);
for (uint32 i = 0; i != obj_sm::ErrorsCount; ++i)
{
if (meshIntermediate.errorsCounter[i] > 0)
{
obj_sm::printErrors(meshIntermediate.errorsCounter[i], obj_sm::EImportErrorCodes(i));
}
}
}
/* Split each mesh and it's vertices */
std::unordered_map<String, obj_sm::CreateData> createInfoSMs;
createInfoSMs.reserve(meshIntermediate.loadedMeshes.size());
for (std::pair<const String, obj_sm::PerMeshData> &meshIntermData : meshIntermediate.loadedMeshes)
{
obj_sm::CreateData &createData = createInfoSMs[PropertyHelper::getValidSymbolName(meshIntermData.first)];
cbe::SMCreateInfo &createInfo = createData.smCi;
/* Offset the origin of each mesh to match the bounds center. */
createData.shiftedOrigin = meshIntermData.second.bound.center();
/* Shift all vertices and bounds */
if (createData.shiftedOrigin.sqrSize() > 0.0f)
{
meshIntermData.second.bound.maxBound -= createData.shiftedOrigin;
meshIntermData.second.bound.minBound -= createData.shiftedOrigin;
for (uint32 vertIdx = meshIntermData.second.verticesRange.minBound; vertIdx < meshIntermData.second.verticesRange.maxBound;
++vertIdx)
{
meshIntermediate.vertices[vertIdx].position -= createData.shiftedOrigin;
}
}
createInfo.meshBatches = std::move(meshIntermData.second.meshBatches);
createInfo.bounds = meshIntermData.second.bound;
createInfo.tbnVerts = std::move(meshIntermData.second.tbnVerts);
/* Assign indices and vertices per mesh after converting intermediate vertices in to per mesh vertices */
createInfo.indices.reserve(meshIntermData.second.indices.size());
std::unordered_map<uint32, uint32> vertIntermIdxToMeshIdx;
for (const uint32 vertIdx : meshIntermData.second.indices)
{
auto meshVertIdxItr = vertIntermIdxToMeshIdx.find(vertIdx);
if (meshVertIdxItr == vertIntermIdxToMeshIdx.cend())
{
const uint32 index = static_cast<uint32>(createInfo.vertsS0.size());
vertIntermIdxToMeshIdx[vertIdx] = index;
createInfo.indices.emplace_back(index);
GalStaticMeshStream0 vert0;
GalStaticMeshStream1 vert1;
std::get<"position">(vert0) = meshIntermediate.vertices[vertIdx].position;
std::get<"normal">(vert1) = meshIntermediate.vertices[vertIdx].normal;
std::get<"tangent">(vert1) = meshIntermediate.vertices[vertIdx].tangent;
std::get<"texCoord">(vert1) = meshIntermediate.vertices[vertIdx].texCoord;
createInfo.vertsS0.emplace_back(vert0);
createInfo.vertsS1.emplace_back(vert1);
}
else
{
createInfo.indices.emplace_back(meshVertIdxItr->second);
}
}
}
std::vector<cbe::Object *> importedObjs;
/* Will be used only if importing as scene. */
std::vector<Transform3D> importedActorTfs;
auto createStaticMesh = [&importOptions](String &packageName, const String &meshName, cbe::SMCreateInfo &&createInfo) -> cbe::StaticMesh *
{
EditorHelpers::makePackageUnique(packageName);
return EditorHelpers::createStaticMesh(packageName, importOptions.importContentPath, meshName, std::move(createInfo));
};
const String fileName = importOptions.engName;
if (options.bImportAsScene || options.bImportAllMesh)
{
importedObjs.reserve(createInfoSMs.size());
importedActorTfs.reserve(createInfoSMs.size());
const String packageRelDir = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + fileName;
for (std::pair<const String, obj_sm::CreateData> &smCI : createInfoSMs)
{
String packageName = packageRelDir + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + smCI.first;
importedObjs.emplace_back(createStaticMesh(packageName, smCI.first, std::move(smCI.second.smCi)));
Transform3D actorTf;
actorTf.setTranslation(smCI.second.shiftedOrigin);
importedActorTfs.emplace_back(actorTf);
}
}
else
{
debugAssert(createInfoSMs.size() == 1);
String packageName = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + fileName;
importedObjs.emplace_back(createStaticMesh(packageName, fileName, std::move(createInfoSMs.begin()->second.smCi)));
}
if (options.bImportAsScene && !importedObjs.empty())
{
const String packageName = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR + fileName;
cbe::Package *worldPackage = cbe::Package::createPackage(packageName, importOptions.importContentPath, false);
debugAssert(worldPackage);
cbe::markPackageDirty(worldPackage);
cbe::INTERNAL_ObjectCoreAccessors::addFlags(worldPackage, cbe::EObjectFlagBits::ObjFlag_PackageLoaded);
std::vector<cbe::StaticMesh *> staticMeshes;
staticMeshes.resize(importedObjs.size());
CBEMemory::memCopy(staticMeshes.data(), importedObjs.data(), sizeof(cbe::StaticMesh *) * importedObjs.size());
cbe::World *world = cbe::create<cbe::World>(fileName, worldPackage, cbe::EObjectFlagBits::ObjFlag_PackageLoaded);
cbe::Actor *rootActor = EditorHelpers::addStaticMeshesToWorld(staticMeshes, importedActorTfs, world, fileName + TCHAR("Root"));
debugAssert(world && rootActor);
importedObjs.insert(importedObjs.begin(), world);
}
return importedObjs;
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -35,7 +35,11 @@ struct StaticMeshImportOptions
bool bImportAllMesh = false; bool bImportAllMesh = false;
META_ANNOTATE() META_ANNOTATE()
bool bLoadSmoothed = false; bool bSmoothNormals = false;
/* If the mesh is already left handed */
META_ANNOTATE()
bool bIsLeftHanded = false;
META_ANNOTATE() META_ANNOTATE()
float smoothingAngle = 35.0f; float smoothingAngle = 35.0f;
@@ -83,8 +87,6 @@ public:
ImportContext prepareContext(ImportOption &inOutOptions) final; ImportContext prepareContext(ImportOption &inOutOptions) final;
ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final; ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final;
std::optional<std::vector<cbe::Object *>> tryImporting(const ImportOption &importOptions) const override;
protected: protected:
void destructImportContext(void *cntx) final; void destructImportContext(void *cntx) final;
/* Override ends */ /* Override ends */

View File

@@ -4,12 +4,12 @@
* \author Jeslas * \author Jeslas
* \date June 2025 * \date June 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
#include <Texture2DImporter.hpp> #include <Importers/Texture2DImporter.hpp>
#include <Types/Platform/LFS/File/FileHelper.h> #include <Types/Platform/LFS/File/FileHelper.h>
#include <Logger/Logger.h> #include <Logger/Logger.h>
#include <ThirdParties/StbWrapper.hpp> #include <ThirdParties/StbWrapper.hpp>
@@ -83,7 +83,7 @@ static bool isNormalTexture(const uint8 *texels, UShort2 textureDimension, uint8
if (Math::abs(rgMaxLum - 127.5f) < 17.5f && blueMaxLum > 200) if (Math::abs(rgMaxLum - 127.5f) < 17.5f && blueMaxLum > 200)
{ {
isNormal = true; isNormal = true;
LOG_VERBOSE( CBE_LOG_VERBOSE(
"Texture2DImporter", "Texture2DImporter",
"Texture {} with Max Red Green lum {} Max RG weight {:0.3}, Max Blue lum {} Max B weight {:0.3} is determined as normal texture", "Texture {} with Max Red Green lum {} Max RG weight {:0.3}, Max Blue lum {} Max B weight {:0.3} is determined as normal texture",
fileName, rgMaxLum, rgMaxWeight, blueMaxLum, blueMaxWeight fileName, rgMaxLum, rgMaxWeight, blueMaxLum, blueMaxWeight
@@ -93,7 +93,7 @@ static bool isNormalTexture(const uint8 *texels, UShort2 textureDimension, uint8
if (!isNormal && fileName.ends_with(TCHAR("_N"))) if (!isNormal && fileName.ends_with(TCHAR("_N")))
{ {
isNormal = true; isNormal = true;
LOG_DEBUG( CBE_LOG_DEBUG(
"Texture2DImporter", "Texture {} is determined as normal texture based on suffix _N, Please rename texture if not intended", "Texture2DImporter", "Texture {} is determined as normal texture based on suffix _N, Please rename texture if not intended",
fileName fileName
); );
@@ -111,6 +111,7 @@ bool StbTexture2DImporter::supportsImporting(ImportOption &inOutOptions)
{ {
inOutOptions.optionsStruct = &options; inOutOptions.optionsStruct = &options;
inOutOptions.optionsStructType = Texture2DImportOptions::staticType(); inOutOptions.optionsStructType = Texture2DImportOptions::staticType();
inOutOptions.prepareProgressCount = 3;
return true; return true;
} }
@@ -139,6 +140,10 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
stb_texture2d::ImportContext *cntx = new stb_texture2d::ImportContext(); stb_texture2d::ImportContext *cntx = new stb_texture2d::ImportContext();
AssetImporterBase::ImportContext ret{ cntx, ImportContextDtor{ this } }; AssetImporterBase::ImportContext ret{ cntx, ImportContextDtor{ this } };
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Read file"));
}
std::vector<uint8> data; std::vector<uint8> data;
if (!FileHelper::readBytes(data, inOutOptions.filePath)) if (!FileHelper::readBytes(data, inOutOptions.filePath))
{ {
@@ -146,6 +151,11 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
return ret; return ret;
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Import image"));
}
int32 channelsCount = 0; int32 channelsCount = 0;
int32 dimX = 0; int32 dimX = 0;
int32 dimY = 0; int32 dimY = 0;
@@ -163,6 +173,11 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
return ret; return ret;
} }
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Setup Context"));
}
cntx->channelsCount = channelsCount; cntx->channelsCount = channelsCount;
cntx->dim = { static_cast<uint16>(dimX), static_cast<uint16>(dimY) }; cntx->dim = { static_cast<uint16>(dimX), static_cast<uint16>(dimY) };
cntx->data = texelData; cntx->data = texelData;
@@ -172,6 +187,7 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
inOutOptions.cntxOptionsStruct = &cntx->contextOpts; inOutOptions.cntxOptionsStruct = &cntx->contextOpts;
inOutOptions.cntxOptionsStructType = stb_texture2d::ContextOptions::staticType(); inOutOptions.cntxOptionsStructType = stb_texture2d::ContextOptions::staticType();
inOutOptions.importProgressCount = 2;
return ret; return ret;
} }
@@ -186,6 +202,11 @@ AssetImporterBase::ImportResult StbTexture2DImporter::importAssets(const ImportO
return err; return err;
} }
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR("Copy data"));
}
cbe::Texture2DCreateInfo createInfo; cbe::Texture2DCreateInfo createInfo;
createInfo.dim = Math::clamp(Short2(cntxPtr->contextOpts.dimensionX, cntxPtr->contextOpts.dimensionY), Short2(1), cntxPtr->dim); createInfo.dim = Math::clamp(Short2(cntxPtr->contextOpts.dimensionX, cntxPtr->contextOpts.dimensionY), Short2(1), cntxPtr->dim);
createInfo.numOfChannels = stb_texture2d::CHANNEL_NUM; createInfo.numOfChannels = stb_texture2d::CHANNEL_NUM;
@@ -212,6 +233,11 @@ AssetImporterBase::ImportResult StbTexture2DImporter::importAssets(const ImportO
stb::deallocStbBuffer(cntxPtr->data); stb::deallocStbBuffer(cntxPtr->data);
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR("Create texture"));
}
/* Now create the package and the asset */ /* Now create the package and the asset */
String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR; String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR;
packageRelPath.append(importOptions.engName); packageRelPath.append(importOptions.engName);
@@ -225,66 +251,3 @@ AssetImporterBase::ImportResult StbTexture2DImporter::importAssets(const ImportO
} }
void StbTexture2DImporter::destructImportContext(void *cntx) { delete std::bit_cast<stb_texture2d::ImportContext *>(cntx); } void StbTexture2DImporter::destructImportContext(void *cntx) { delete std::bit_cast<stb_texture2d::ImportContext *>(cntx); }
std::optional<std::vector<cbe::Object *>> StbTexture2DImporter::tryImporting(const ImportOption &importOptions) const
{
std::vector<uint8> data;
if (FileHelper::readBytes(data, importOptions.filePath))
{
int32 channelsCount;
int32 dimX;
int32 dimY;
if (options.bFlipY)
{
stb::setLoadVerticalFlipped(true);
}
uint8 *texelData = stb::loadFromMemory(data, &dimX, &dimY, &channelsCount, stb_texture2d::CHANNEL_NUM);
stb::setLoadVerticalFlipped(false);
if (texelData == nullptr)
{
LOG_ERROR("Texture2DImporter", "Failed loading image[{}] - {}", importOptions.filePath, UTF8_TO_TCHAR(stb::lastFailure()));
}
else
{
cbe::Texture2DCreateInfo createInfo;
createInfo.dim = { static_cast<uint16>(dimX), static_cast<uint16>(dimY) };
createInfo.numOfChannels = stb_texture2d::CHANNEL_NUM;
createInfo.bIsNormal = isNormalTexture(texelData, createInfo.dim, stb_texture2d::CHANNEL_NUM, importOptions.engName);
/* TODO(Jeslas) : Generate mips */
createInfo.dataPerMip.resize(1);
const int32 pixelsCount = dimY * dimX;
createInfo.dataPerMip[0].resize(pixelsCount * stb_texture2d::CHANNEL_NUM);
memcpy(createInfo.dataPerMip[0].data(), texelData, pixelsCount * stb_texture2d::CHANNEL_NUM);
/* If normal we are inverting x value to account for flip of texture in u channel along tangent axis */
// TODO(Jeslas) : not exactly sure why I did that verify before committing.
// The normals without this inversion seems to be better though.
// if (createInfo.bIsNormal)
//{
// for (int32 i = 0; i < pixelsCount; ++i)
// {
// const SizeT redCompIdx = (i * stb_texture2d::CHANNEL_NUM) + 0;
// createInfo.dataPerMip[0][redCompIdx]
// = static_cast<uint8>(Math::clamp(255 - createInfo.dataPerMip[0][redCompIdx], 0, 255));
// }
//}
stb::deallocStbBuffer(texelData);
/* Now create the package and the asset */
String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR;
packageRelPath.append(importOptions.engName);
EditorHelpers::makePackageUnique(packageRelPath);
std::vector<cbe::Object *> textures{
EditorHelpers::createTexture2D(packageRelPath, importOptions.importContentPath, importOptions.engName, std::move(createInfo))
};
return textures;
}
}
return {};
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date June 2025 * \date June 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -57,8 +57,6 @@ public:
ImportContext prepareContext(ImportOption &inOutOptions) final; ImportContext prepareContext(ImportOption &inOutOptions) final;
ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final; ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final;
std::optional<std::vector<cbe::Object *>> tryImporting(const ImportOption &importOptions) const final;
protected: protected:
void destructImportContext(void *cntx) final; void destructImportContext(void *cntx) final;
/* Overrides ends */ /* Overrides ends */

View File

@@ -0,0 +1,33 @@
/*!
* \file EditorDragPayloads.cpp
*
* \author Subity
* \date January 2026
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
#include <Payloads/EditorDragPayloads.hpp>
#include <Components/ComponentBase.hpp>
namespace cbe
{
//////////////////////////////////////////////////////////////////////////
// ComponentDragPayload Implementation
//////////////////////////////////////////////////////////////////////////
ComponentDragPayload::ComponentDragPayload(Object *obj, uint64 userData)
: objPtr(obj)
, uData(userData)
, bLogicComp(PropertyHelper::isChildOf(obj->getType(), cbe::LogicComponent::staticType()))
{}
String ComponentDragPayload::getTitle() const
{
return STR_FORMAT("Component: {}", objPtr.isValid() ? objPtr->getObjectData().name : TCHAR("NULL"));
}
} // namespace cbe

View File

@@ -0,0 +1,39 @@
/*!
* \file EditorDragPayloads.hpp
*
* \author Subity
* \date January 2026
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
#include <Widgets/DragPayload.hpp>
#include <ObjectPtrs.h>
namespace cbe
{
class ComponentDragPayload : public IRttiDragPayload
{
CBE_SIMPLE_RTTI_CLASS(ComponentDragPayload, IRttiDragPayload)
public:
ComponentDragPayload(Object *obj, uint64 userData);
Object *getObject() const { return objPtr.get(); }
bool isLogicComponent() const { return bLogicComp; }
uint64 getUserData() const { return uData; }
/* DragPayload overrides */
protected:
String getTitle() const override;
/* Overrides ends */
private:
WeakObjectPtr objPtr;
uint64 uData;
bool bLogicComp;
};
} // namespace cbe

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date December 2025 * \date December 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -58,9 +58,15 @@ ViewportDrawResponse TrasformComponentViewportDrawer::drawObjContext(DrawInput i
} }
TransformComponentDrawContext &cntx = static_cast<TransformComponentDrawContext &>(input.objCntx); TransformComponentDrawContext &cntx = static_cast<TransformComponentDrawContext &>(input.objCntx);
cbe::TransformComponent *tfComponent = static_cast<cbe::TransformComponent *>(cntx.getObject());
if (tfComponent == nullptr)
{
return resp;
}
input.imDd.beginSelectionProxy(&cntx); input.imDd.beginSelectionProxy(&cntx);
Transform3D tf = static_cast<cbe::TransformComponent *>(cntx.getObject())->getWorldTransform(); Transform3D tf = tfComponent->getWorldTransform();
tf.setScale(tf.getScale() * TF_SPHERE_SCALE); tf.setScale(tf.getScale() * TF_SPHERE_SCALE);
input.imDd.drawMeshes( input.imDd.drawMeshes(
cbe::WorldImDrawContext::MeshDrawInfo{ cbe::WorldImDrawContext::MeshDrawInfo{

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -23,8 +23,8 @@ WgConsoleStringBuffer::~WgConsoleStringBuffer()
WgConsoleImGuiLayer::WgConsoleImGuiLayer() WgConsoleImGuiLayer::WgConsoleImGuiLayer()
{ {
callbackHndl = Logger::bindPacketListener( callbackHndl = cbe::Logger::bindPacketListener(
[this](const String &strBuffer, const std::vector<Logger::LogMsgPacket> &packets) [this](const String &strBuffer, const std::vector<cbe::Logger::LogMsgPacket> &packets)
{ {
onMsgPacketsReceived(strBuffer, packets); onMsgPacketsReceived(strBuffer, packets);
} }
@@ -32,7 +32,7 @@ WgConsoleImGuiLayer::WgConsoleImGuiLayer()
} }
WgConsoleImGuiLayer::~WgConsoleImGuiLayer() WgConsoleImGuiLayer::~WgConsoleImGuiLayer()
{ {
Logger::unbindPacketListener(callbackHndl); cbe::Logger::unbindPacketListener(callbackHndl);
callbackHndl = {}; callbackHndl = {};
packets.clear(); packets.clear();
@@ -77,7 +77,7 @@ void WgConsoleImGuiLayer::drawImGui()
} }
} }
copat::JobSystemFuncAwaiter copat::JobSystemFuncAwaiter
WgConsoleImGuiLayer::onMsgPacketsReceived(const String &inStrBuffer, const std::vector<Logger::LogMsgPacket> &inPackets) WgConsoleImGuiLayer::onMsgPacketsReceived(const String &inStrBuffer, const std::vector<cbe::Logger::LogMsgPacket> &inPackets)
{ {
if (inPackets.empty()) if (inPackets.empty())
{ {

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2024 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -51,7 +51,7 @@ private:
struct WgConsolePacket struct WgConsolePacket
{ {
ReferenceCountPtr<WgConsoleStringBuffer> strBuffer; ReferenceCountPtr<WgConsoleStringBuffer> strBuffer;
Logger::LogMsgPacket logPacket; cbe::Logger::LogMsgPacket logPacket;
}; };
class WgConsoleImGuiLayer : public IImGuiLayer class WgConsoleImGuiLayer : public IImGuiLayer
@@ -73,10 +73,10 @@ private:
SizeT packetsHead = 0; SizeT packetsHead = 0;
uint32 maxLogHistory = 100; uint32 maxLogHistory = 100;
Logger::ELogSeverityFlags logSeverityFilter = Logger::Log | Logger::Warning | Logger::Error; cbe::Logger::ELogSeverityFlags logSeverityFilter = cbe::Logger::Log | cbe::Logger::Warning | cbe::Logger::Error;
bool bAutoscroll = true; bool bAutoscroll = true;
private: private:
copat::JobSystemFuncAwaiter onMsgPacketsReceived(const String &inStrBuffer, const std::vector<Logger::LogMsgPacket> &inPackets); copat::JobSystemFuncAwaiter onMsgPacketsReceived(const String &inStrBuffer, const std::vector<cbe::Logger::LogMsgPacket> &inPackets);
void changeMaxLogHistory(uint32 maxHistory); void changeMaxLogHistory(uint32 maxHistory);
}; };

View File

@@ -4,29 +4,31 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
#include <Widgets/WgContentsImGuiLayer.h> #include <Widgets/WgContentsImGuiLayer.h>
#include <CbeApplication.h>
#include <CBEPackage.hpp> #include <CBEPackage.hpp>
#include <CoreObjectGC.h>
#include <Wg/EngineDragPayloads.hpp> #include <Wg/EngineDragPayloads.hpp>
#include <Serialization/ArrayArchiveStream.h> #include <Serialization/ArrayArchiveStream.h>
#include <Serialization/PackageLoader.h> #include <Serialization/PackageLoader.h>
#include <Serialization/PackageSaver.h> #include <Serialization/PackageSaver.h>
#include <Widgets/EditorWidgetsHelper.hpp>
#include <Widgets/WgEditorConstants.hpp>
#include <Widgets/WgEditorImGuiLayer.h>
#include <Widgets/ImGui/CbeImGui.hpp> #include <Widgets/ImGui/CbeImGui.hpp>
#include <Types/Platform/LFS/File/FileHelper.h> #include <Types/Platform/LFS/File/FileHelper.h>
#include <Types/Platform/LFS/PathFunctions.h> #include <Types/Platform/LFS/PathFunctions.h>
#include <Types/Platform/LFS/Paths.h> #include <Types/Platform/LFS/Paths.h>
#include <Types/Platform/LFS/PlatformLFS.h> #include <Types/Platform/LFS/PlatformLFS.h>
#include <CranberryEngineApp.h>
#include <IApplicationModule.h> #include <IApplicationModule.h>
#include <CBEAssetManager.hpp> #include <CBEAssetManager.hpp>
#include <Widgets/EditorWidgetsHelper.hpp>
#include <ObjectPathHelpers.h> #include <ObjectPathHelpers.h>
#include <Property/PropertyHelper.h> #include <Property/PropertyHelper.h>
#include <Widgets/WgEditorConstants.hpp>
#include <Classes/ActorPrefab.hpp> #include <Classes/ActorPrefab.hpp>
#include <Classes/World.hpp> #include <Classes/World.hpp>
#include <Classes/EngineBase.hpp> #include <Classes/EngineBase.hpp>
@@ -46,8 +48,6 @@ constexpr ImGuiWindowFlags CWD_CONTENTS_WND_FLAGS = ImGuiWindowFlags_NoTitleBar
constexpr const TChar *WG_CONTENT_LOG_CATEGORY = "WgContents"; constexpr const TChar *WG_CONTENT_LOG_CATEGORY = "WgContents";
constexpr Vector2 HMODAL_POPUP_SIZE = { 500, 200 };
constexpr Vector2 VMODAL_POPUP_SIZE = { 300, 500 };
constexpr const AChar *IMPORT_MODAL_POPUP = "Import Assets"; constexpr const AChar *IMPORT_MODAL_POPUP = "Import Assets";
constexpr const AChar *CREATE_MODAL_POPUP = "New Asset"; constexpr const AChar *CREATE_MODAL_POPUP = "New Asset";
constexpr const AChar *DELETE_ASSETS_MODAL_POPUP = "Delete Assets"; constexpr const AChar *DELETE_ASSETS_MODAL_POPUP = "Delete Assets";
@@ -69,8 +69,7 @@ static void deleteAssetPackage(StringView rootObjectPath, StringView packagePath
} }
static void openAssetEditorFromAssetPack(const cbe::AssetManager::AssetPackage *assetPack) static void openAssetEditorFromAssetPack(const cbe::AssetManager::AssetPackage *assetPack)
{ {
cbe::Object *asset = cbe::getOrLoad(assetPack->rootObjectPath, assetPack->rootObjectClass); cbe::gCBEditorEngine->openAssetEditor(assetPack->rootObjectPath, assetPack->rootObjectClass);
cbe::gCBEditorEngine->openAssetEditor(asset);
} }
/** /**
* Approach each delete, copy, move actions from transaction point. * Approach each delete, copy, move actions from transaction point.
@@ -96,7 +95,7 @@ public:
{ {
/* Remove the backup directory */ /* Remove the backup directory */
const bool bDeleted = FileHelper::deleteDir(backupDir); const bool bDeleted = FileHelper::deleteDir(backupDir);
LOG_ERROR_C(!bDeleted, cbe::Transaction::LOG_CATEGORY, "Failed to delete transaction directory {}", backupDir); CBE_LOG_ERROR_C(!bDeleted, cbe::Transaction::LOG_CATEGORY, "Failed to delete transaction directory {}", backupDir);
backupDir.clear(); backupDir.clear();
} }
} }
@@ -124,7 +123,7 @@ public:
const bool bSucceeded = FileHelper::copyFile( const bool bSucceeded = FileHelper::copyFile(
PathFunctions::combinePath(backupDir, contentRelPkgPath), PathFunctions::combinePath(contentDir, contentRelPkgPath) PathFunctions::combinePath(backupDir, contentRelPkgPath), PathFunctions::combinePath(contentDir, contentRelPkgPath)
); );
LOG_ERROR_C( CBE_LOG_ERROR_C(
!bSucceeded, cbe::Transaction::LOG_CATEGORY, "Failed to copy package {} to content dir {}", pkg.packagePath, contentDir !bSucceeded, cbe::Transaction::LOG_CATEGORY, "Failed to copy package {} to content dir {}", pkg.packagePath, contentDir
); );
} }
@@ -174,7 +173,7 @@ public:
{ {
/* Remove the backup directory */ /* Remove the backup directory */
const bool bDeleted = FileHelper::deleteDir(backupDir); const bool bDeleted = FileHelper::deleteDir(backupDir);
LOG_ERROR_C(!bDeleted, cbe::Transaction::LOG_CATEGORY, "Failed to delete transaction directory {}", backupDir); CBE_LOG_ERROR_C(!bDeleted, cbe::Transaction::LOG_CATEGORY, "Failed to delete transaction directory {}", backupDir);
backupDir.clear(); backupDir.clear();
} }
} }
@@ -210,7 +209,7 @@ public:
FileHelper::makeDir(destDir); FileHelper::makeDir(destDir);
} }
const bool bCopied = FileHelper::copyFile(srcPath, destPath); const bool bCopied = FileHelper::copyFile(srcPath, destPath);
LOG_ERROR_C(!bCopied, cbe::Transaction::LOG_CATEGORY, "Failed to copy package {} to file {}", srcPath, destPath); CBE_LOG_ERROR_C(!bCopied, cbe::Transaction::LOG_CATEGORY, "Failed to copy package {} to file {}", srcPath, destPath);
} }
/* Load one package to refresh the package manager */ /* Load one package to refresh the package manager */
@@ -271,7 +270,7 @@ public:
{ {
/* Remove the backup directory */ /* Remove the backup directory */
const bool bDeleted = FileHelper::deleteDir(backupDir); const bool bDeleted = FileHelper::deleteDir(backupDir);
LOG_ERROR_C(!bDeleted, cbe::Transaction::LOG_CATEGORY, "Failed to delete transaction directory {}", backupDir); CBE_LOG_ERROR_C(!bDeleted, cbe::Transaction::LOG_CATEGORY, "Failed to delete transaction directory {}", backupDir);
backupDir.clear(); backupDir.clear();
} }
} }
@@ -353,7 +352,7 @@ private:
FileHelper::makeDir(destDir); FileHelper::makeDir(destDir);
} }
const bool bCopied = FileHelper::copyFile(srcPath, destPath); const bool bCopied = FileHelper::copyFile(srcPath, destPath);
LOG_ERROR_C(!bCopied, cbe::Transaction::LOG_CATEGORY, "Failed to copy package {} to file {}", srcPath, destPath); CBE_LOG_ERROR_C(!bCopied, cbe::Transaction::LOG_CATEGORY, "Failed to copy package {} to file {}", srcPath, destPath);
} }
/* Load one package to refresh the package manager */ /* Load one package to refresh the package manager */
@@ -377,8 +376,7 @@ void WgContentsImGuiLayer::drawImGui()
{ {
if (assetManager == nullptr) if (assetManager == nullptr)
{ {
CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication()); assetManager = cbe::gCBEditorEngine->getAssetManager();
assetManager = application->getAssetManager();
} }
if (assetManager == nullptr) if (assetManager == nullptr)
{ {
@@ -389,6 +387,9 @@ void WgContentsImGuiLayer::drawImGui()
const ImGuiWindowFlags wndFlags = ImGuiWindowFlags_NoFocusOnAppearing; ///< To not mess with docking parent focus const ImGuiWindowFlags wndFlags = ImGuiWindowFlags_NoFocusOnAppearing; ///< To not mess with docking parent focus
const cbe::ImGuiScopedWindow window{ cbe::wg_consts::WND_CONTENTS_ID, wndFlags }; const cbe::ImGuiScopedWindow window{ cbe::wg_consts::WND_CONTENTS_ID, wndFlags };
ImGui::PopStyleVar();
if (window) if (window)
{ {
if (ImGui::BeginChild( if (ImGui::BeginChild(
@@ -428,8 +429,6 @@ void WgContentsImGuiLayer::drawImGui()
drawCwdContents(); drawCwdContents();
} }
ImGui::PopStyleVar();
drawImportAssetsModal(); drawImportAssetsModal();
drawDeleteAssetsModal(); drawDeleteAssetsModal();
drawCreateAssetModal(); drawCreateAssetModal();
@@ -1033,16 +1032,20 @@ void WgContentsImGuiLayer::drawAssetContextMenus(const CwdContent &content)
drawMultiSelectContextMenus(); drawMultiSelectContextMenus();
return; return;
} }
#if DEBUG_VALIDATIONS_ENABLED
{ {
void *selItr = nullptr; void *selItr = nullptr;
ImGuiID id; ImGuiID id;
const bool bHasSel = selections.GetNextSelectedItem(&selItr, &id); const bool bHasSel = selections.GetNextSelectedItem(&selItr, &id);
const uint32 cwdContentIdx = imGuiIdToCwdContentIdx(id); const uint32 cwdContentIdx = imGuiIdToCwdContentIdx(id);
debugAssert(bHasSel && cwdContentIdx < cwdContents.size()); debugAssert(bHasSel && cwdContentIdx < cwdContents.size());
debugAssertf(content.nodeIdx == cwdContents[cwdContentIdx].nodeIdx, "Context menu must open only over current selection!"); if (content.nodeIdx != cwdContents[cwdContentIdx].nodeIdx)
{
/* Right mouse button released on top of folder without selecting it. Probably stray mouse drag release. */
ImGui::CloseCurrentPopup();
return;
}
} }
#endif
if (ImGui::MenuItem(CBE_MAT_ICON_EDIT_SQUARE " Open")) if (ImGui::MenuItem(CBE_MAT_ICON_EDIT_SQUARE " Open"))
{ {
@@ -1080,7 +1083,6 @@ void WgContentsImGuiLayer::drawFolderContextMenus(TreeNodeIdx dirFolderTreeIdx)
return; return;
} }
#if DEBUG_VALIDATIONS_ENABLED
if (selections.Size == 1) if (selections.Size == 1)
{ {
void *selItr = nullptr; void *selItr = nullptr;
@@ -1088,9 +1090,13 @@ void WgContentsImGuiLayer::drawFolderContextMenus(TreeNodeIdx dirFolderTreeIdx)
const bool bHasSel = selections.GetNextSelectedItem(&selItr, &id); const bool bHasSel = selections.GetNextSelectedItem(&selItr, &id);
const uint32 cwdContentIdx = imGuiIdToCwdContentIdx(id); const uint32 cwdContentIdx = imGuiIdToCwdContentIdx(id);
debugAssert(bHasSel && cwdContentIdx < cwdContents.size()); debugAssert(bHasSel && cwdContentIdx < cwdContents.size());
debugAssertf(dirFolderTreeIdx == cwdContents[cwdContentIdx].nodeIdx, "Context menu must open only over current selection!"); if (dirFolderTreeIdx != cwdContents[cwdContentIdx].nodeIdx)
{
/* Right mouse button released on top of folder without selecting it. Probably stray mouse drag release. */
ImGui::CloseCurrentPopup();
return;
}
} }
#endif
const AChar *createAssetLabel = CBE_MAT_ICON_LARGE_ADD " Create"; const AChar *createAssetLabel = CBE_MAT_ICON_LARGE_ADD " Create";
const bool bCreateAssetOpenLastFrame = cbe::ImGuiHelpers::isPopupOpen(cbe::ImGuiHelpers::getPopupId(createAssetLabel)); const bool bCreateAssetOpenLastFrame = cbe::ImGuiHelpers::isPopupOpen(cbe::ImGuiHelpers::getPopupId(createAssetLabel));
@@ -1313,13 +1319,18 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
ImportAssetData &impData = *importData; ImportAssetData &impData = *importData;
const uint32 pathIdx = impData.pathIdx; const uint32 pathIdx = impData.pathIdx;
/* Necessary to try to reopen this pop up after async processing is done. Since progress bar closes this one. */
if (!ImGui::IsPopupOpen(IMPORT_MODAL_POPUP) && !impData.bProcessing)
{
ImGui::OpenPopup(IMPORT_MODAL_POPUP);
}
bool bPopupOpen = true; bool bPopupOpen = true;
/* To allow matching content size every time it appears */ /* To allow matching content size every time it appears */
ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing);
ImGui::SetNextWindowSizeConstraints(VMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() }); ImGui::SetNextWindowSizeConstraints(cbe::wg_consts::VMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() });
if (ImGui::BeginPopupModal(IMPORT_MODAL_POPUP, &bPopupOpen, ImGuiWindowFlags_None)) if (ImGui::BeginPopupModal(IMPORT_MODAL_POPUP, &bPopupOpen, ImGuiWindowFlags_None))
{ {
bool bImportNextPath = false;
if (impData.importer == nullptr) if (impData.importer == nullptr)
{ {
/* It is okay we take view directly from paths instead of import option's filePath as both live until import is done. */ /* It is okay we take view directly from paths instead of import option's filePath as both live until import is done. */
@@ -1357,7 +1368,7 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
/* Still null? Skip to next path */ /* Still null? Skip to next path */
if (impData.importer == nullptr) if (impData.importer == nullptr)
{ {
bImportNextPath = true; impData.bImportNext = true;
} }
else else
{ {
@@ -1377,6 +1388,8 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
); );
} }
ImGui::BeginDisabled(impData.bProcessing);
ImGui::SetNextItemShortcut(ImGuiKey_Escape); ImGui::SetNextItemShortcut(ImGuiKey_Escape);
if (ImGui::Button("Cancel")) if (ImGui::Button("Cancel"))
{ {
@@ -1391,32 +1404,109 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
/* If importer and context is valid try importing else go to next path. */ /* If importer and context is valid try importing else go to next path. */
if (impData.cntx) if (impData.cntx)
{ {
const AssetImporterBase::ImportResult result = impData.importer->importAssets(impData.importOption, impData.cntx); debugAssert(impData.importOption.importProgressCount > 0);
if (std::holds_alternative<AssetImporterBase::ImportSuccess>(result))
{ /* +1 for completion in main thread */
const AssetImporterBase::ImportSuccess &success = std::get<AssetImporterBase::ImportSuccess>(result); const cbe::ProgressTrackerHnd progTracker = cbe::gCBEditorEngine->addProgressTracker(cbe::ProgressTrackerInfo{
for (cbe::Object *obj : success.objs) .name = TCHAR("Import"),
.stepsCount = impData.importOption.importProgressCount + 1,
});
impData.importOption.progressCb.bindLambda(
[progTracker](uint32 stepsCount, StringView desc)
{ {
LOG(WG_CONTENT_LOG_CATEGORY, "Imported {}", obj->getObjectData().path); cbe::gCBEditorEngine->progressProgressTracker(progTracker, stepsCount, desc);
cbe::save(obj); }
);
auto importAssets = [](copat::JobSystem &js, WgContentsImGuiLayer *thisLayer,
cbe::ProgressTrackerHnd progTracker) -> copat::JobSystemFuncAwaiter
{
CoreObjectGC &gc = ICoreObjectsModule::get()->getGC();
/* To not clear the loaded objects while Async loading */
gc.pauseGc();
co_await copat::SwitchJobSystemThreadAwaiter{ js, copat::EJobThreadType::WorkerThreads };
debugAssert(thisLayer->importData);
ImportAssetData &impData = *thisLayer->importData;
/* Perform the import */
const AssetImporterBase::ImportResult result = impData.importer->importAssets(impData.importOption, impData.cntx);
/* Sync with main thread to start saving */
co_await copat::SwitchJobSystemThreadAwaiter{ *copat::JobSystem::get(), copat::EJobThreadType::MainThread };
cbe::gCBEditorEngine->progressProgressTracker(progTracker, 1, {});
cbe::ProgressTrackerHnd savingProgTracker = 0;
if (std::holds_alternative<AssetImporterBase::ImportSuccess>(result))
{
const AssetImporterBase::ImportSuccess &success = std::get<AssetImporterBase::ImportSuccess>(result);
/* +1 to finish at main thread and refresh content browser */
savingProgTracker = cbe::gCBEditorEngine->addProgressTracker(cbe::ProgressTrackerInfo{
.name = TCHAR("Saving"),
.stepsCount = static_cast<uint32>(success.objs.size()) + 1,
});
/* Save in worker thread */
co_await copat::SwitchJobSystemThreadAwaiter{ js, copat::EJobThreadType::WorkerThreads };
for (cbe::Object *obj : success.objs)
{
CBE_LOG(WG_CONTENT_LOG_CATEGORY, "Imported and Saved {}", obj->getObjectData().path);
cbe::gCBEditorEngine->progressProgressTracker(
savingProgTracker, 1, STR_FORMAT("Saving {}", obj->getObjectData().name)
);
cbe::save(obj);
co_await copat::YieldAwaiter{};
}
/* Switch back to main thread */
co_await copat::SwitchJobSystemThreadAwaiter{ *copat::JobSystem::get(), copat::EJobThreadType::MainThread };
}
else
{
CBE_LOG_ERROR(
WG_CONTENT_LOG_CATEGORY, "Importing {} failed with following errors", impData.importOption.filePath
);
const AssetImporterBase::ImportError &errs = std::get<AssetImporterBase::ImportError>(result);
for (SizeT i = 0; i < errs.errors.size(); ++i)
{
CBE_LOG_ERROR(WG_CONTENT_LOG_CATEGORY, "Import error {}: {}", i, errs.errors[i]);
}
} }
/* Refresh if imported successfully. */ if (std::holds_alternative<AssetImporterBase::ImportSuccess>(result))
refreshCwd();
}
else
{
LOG_ERROR(WG_CONTENT_LOG_CATEGORY, "Importing {} failed with following errors", impData.importOption.filePath);
const AssetImporterBase::ImportError &errs = std::get<AssetImporterBase::ImportError>(result);
for (SizeT i = 0; i < errs.errors.size(); ++i)
{ {
LOG_ERROR(WG_CONTENT_LOG_CATEGORY, "Import error {}: {}", i, errs.errors[i]); cbe::gCBEditorEngine->progressProgressTracker(savingProgTracker, 1, TCHAR("Refresh content browser"));
/* Refresh if imported successfully. */
thisLayer->refreshCwd();
} }
}
impData.bProcessing = false;
/* Go to next path */
impData.bImportNext = true;
impData.importOption.progressCb.unbind();
/* Cancel just in case prepare fails. */
cbe::gCBEditorEngine->cancelProgressTracker(progTracker);
cbe::gCBEditorEngine->cancelProgressTracker(savingProgTracker);
gc.resumeGc();
};
impData.bProcessing = true;
importAssets(*copat::JobSystem::get(), this, progTracker);
}
else
{
/* Go to next path */
impData.bImportNext = true;
} }
/* Go to next path */
bImportNextPath = true;
} }
} }
else else
@@ -1424,17 +1514,53 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
if (ImGui::Button("Prepare")) if (ImGui::Button("Prepare"))
{ {
debugAssert(impData.importer != nullptr); debugAssert(impData.importer != nullptr);
impData.cntx = impData.importer->prepareContext(impData.importOption); debugAssert(impData.importOption.prepareProgressCount > 0);
if (impData.importOption.cntxOptionsStruct != nullptr)
const cbe::ProgressTrackerHnd progTracker = cbe::gCBEditorEngine->addProgressTracker(cbe::ProgressTrackerInfo{
.name = TCHAR("Prepare Import"),
.stepsCount = impData.importOption.prepareProgressCount + 1,
});
impData.importOption.progressCb.bindLambda(
[progTracker](uint32 stepsCount, StringView desc)
{
cbe::gCBEditorEngine->progressProgressTracker(progTracker, stepsCount, desc);
}
);
auto prepareCntx
= [](copat::JobSystem &js, ImportAssetData &impData, cbe::ProgressTrackerHnd progTracker) -> copat::JobSystemFuncAwaiter
{ {
impData.cntxOptionsDrawer = std::make_unique<cbe::StructDetailsDrawer>( co_await copat::SwitchJobSystemThreadAwaiter{ js, copat::EJobThreadType::WorkerThreads };
impData.importOption.cntxOptionsStructType, impData.importOption.cntxOptionsStruct
); /* Prepare the context */
} impData.cntx = impData.importer->prepareContext(impData.importOption);
impData.bPrepared = true; if (impData.importOption.cntxOptionsStruct != nullptr)
{
impData.cntxOptionsDrawer = std::make_unique<cbe::StructDetailsDrawer>(
impData.importOption.cntxOptionsStructType, impData.importOption.cntxOptionsStruct
);
}
co_await copat::SwitchJobSystemThreadAwaiter{ *copat::JobSystem::get(), copat::EJobThreadType::MainThread };
impData.bPrepared = true;
impData.bProcessing = false;
impData.importOption.progressCb.unbind();
/* Do final progress */
cbe::gCBEditorEngine->progressProgressTracker(progTracker, 1, {});
/* Cancel just in case prepare fails. */
cbe::gCBEditorEngine->cancelProgressTracker(progTracker);
};
impData.bProcessing = true;
prepareCntx(*copat::JobSystem::get(), impData, progTracker);
} }
} }
ImGui::EndDisabled();
ImGui::Separator(); ImGui::Separator();
if (cbe::EditorWidgetsHelper::beginPropertiesTable("", 150.f)) if (cbe::EditorWidgetsHelper::beginPropertiesTable("", 150.f))
{ {
@@ -1451,7 +1577,7 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
ImGui::EndTable(); ImGui::EndTable();
} }
if (bImportNextPath) if (impData.bImportNext)
{ {
impData.pathIdx++; impData.pathIdx++;
/* Reset the data cache for a file import */ /* Reset the data cache for a file import */
@@ -1461,6 +1587,8 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
impData.cntxOptionsDrawer.reset(); impData.cntxOptionsDrawer.reset();
impData.cntx.reset(); impData.cntx.reset();
impData.bPrepared = false; impData.bPrepared = false;
impData.bProcessing = false;
impData.bImportNext = false;
if (impData.pathIdx >= impData.fromPaths.size()) if (impData.pathIdx >= impData.fromPaths.size())
{ {
/* Close the pop up now. */ /* Close the pop up now. */
@@ -1475,7 +1603,8 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
ImGui::EndPopup(); ImGui::EndPopup();
} }
if (!bPopupOpen) /* If processing, probably the progress bar made this popup to close. */
if (!bPopupOpen && !impData.bProcessing)
{ {
importData.reset(); importData.reset();
} }
@@ -1771,9 +1900,19 @@ void WgContentsImGuiLayer::issueMoveAssets(MoveAssetsInfo &&moveInf)
movedDummyTransaction.apply(dummyCollector); movedDummyTransaction.apply(dummyCollector);
for (uint32 i = 0; i < moveInf.movingAssets.size(); ++i) for (uint32 i = 0; i < moveInf.movingAssets.size(); ++i)
{ {
cbe::Object *orgObj = orgRootObjs[i];
cbe::Object *movedObj = cbe::getOrLoad(movedDummyTransaction.pkgs[i].rootObjectPath, nullptr); cbe::Object *movedObj = cbe::getOrLoad(movedDummyTransaction.pkgs[i].rootObjectPath, nullptr);
debugAssert(movedObj != nullptr); debugAssert(movedObj != nullptr);
replacements[orgRootObjs[i]] = movedObj; replacements[orgObj] = movedObj;
/* Since asset sub objects will also be modified we need to replace them as well. */
std::vector<cbe::Object *> childrenObjs;
objectsDb.getSubobjects(childrenObjs, orgObj->getDbIdx());
for (cbe::Object *child : childrenObjs)
{
const String childSubPath = ObjectPathHelper::computeObjectPath(child, orgObj);
replacements[child] = cbe::get(ObjectPathHelper::getFullPath(childSubPath, movedObj));
}
} }
cbe::TransactionRef thisTransaction; cbe::TransactionRef thisTransaction;
@@ -1800,7 +1939,7 @@ void WgContentsImGuiLayer::issueMoveAssets(MoveAssetsInfo &&moveInf)
{ {
thisTransaction->captureObjectBaseline(referrer); thisTransaction->captureObjectBaseline(referrer);
std::vector<cbe::Object *> children; std::vector<cbe::Object *> children;
objectsDb.getChildren(children, referrer->getDbIdx()); objectsDb.getSubobjects(children, referrer->getDbIdx());
for (cbe::Object *child : children) for (cbe::Object *child : children)
{ {
thisTransaction->captureObjectBaseline(child); thisTransaction->captureObjectBaseline(child);
@@ -1863,7 +2002,7 @@ void WgContentsImGuiLayer::drawRenameModal()
/* To allow matching content size every time if appears */ /* To allow matching content size every time if appears */
ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing);
ImGui::SetNextWindowSizeConstraints(HMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() }); ImGui::SetNextWindowSizeConstraints(cbe::wg_consts::HMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() });
if (ImGui::BeginPopupModal(RENAME_ASSET_MODAL_POPUP, &bPopupOpen)) if (ImGui::BeginPopupModal(RENAME_ASSET_MODAL_POPUP, &bPopupOpen))
{ {
const std::string_view descText = renameData.content.bFolder ? "Rename folder " : "Rename asset "; const std::string_view descText = renameData.content.bFolder ? "Rename folder " : "Rename asset ";
@@ -2083,7 +2222,7 @@ void WgContentsImGuiLayer::drawMoveModal()
/* To allow matching content size every time if appears */ /* To allow matching content size every time if appears */
ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing);
ImGui::SetNextWindowSizeConstraints(HMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() }); ImGui::SetNextWindowSizeConstraints(cbe::wg_consts::HMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() });
if (ImGui::BeginPopupModal(MOVE_ASSETS_MODAL_POPUP, &bPopupOpen)) if (ImGui::BeginPopupModal(MOVE_ASSETS_MODAL_POPUP, &bPopupOpen))
{ {
cbe::ImGuiHelpers::textUnformatted(std::format("Moving {} assets", moveData.movingAssets.size())); cbe::ImGuiHelpers::textUnformatted(std::format("Moving {} assets", moveData.movingAssets.size()));
@@ -2256,7 +2395,7 @@ void WgContentsImGuiLayer::openDeleteAssetsModal(bool bDeleteFolder)
std::vector<CwdContent> &referrerContents = deleteAssetData.deletingAssetsReferrers.emplace_back(); std::vector<CwdContent> &referrerContents = deleteAssetData.deletingAssetsReferrers.emplace_back();
std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers; std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, treeNodeIdx); assetManager->getPackageReferrersFromRefTree(referrers, treeNodeIdx);
for (const cbe::AssetManager::PackageAllocator::AllocHandle &hnd : referrers) for (const cbe::AssetManager::PackageAllocator::AllocHandle &hnd : referrers)
{ {
referrerContents.emplace_back(fillPackageContent(assetManager->getAssetPackage(hnd)->folderTreeIdx)); referrerContents.emplace_back(fillPackageContent(assetManager->getAssetPackage(hnd)->folderTreeIdx));
@@ -2281,6 +2420,7 @@ void WgContentsImGuiLayer::drawDeleteAssetsModal()
return; return;
} }
const CoreObjectsDB &objectsDb = ICoreObjectsModule::get()->objectsDB();
DeleteAssetData &deleteAssetsData = std::get<DeleteAssetData>(assetEditData); DeleteAssetData &deleteAssetsData = std::get<DeleteAssetData>(assetEditData);
bool bIssueDelete = false; bool bIssueDelete = false;
@@ -2288,7 +2428,7 @@ void WgContentsImGuiLayer::drawDeleteAssetsModal()
/* To allow matching content size every time if appears */ /* To allow matching content size every time if appears */
ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing); ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing);
ImGui::SetNextWindowSizeConstraints(HMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() }); ImGui::SetNextWindowSizeConstraints(cbe::wg_consts::HMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() });
if (ImGui::BeginPopupModal(DELETE_ASSETS_MODAL_POPUP, &bPopupOpen)) if (ImGui::BeginPopupModal(DELETE_ASSETS_MODAL_POPUP, &bPopupOpen))
{ {
ImGui::TextUnformatted(std::format("Delete following {} Assets?", deleteAssetsData.deletingAssets.size()).c_str()); ImGui::TextUnformatted(std::format("Delete following {} Assets?", deleteAssetsData.deletingAssets.size()).c_str());
@@ -2393,6 +2533,20 @@ void WgContentsImGuiLayer::drawDeleteAssetsModal()
cbe::Object *obj = cbe::getOrLoad(pkg->rootObjectPath, nullptr); cbe::Object *obj = cbe::getOrLoad(pkg->rootObjectPath, nullptr);
cbe::Object *replacement = replacementCntx.objPath.isValid() ? replacementCntx.objPath.getObject() : nullptr; cbe::Object *replacement = replacementCntx.objPath.isValid() ? replacementCntx.objPath.getObject() : nullptr;
replacements[obj] = replacement; replacements[obj] = replacement;
/* Since asset sub objects will also be modified we need to replace them as well. */
std::vector<cbe::Object *> childrenObjs;
objectsDb.getSubobjects(childrenObjs, obj->getDbIdx());
for (cbe::Object *child : childrenObjs)
{
if (replacement == nullptr)
{
replacements[child] = nullptr;
continue;
}
const String childSubPath = ObjectPathHelper::computeObjectPath(child, obj);
replacements[child] = cbe::get(ObjectPathHelper::getFullPath(childSubPath, replacement));
}
} }
/* Generate unique referrer objects list */ /* Generate unique referrer objects list */
for (uint32 i = 0; i < deleteAssetsData.deletingAssets.size(); ++i) for (uint32 i = 0; i < deleteAssetsData.deletingAssets.size(); ++i)
@@ -2478,12 +2632,11 @@ void WgContentsImGuiLayer::drawDeleteAssetsModal()
/* Issue save transaction that must be reverted on undelete */ /* Issue save transaction that must be reverted on undelete */
thisTransaction->addCustomAction<cbe::SaveAssetsAction>(nullptr, referrers, cbe::SaveAssetsAction::AtRevert); thisTransaction->addCustomAction<cbe::SaveAssetsAction>(nullptr, referrers, cbe::SaveAssetsAction::AtRevert);
const CoreObjectsDB &objectsDb = ICoreObjectsModule::get()->objectsDB();
for (cbe::Object *referrer : referrers) for (cbe::Object *referrer : referrers)
{ {
thisTransaction->captureObjectBaseline(referrer); thisTransaction->captureObjectBaseline(referrer);
std::vector<cbe::Object *> children; std::vector<cbe::Object *> children;
objectsDb.getChildren(children, referrer->getDbIdx()); objectsDb.getSubobjects(children, referrer->getDbIdx());
for (cbe::Object *child : children) for (cbe::Object *child : children)
{ {
thisTransaction->captureObjectBaseline(child); thisTransaction->captureObjectBaseline(child);
@@ -2608,7 +2761,7 @@ void WgContentsImGuiLayer::cutAssets(bool bCutFolder)
std::vector<CwdContent> &referrerContents = moveAssetData.movingAssetsReferrers.emplace_back(); std::vector<CwdContent> &referrerContents = moveAssetData.movingAssetsReferrers.emplace_back();
std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers; std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, treeNodeIdx); assetManager->getPackageReferrersFromRefTree(referrers, treeNodeIdx);
for (const cbe::AssetManager::PackageAllocator::AllocHandle &hnd : referrers) for (const cbe::AssetManager::PackageAllocator::AllocHandle &hnd : referrers)
{ {
referrerContents.emplace_back(fillPackageContent(assetManager->getAssetPackage(hnd)->folderTreeIdx)); referrerContents.emplace_back(fillPackageContent(assetManager->getAssetPackage(hnd)->folderTreeIdx));
@@ -2893,7 +3046,7 @@ void WgContentsImGuiLayer::openRenameAssetOrFolderModal(TreeNodeIdx folderTreeId
); );
std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers; std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, folderTreeIdx); assetManager->getPackageReferrersFromRefTree(referrers, folderTreeIdx);
std::vector<CwdContent> &outReferrers = renameData.referrers.emplace_back(); std::vector<CwdContent> &outReferrers = renameData.referrers.emplace_back();
outReferrers.reserve(referrers.size()); outReferrers.reserve(referrers.size());
@@ -2922,7 +3075,7 @@ void WgContentsImGuiLayer::openRenameAssetOrFolderModal(TreeNodeIdx folderTreeId
renameData.folderAssets.emplace_back(fillPackageContent(childIdx)); renameData.folderAssets.emplace_back(fillPackageContent(childIdx));
std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers; std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, childIdx); assetManager->getPackageReferrersFromRefTree(referrers, childIdx);
std::vector<CwdContent> &outReferrers = renameData.referrers.emplace_back(); std::vector<CwdContent> &outReferrers = renameData.referrers.emplace_back();
outReferrers.reserve(referrers.size()); outReferrers.reserve(referrers.size());
for (cbe::AssetManager::PackageAllocator::AllocHandle pkgAllocHnd : referrers) for (cbe::AssetManager::PackageAllocator::AllocHandle pkgAllocHnd : referrers)

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -172,7 +172,11 @@ private:
/* Importing path index. We do one index at a time */ /* Importing path index. We do one index at a time */
uint32 pathIdx = 0; uint32 pathIdx = 0;
/* When context is prepared. Gets filled after importer is prepared */ /* When context is prepared. Gets filled after importer is prepared */
bool bPrepared = false; bool bPrepared:1 = false;
/* When a step is being async processed. */
bool bProcessing:1 = false;
/* If current import is done and next import is requested */
bool bImportNext:1 = false;
}; };
private: private:

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -22,16 +22,29 @@
struct WorldViewport; struct WorldViewport;
namespace cbe
{
enum class EDetailsFlags : uint8
{
None = 0, ///< None
SendCompSelMsgs, ///< If want to send component selection change messages to editor.
};
MAKE_ENUM_BIT_OPS(EDetailsFlags)
struct WgDetailsCreateInfo struct WgDetailsCreateInfo
{ {
const cbe::ICoreAssetEditorRef &edRoot; const cbe::ICoreAssetEditorRef &edRoot;
std::string detailWndName; std::string detailWndName;
EDetailsFlags flags = EDetailsFlags::None;
}; };
} // namespace cbe
class WgDetailsImGuiLayer : public IImGuiLayer class WgDetailsImGuiLayer : public IImGuiLayer
{ {
public: public:
WgDetailsImGuiLayer(WgDetailsCreateInfo ci); WgDetailsImGuiLayer(cbe::WgDetailsCreateInfo ci);
~WgDetailsImGuiLayer(); ~WgDetailsImGuiLayer();
/* IImGuiLayer overrides */ /* IImGuiLayer overrides */
@@ -39,10 +52,17 @@ public:
/* Overrides ends */ /* Overrides ends */
void setWorldViewport(cbe::World *world, WorldViewport *viewport); void setWorldViewport(cbe::World *world, WorldViewport *viewport);
void resetDetails()
{
/* redo this when sub-selection gets added */
onSelectionChanged();
}
void onSelectionChanged(); void onSelectionChanged();
void onSelectionTransformed() { rebuildTransformCompWidgetDrawer(); } void onSelectionTransformed() { rebuildTransformCompWidgetDrawer(); }
void
onSubselectionChanged(ArrayView<cbe::WeakObjectPtr> selectedObjs, const std::unordered_set<cbe::WorldSelectionProxyRef> &selectedProxies);
/* If none of the actor selected is one of the object provided in does not refreshes */ /* If none of the actor selected is one of the object provided in does not refreshes */
void refreshDetailsDrawers(ArrayView<cbe::Object *> objsModified); void refreshDetailsDrawers(ArrayView<cbe::WeakObjectPtr> objsModified);
private: private:
static_assert(!IsTCharWide::value, "Wide character support is not optimized here! It will work but performs bad"); static_assert(!IsTCharWide::value, "Wide character support is not optimized here! It will work but performs bad");
@@ -73,7 +93,7 @@ private:
enum ESelectionVariantIndex enum ESelectionVariantIndex
{ {
Selection_Single, Selection_Single,
Selection_Multi Selection_Multi,
}; };
using TransformCompNodeData = std::variant<TransformPerCompNodeData, std::vector<TransformPerCompNodeData>>; using TransformCompNodeData = std::variant<TransformPerCompNodeData, std::vector<TransformPerCompNodeData>>;
using LogicCompData = std::variant<LogicPerCompData, std::vector<LogicPerCompData>>; using LogicCompData = std::variant<LogicPerCompData, std::vector<LogicPerCompData>>;
@@ -81,6 +101,48 @@ private:
using TfCompTreeTypeList = TL::CreateFrom_t<TransformCompNodeData, TransformCompCommonNodeData>; using TfCompTreeTypeList = TL::CreateFrom_t<TransformCompNodeData, TransformCompCommonNodeData>;
using FlatTfCompTree = FlatTree<TfCompTreeTypeList, uint32>; using FlatTfCompTree = FlatTree<TfCompTreeTypeList, uint32>;
struct ComponentListRef
{
cbe::WeakObjectPtr comp;
/* Will be node index for tf and leaf component.
* Will be logic component index for logic component. */
uint32 idx;
bool bLeaf:1;
bool bLogic:1;
};
struct AddComponentData
{
String compName;
std::vector<cbe::ObjectPath> selCompPerActor;
CBEClass componentClazz;
StringView transactionText;
bool bIssueTransaction;
bool bValidName;
};
struct RemoveComponentData
{
std::string compName;
ComponentListRef compRef;
bool bIssueTransaction;
};
struct RenameComponentData
{
std::string compName;
std::string newName;
ComponentListRef compRef;
bool bIssueTransaction;
};
struct ChangeRootComponentData
{
std::string compName;
ComponentListRef compRef;
bool bIssueTransaction;
};
using ComponentManipulationData
= std::variant<NullType, AddComponentData, RemoveComponentData, RenameComponentData, ChangeRootComponentData>;
private: private:
bool isMultiSelection() const { return selectedActors.size() > 1; } bool isMultiSelection() const { return selectedActors.size() > 1; }
void buildTransformCompTree(); void buildTransformCompTree();
@@ -98,6 +160,16 @@ private:
void drawTfNodeDetails(FlatTfCompTree::NodeIdx nodeIdx); void drawTfNodeDetails(FlatTfCompTree::NodeIdx nodeIdx);
void drawLogicCompDetails(uint32 logicCompIdx); void drawLogicCompDetails(uint32 logicCompIdx);
void drawAddComponentMenu();
void drawCompContextMenu(const cbe::ObjectPath &objPath, CBEClass clazz, uint32 idx, bool bLogicComp, bool bLeafComp);
void drawAddComponentModal();
void drawRemoveComponentModal();
void drawRenameComponentModal();
void drawChangeRootComponentModal();
void drawCompDragSource(const cbe::ObjectPath &objPath, uint32 idx);
void drawTfCompTreeDragTarget(const cbe::ObjectPath &objPath);
private: private:
constexpr static const AChar *MULTIPLE_SELECTION_TXT = "Multiple"; constexpr static const AChar *MULTIPLE_SELECTION_TXT = "Multiple";
constexpr static const float COMP_LIST_FRAME_SIZE = 0.2f; constexpr static const float COMP_LIST_FRAME_SIZE = 0.2f;
@@ -124,6 +196,8 @@ private:
FlatTfCompTree::NodeIdx selectedTfNodeIdx = FlatTfCompTree::InvalidIdx; FlatTfCompTree::NodeIdx selectedTfNodeIdx = FlatTfCompTree::InvalidIdx;
String filterName; String filterName;
ComponentManipulationData compEditData;
std::string detailsWndName; std::string detailsWndName;
cbe::EDetailsFlags detailsFlags;
}; };

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -13,6 +13,8 @@
#include <Widgets/WgEditorConstants.hpp> #include <Widgets/WgEditorConstants.hpp>
#include <Widgets/WgViewportImGuiLayer.h> #include <Widgets/WgViewportImGuiLayer.h>
#include <Widgets/WgDetailsImGuiLayer.h> #include <Widgets/WgDetailsImGuiLayer.h>
#include <ICoreObjectsModule.h>
#include <CoreObjectGC.h>
#include <CbeApplication.h> #include <CbeApplication.h>
#include <EditorHelpers.h> #include <EditorHelpers.h>
#include <IApplicationModule.h> #include <IApplicationModule.h>
@@ -29,6 +31,9 @@
#include <AssetEditors/IEngineAssetEditor.hpp> #include <AssetEditors/IEngineAssetEditor.hpp>
#include <Widgets/EditorWidgetsHelper.hpp> #include <Widgets/EditorWidgetsHelper.hpp>
constexpr const AChar *PROGRESS_TRACKER_MODAL_POPUP = "###ProgressTracker";
static const TickRep PROGRESS_DOT_ANIM_TICKS = Time::fromSeconds(1.0f);
WgEditorImGuiLayer::WgEditorImGuiLayer(const WgEditorCreateInfo &ci) WgEditorImGuiLayer::WgEditorImGuiLayer(const WgEditorCreateInfo &ci)
: ownerRoot(ci.rootEd.get()) : ownerRoot(ci.rootEd.get())
, wnd(ci.windowWidget) , wnd(ci.windowWidget)
@@ -41,9 +46,9 @@ WgEditorImGuiLayer::WgEditorImGuiLayer(const WgEditorCreateInfo &ci)
void WgEditorImGuiLayer::drawImGui() void WgEditorImGuiLayer::drawImGui()
{ {
if (bShowDemo) if (demoData.bShowImGuiDemo)
{ {
ImGui::ShowDemoWindow(&bShowDemo); ImGui::ShowDemoWindow(&demoData.bShowImGuiDemo);
} }
if (bShowStyleEditor) if (bShowStyleEditor)
{ {
@@ -51,6 +56,23 @@ void WgEditorImGuiLayer::drawImGui()
ImGui::ShowStyleEditor(); ImGui::ShowStyleEditor();
ImGui::End(); ImGui::End();
} }
if (bShowImGuiMetricWnd)
{
ImGui::ShowMetricsWindow(&bShowImGuiMetricWnd);
}
if (bShowDebugLog)
{
ImGui::ShowDebugLogWindow(&bShowDebugLog);
}
if (bShowImGuiIdStack)
{
ImGui::ShowIDStackToolWindow(&bShowImGuiIdStack);
}
if (demoData.bShowEngineWgDemo)
{
drawEngineWidgetDemo();
}
/* Needed to manually set and reset the value of window menu button icon */ /* Needed to manually set and reset the value of window menu button icon */
ImGuiStyle &style = ImGui::GetStyle(); ImGuiStyle &style = ImGui::GetStyle();
@@ -124,24 +146,138 @@ void WgEditorImGuiLayer::removeMenuExtender(const TChar *menuName, DelegateHandl
} }
void WgEditorImGuiLayer::addNotification(const cbe::NotificationInfo &info) void WgEditorImGuiLayer::addNotification(const cbe::NotificationInfo &info)
{
const uint64 allocId = addStickyNotification(info);
notifyAlloc.getAllocAt(allocId)->bSticky = false;
}
uint64 WgEditorImGuiLayer::addStickyNotification(const cbe::NotificationInfo &info)
{ {
if (currNotifyCount >= maxNotifies) if (currNotifyCount >= maxNotifies)
{ {
/* Remove the oldest */ /* Remove the oldest */
NotifyDrawInfo *tailNotification = LinkedListHelpers::tail(notifyHead); NotifyDrawInfo *rmNotification = LinkedListHelpers::tail(notifyHead);
LinkedListHelpers::remove(&notifyHead, tailNotification, nullptr); /* Find earliest non sticky notification */
tailNotification->~NotifyDrawInfo(); rmNotification = LinkedListHelpers::rfind(
notifyAlloc.free(tailNotification); notifyHead, rmNotification,
currNotifyCount--; [](const NotifyDrawInfo &notification)
{
return !notification.bSticky;
}
);
if (rmNotification != nullptr)
{
remNotification(rmNotification);
}
} }
NotifyDrawInfo *notification = new (notifyAlloc.getAllocAt(notifyAlloc.allocate())) NotifyDrawInfo{ const uint64 allocIdx = notifyAlloc.allocate<uint64>();
NotifyDrawInfo *notification = new (notifyAlloc.getAllocAt(allocIdx)) NotifyDrawInfo{
.name = std::string{ TCHAR_TO_UTF8(info.name) }, .name = std::string{ TCHAR_TO_UTF8(info.name) },
.endAt = Time::fromSeconds(info.durrInSeconds) + Time::timeNow(), .endAt = Time::fromSeconds(info.durrInSeconds) + Time::timeNow(),
.cb = std::move(info.callback), .cb = std::move(info.callback),
.bSticky = true,
}; };
LinkedListHelpers::pushHead(&notifyHead, notification); LinkedListHelpers::pushHead(&notifyHead, notification);
currNotifyCount++; currNotifyCount++;
return allocIdx;
}
void WgEditorImGuiLayer::removeStickyNotification(uint64 stickyId)
{
if (!notifyAlloc.isValid(stickyId))
{
return;
}
NotifyDrawInfo *notification = notifyAlloc.getAllocAt(stickyId);
remNotification(notification);
}
cbe::ProgressTrackerHnd WgEditorImGuiLayer::addProgressTracker(const cbe::ProgressTrackerInfo &info)
{
const cbe::ProgressTrackerHnd hnd = progTrackerAlloc.allocate<cbe::ProgressTrackerHnd>();
ProgressDrawInfo &progTracker = *new (progTrackerAlloc.getAllocAt(hnd)) ProgressDrawInfo{
.maxSteps = info.stepsCount,
.stepDesc = "",
.jobName = std::string{ TCHAR_TO_UTF8(info.name) },
};
if (info.stepsCount == 0)
{
progTracker.maxSteps = std::numeric_limits<uint16>::max();
progTracker.bContinuous = true;
}
if (info.bBackground)
{
cbe::NotificationInfo notifyInfo{ .name = info.name };
notifyInfo.callback.bindObject(this, &WgEditorImGuiLayer::drawBackgroundProgressTracker, hnd);
progTracker.stickyId = addStickyNotification(notifyInfo);
}
LinkedListHelpers::pushHead(&progTrackerHead, &progTracker);
return hnd;
}
void WgEditorImGuiLayer::progressProgressTracker(cbe::ProgressTrackerHnd hnd, uint32 stepsCount, StringView stepDesc)
{
if (!progTrackerAlloc.isValid(hnd))
{
return;
}
ProgressDrawInfo &progTracker = *progTrackerAlloc.getAllocAt(hnd);
if (!stepDesc.empty())
{
progTracker.stepDesc = TCHAR_TO_UTF8(stepDesc);
}
debugAssertf(
progTracker.maxSteps >= (progTracker.currProgress.load(std::memory_order::relaxed) + stepsCount),
"Progress steps cannot go beyond max steps!"
);
progTracker.currProgress.fetch_add(stepsCount, std::memory_order::release);
}
void WgEditorImGuiLayer::setProgressTracerProgress(cbe::ProgressTrackerHnd hnd, float progress)
{
if (!progTrackerAlloc.isValid(hnd))
{
return;
}
ProgressDrawInfo &progTracker = *progTrackerAlloc.getAllocAt(hnd);
debugAssertf(progTracker.maxSteps == std::numeric_limits<uint16>::max(), "Continuous progress must be quantized to 16bit");
constexpr float progressScale = static_cast<float>(std::numeric_limits<uint16>::max() / 100000.0);
debugAssertf(progress >= 0.0f && progress <= 1.0f, "Progress must be between 0.0 to 1.0");
const uint32 quantProgress = static_cast<uint32>((progressScale * progress) * 100000.0);
if (quantProgress > progTracker.currProgress.load(std::memory_order::relaxed))
{
progTracker.currProgress.store(quantProgress, std::memory_order::release);
}
}
bool WgEditorImGuiLayer::isProgressTrackerCancelling(cbe::ProgressTrackerHnd hnd) const
{
if (!progTrackerAlloc.isValid(hnd))
{
/* Can never cancel */
return false;
}
return progTrackerAlloc.getAllocAt(hnd)->bCancelReq;
}
void WgEditorImGuiLayer::cancelProgressTracker(cbe::ProgressTrackerHnd hnd)
{
if (!progTrackerAlloc.isValid(hnd))
{
return;
}
ProgressDrawInfo &progTracker = *progTrackerAlloc.getAllocAt(hnd);
/* Immediately set the current progress to max and let next frame clear. */
progTracker.currProgress.store(progTracker.maxSteps, std::memory_order::relaxed);
} }
void WgEditorImGuiLayer::addAssetEditor(const cbe::ICoreAssetEditorRef &assetEditor) void WgEditorImGuiLayer::addAssetEditor(const cbe::ICoreAssetEditorRef &assetEditor)
@@ -211,7 +347,239 @@ cbe::ETryExit WgEditorImGuiLayer::tryExitEditor()
} }
assetEdDat.closeState = cbe::DestroyState_Requested; assetEdDat.closeState = cbe::DestroyState_Requested;
} }
return assetEditors.empty() ? cbe::TryExit_Success : cbe::TryExit_Wait; if (!assetEditors.empty())
{
return cbe::TryExit_Wait;
}
/* Wait for any progress trackers */
return progTrackerHead != nullptr ? cbe::TryExit_Wait : cbe::TryExit_Success;
}
void WgEditorImGuiLayer::drawEngineWidgetDemo()
{
ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Appearing);
ImGui::SetNextWindowSizeConstraints(cbe::wg_consts::VMODAL_POPUP_SIZE, Vector2{ std::numeric_limits<float>::max() });
if (ImGui::Begin("Engine Widget Demo", &demoData.bShowEngineWgDemo))
{
if (ImGui::TreeNode("Notification Popup"))
{
ImGui::PushID("Basics");
ImGui::SeparatorText("Basic");
cbe::NotificationInfo info = {};
bool bIssueNotification = false;
bool bStickyNotification = false;
if (ImGui::Button("1s"))
{
bIssueNotification = true;
info.durrInSeconds = 1;
info.name = TCHAR("Demo Notification 1s");
}
ImGui::SetItemTooltip("Issues notification with 1 second lifetime");
ImGui::SameLine();
if (ImGui::Button("2s"))
{
bIssueNotification = true;
info.durrInSeconds = 2;
info.name = TCHAR("Demo Notification 2s");
}
ImGui::SetItemTooltip("Issues notification with 2 seconds lifetime");
ImGui::SameLine();
if (ImGui::Button("5s"))
{
bIssueNotification = true;
info.durrInSeconds = 5;
info.name = TCHAR("Demo Notification 5s");
}
ImGui::SetItemTooltip("Issues notification with 5 seconds lifetime");
ImGui::SameLine();
if (ImGui::Button("sticky"))
{
info.name = TCHAR("Demo Notification Sticky");
bIssueNotification = true;
bStickyNotification = true;
}
ImGui::SetItemTooltip("Issues sticky notification");
if (bIssueNotification)
{
if (bStickyNotification)
{
addStickyNotification(info);
}
else
{
addNotification(info);
}
bIssueNotification = false;
bStickyNotification = false;
info = {};
}
ImGui::PopID();
ImGui::PushID("With Callback");
ImGui::SeparatorText("With Callback");
auto cb = []
{
// Animate a simple progress bar
static float progress = 0.0f, progress_dir = 1.0f;
progress += progress_dir * 0.4f * ImGui::GetIO().DeltaTime;
if (progress >= +1.1f)
{
progress = +1.1f;
progress_dir *= -1.0f;
}
if (progress <= -0.1f)
{
progress = -0.1f;
progress_dir *= -1.0f;
}
// Typically we would use ImVec2(-1.0f,0.0f) or ImVec2(-FLT_MIN,0.0f) to use all available width,
// or ImVec2(width,0.0f) for a specified width. ImVec2(0.0f,0.0f) uses ItemWidth.
ImGui::ProgressBar(progress, ImVec2(0.0f, 0.0f));
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Text("Progress Bar");
float progress_saturated = Math::clamp(progress, 0.0f, 1.0f);
char buf[32];
sprintf_s(buf, 32, "%d/%d", (int)(progress_saturated * 1753), 1753);
ImGui::ProgressBar(progress, ImVec2(0.f, 0.f), buf);
// Pass an animated negative value, e.g. -1.0f * (float)ImGui::GetTime() is the recommended value.
// Adjust the factor if you want to adjust the animation speed.
ImGui::ProgressBar(-1.0f * (float)ImGui::GetTime(), ImVec2(0.0f, 0.0f), "Searching..");
ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x);
ImGui::Text("Indeterminate");
};
info.callback.bindLambda(cb);
if (ImGui::Button("5s"))
{
bIssueNotification = true;
info.durrInSeconds = 5;
info.name = TCHAR("Demo Notification 5s");
}
ImGui::SetItemTooltip("Issues notification with 5 seconds lifetime");
ImGui::SameLine();
if (ImGui::Button("sticky"))
{
info.name = TCHAR("Demo Notification Sticky");
bIssueNotification = true;
bStickyNotification = true;
}
ImGui::SetItemTooltip("Issues sticky notification");
if (bIssueNotification)
{
if (bStickyNotification)
{
addStickyNotification(info);
}
else
{
addNotification(info);
}
bIssueNotification = false;
bStickyNotification = false;
info = {};
}
ImGui::PopID();
ImGui::TreePop();
}
if (ImGui::TreeNode("Progress Tracker"))
{
ImGui::BeginDisabled(progTrackerAlloc.isValid(demoData.progHnd));
if (ImGui::Button("FG"))
{
cbe::ProgressTrackerInfo info{
.name = TCHAR("Demo FG Progress"),
.stepsCount = 300,
.bBackground = false,
};
demoData.progHnd = addProgressTracker(info);
}
ImGui::SetItemTooltip("Foreground Progress Bar");
ImGui::EndDisabled();
ImGui::BeginDisabled(progTrackerAlloc.isValid(demoData.progHndBg));
if (ImGui::Button("BG"))
{
cbe::ProgressTrackerInfo info{
.name = TCHAR("Demo BG Progress"),
.stepsCount = 300,
.bBackground = true,
};
demoData.progHndBg = addProgressTracker(info);
}
ImGui::SetItemTooltip("Background Progress Bar");
ImGui::EndDisabled();
ImGui::BeginDisabled(progTrackerAlloc.isValid(demoData.cProgHnd));
if (ImGui::Button("FG Continuous"))
{
cbe::ProgressTrackerInfo info{
.name = TCHAR("Demo FG Continuous Progress"),
.stepsCount = 0,
.bBackground = false,
};
demoData.cProgHnd = addProgressTracker(info);
demoData.cProg = 0.0f;
}
ImGui::SetItemTooltip("Foreground Continuous Progress Bar");
ImGui::EndDisabled();
ImGui::BeginDisabled(progTrackerAlloc.isValid(demoData.cProgHndBg));
if (ImGui::Button("BG Continuous"))
{
cbe::ProgressTrackerInfo info{
.name = TCHAR("Demo BG Continuous Progress"),
.stepsCount = 0,
.bBackground = true,
};
demoData.cProgHndBg = addProgressTracker(info);
demoData.cProgBg = 0.0f;
}
ImGui::SetItemTooltip("Background Continuous Progress Bar");
ImGui::EndDisabled();
ImGui::TreePop();
}
}
ImGui::End();
if (progTrackerAlloc.isValid(demoData.progHnd))
{
progressProgressTracker(demoData.progHnd, 1, TCHAR("Demo FG Step"));
}
if (progTrackerAlloc.isValid(demoData.progHndBg))
{
progressProgressTracker(demoData.progHndBg, 1, TCHAR("Demo BG Step"));
}
if (progTrackerAlloc.isValid(demoData.cProgHnd))
{
demoData.cProg += 0.01f;
demoData.cProg = Math::clamp(demoData.cProg, 0.0f, 1.0f);
setProgressTracerProgress(demoData.cProgHnd, demoData.cProg);
}
if (progTrackerAlloc.isValid(demoData.cProgHndBg))
{
demoData.cProgBg += 0.01f;
demoData.cProgBg = Math::clamp(demoData.cProgBg, 0.0f, 1.0f);
setProgressTracerProgress(demoData.cProgHndBg, demoData.cProgBg);
}
} }
void WgEditorImGuiLayer::drawEditorWindows() void WgEditorImGuiLayer::drawEditorWindows()
@@ -242,12 +610,11 @@ void WgEditorImGuiLayer::drawEditorWindows()
ImGui::SetNextWindowDockID(ED_DOCK_SPACE); ImGui::SetNextWindowDockID(ED_DOCK_SPACE);
cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld(); cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld();
const cbe::World *editorWorld = gCBEEngine->worldManager()->getEditorWorld();
bool bWorldOpen = cbe::isValidFast(mainWorld); bool bWorldOpen = cbe::isValidFast(mainWorld);
const std::string windowTitle const std::string windowTitle
= std::format("{}{}", bWorldOpen ? TCHAR_TO_UTF8(mainWorld->getObjectData().name) : "None", cbe::wg_consts::ED_MAIN_WINDOW); = std::format("{}{}", bWorldOpen ? TCHAR_TO_UTF8(mainWorld->getObjectData().name) : "None", cbe::wg_consts::ED_MAIN_WINDOW);
ImGuiWindowFlags edWindowFlags = edWndBaseFlags | ImGuiWindowFlags_MenuBar; ImGuiWindowFlags edWindowFlags = edWndBaseFlags | ImGuiWindowFlags_MenuBar;
if (cbe::isValidFast(editorWorld) && cbe::isPackageDirty(editorWorld)) if (cbe::gCBEditorEngine->getMainEd()->isAssetDirty())
{ {
edWindowFlags |= ImGuiWindowFlags_UnsavedDocument; edWindowFlags |= ImGuiWindowFlags_UnsavedDocument;
} }
@@ -303,6 +670,8 @@ void WgEditorImGuiLayer::drawEditorWindows()
ImGui::PopStyleColor(1); ImGui::PopStyleColor(1);
addMenubar(); addMenubar();
drawProgressTrackers();
drawNotifications(); drawNotifications();
/* Handle shortcuts. Do not handle if pop up is focused. */ /* Handle shortcuts. Do not handle if pop up is focused. */
@@ -447,6 +816,7 @@ void WgEditorImGuiLayer::drawEditorWindows()
/* Only one window must be visible at any time. So draw notification inside it. */ /* Only one window must be visible at any time. So draw notification inside it. */
if (ImGui::GetWindowDockID() == ED_DOCK_SPACE) if (ImGui::GetWindowDockID() == ED_DOCK_SPACE)
{ {
drawProgressTrackers();
drawNotifications(); drawNotifications();
} }
@@ -504,8 +874,27 @@ void WgEditorImGuiLayer::addMenubar()
if (ImGui::BeginMenu("Developer")) if (ImGui::BeginMenu("Developer"))
{ {
ImGui::MenuItem("CoPaT stats", nullptr, &bShowJobQueueStats); ImGui::MenuItem("CoPaT stats", nullptr, &bShowJobQueueStats);
ImGui::MenuItem("ImGUI demo", nullptr, &bShowDemo); ImGui::MenuItem("ImGui Metrics", nullptr, &bShowImGuiMetricWnd);
ImGui::SeparatorText("ImGui");
if (ImGui::MenuItem("Item Picker", NULL, false, PlatformFunctions::hasAttachedDebugger()))
{
ImGui::DebugStartItemPicker();
}
ImGui::MenuItem("ID Stack", nullptr, &bShowImGuiIdStack);
ImGui::MenuItem("Style Editor", nullptr, &bShowStyleEditor); ImGui::MenuItem("Style Editor", nullptr, &bShowStyleEditor);
ImGui::MenuItem("Debug Log", nullptr, &bShowDebugLog);
if (ImGui::BeginMenu("Test UI"))
{
ImGui::MenuItem("ImGUI demo", nullptr, &demoData.bShowImGuiDemo);
ImGui::MenuItem("Engine widget demo", nullptr, &demoData.bShowEngineWgDemo);
ImGui::EndMenu();
}
ImGui::EndMenu(); ImGui::EndMenu();
} }
@@ -626,8 +1015,13 @@ void WgEditorImGuiLayer::undo()
} }
if (!editedObjs.empty()) if (!editedObjs.empty())
{ {
std::vector<cbe::WeakObjectPtr> weakObjPtrs{ editedObjs.size() };
for (SizeT i = 0; i < editedObjs.size(); ++i)
{
weakObjPtrs[i] = editedObjs[i];
}
ownerRoot->pushMessage( ownerRoot->pushMessage(
cbe::MessageData{ .issuerData = std::bit_cast<uint64>(this), .msg = std::move(editedObjs) }, cbe::CoreEdMessage_ObjsEdited cbe::MessageData{ .issuerData = std::bit_cast<uint64>(this), .msg = std::move(weakObjPtrs) }, cbe::CoreEdMessage_ObjsEdited
); );
} }
@@ -644,8 +1038,13 @@ void WgEditorImGuiLayer::redo()
if (!editedObjs.empty()) if (!editedObjs.empty())
{ {
std::vector<cbe::WeakObjectPtr> weakObjPtrs{ editedObjs.size() };
for (SizeT i = 0; i < editedObjs.size(); ++i)
{
weakObjPtrs[i] = editedObjs[i];
}
ownerRoot->pushMessage( ownerRoot->pushMessage(
cbe::MessageData{ .issuerData = std::bit_cast<uint64>(this), .msg = std::move(editedObjs) }, cbe::CoreEdMessage_ObjsEdited cbe::MessageData{ .issuerData = std::bit_cast<uint64>(this), .msg = std::move(weakObjPtrs) }, cbe::CoreEdMessage_ObjsEdited
); );
} }
@@ -678,8 +1077,7 @@ void WgEditorImGuiLayer::drawNotifications()
} }
const ImGuiStyle &style = ImGui::GetStyle(); const ImGuiStyle &style = ImGui::GetStyle();
ImGuiViewport *viewport = ImGui::GetWindowViewport() != nullptr ? ImGui::GetWindowViewport() : ImGui::GetMainViewport();
ImGuiViewport *viewport = ImGui::GetMainViewport();
const Vector2 notificationSpacing{ ImGui::GetFontSize(), ImGui::GetTextLineHeight() }; const Vector2 notificationSpacing{ ImGui::GetFontSize(), ImGui::GetTextLineHeight() };
@@ -699,8 +1097,20 @@ void WgEditorImGuiLayer::drawNotifications()
uint32 idx = 0; uint32 idx = 0;
while (notification != nullptr) while (notification != nullptr)
{ {
if (notification->cb)
{
/* Custom callback must be able to resize in Y */
ImGui::SetNextWindowSize(ImVec2(notifyWindowSize.x, 0.0f), ImGuiCond_Always);
/* Additional offset necessary to avoid overlapping notification below. Delta between ideal size and generated size */
nextNotifyPos.y -= (notification->wndHeight - notifyWindowSize.y);
}
else
{
ImGui::SetNextWindowSize(notifyWindowSize, ImGuiCond_Always);
}
ImGui::SetNextWindowPos(nextNotifyPos, ImGuiCond_Always); ImGui::SetNextWindowPos(nextNotifyPos, ImGuiCond_Always);
ImGui::SetNextWindowSize(notifyWindowSize, ImGuiCond_Always); ImGui::SetNextWindowSizeConstraints(notifyWindowSize, Vector2{ std::numeric_limits<float>::max() });
ImGui::SetNextWindowBgAlpha(0.8f); ImGui::SetNextWindowBgAlpha(0.8f);
ImGui::Begin( ImGui::Begin(
@@ -708,6 +1118,8 @@ void WgEditorImGuiLayer::drawNotifications()
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove
); );
ImGui::TextUnformatted(notification->name.c_str(), notification->name.c_str() + notification->name.length()); ImGui::TextUnformatted(notification->name.c_str(), notification->name.c_str() + notification->name.length());
ImGui::SetItemTooltip(notification->name.c_str());
ImGui::SameLine(notifyWindowSize.x - cbe::ImGuiHelpers::calcButtonSize("x").x - style.FramePadding.x); ImGui::SameLine(notifyWindowSize.x - cbe::ImGuiHelpers::calcButtonSize("x").x - style.FramePadding.x);
if (cbe::ImGuiHelpers::buttonNoBg("x")) if (cbe::ImGuiHelpers::buttonNoBg("x"))
{ {
@@ -718,27 +1130,209 @@ void WgEditorImGuiLayer::drawNotifications()
{ {
notification->cb.invoke(); notification->cb.invoke();
} }
notification->wndHeight = ImGui::GetWindowSize().y;
ImGui::End(); ImGui::End();
notification = LinkedListHelpers::next(notification);
idx++; idx++;
nextNotifyPos.y -= (notifyWindowSize.y + notificationSpacing.y); nextNotifyPos.y -= (notifyWindowSize.y + notificationSpacing.y);
notification = LinkedListHelpers::next(notification);
} }
auto remNotification = [this](NotifyDrawInfo *notificationToRemove)
{
LinkedListHelpers::remove(&notifyHead, notificationToRemove, nullptr);
notificationToRemove->~NotifyDrawInfo();
notifyAlloc.free(notificationToRemove);
currNotifyCount--;
};
if (notificationToRemove != nullptr) if (notificationToRemove != nullptr)
{ {
remNotification(notificationToRemove); remNotification(notificationToRemove);
} }
/* Skip to first non sticky notification from back. */
NotifyDrawInfo *tailNotification = LinkedListHelpers::tail(notifyHead); NotifyDrawInfo *tailNotification = LinkedListHelpers::tail(notifyHead);
tailNotification = LinkedListHelpers::rfind(
notifyHead, tailNotification,
[](const NotifyDrawInfo &notification)
{
return !notification.bSticky;
}
);
if (tailNotification != nullptr && tailNotification->endAt <= Time::timeNow()) if (tailNotification != nullptr && tailNotification->endAt <= Time::timeNow())
{ {
remNotification(tailNotification); remNotification(tailNotification);
} }
} }
void WgEditorImGuiLayer::remNotification(NotifyDrawInfo *rmNotification)
{
LinkedListHelpers::remove(&notifyHead, rmNotification, nullptr);
rmNotification->~NotifyDrawInfo();
notifyAlloc.free(rmNotification);
currNotifyCount--;
}
void WgEditorImGuiLayer::drawProgressTrackers()
{
/* Clear the progress trackers that reached the end first. */
ProgressDrawInfo *progTracker = progTrackerHead;
while (progTracker != nullptr)
{
ProgressDrawInfo *nextTracker = LinkedListHelpers::next(progTracker);
if (progTracker->currProgress.load(std::memory_order::relaxed) < progTracker->maxSteps)
{
progTracker = nextTracker;
continue;
}
/* Remove the progTracker from list and erase it from allocator */
if (progTracker->stickyId != 0)
{
removeStickyNotification(progTracker->stickyId);
}
LinkedListHelpers::remove(&progTrackerHead, progTracker, nullptr);
progTracker->~ProgressDrawInfo();
progTrackerAlloc.free(progTracker);
progTracker = nextTracker;
}
progTracker = LinkedListHelpers::find(
progTrackerHead,
[](const ProgressDrawInfo &progTracker)
{
return progTracker.stickyId == 0;
},
nullptr
);
if (progTracker == nullptr)
{
return;
}
if (!ImGui::IsPopupOpen(PROGRESS_TRACKER_MODAL_POPUP))
{
ImGui::OpenPopup(PROGRESS_TRACKER_MODAL_POPUP);
ImGuiViewport *viewport = ImGui::GetWindowViewport() != nullptr ? ImGui::GetWindowViewport() : ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->GetCenter(), ImGuiCond_Appearing, Vector2(0.5f));
}
/* To allow matching content size always */
ImGui::SetNextWindowSize(ImVec2{}, ImGuiCond_Always);
ImGui::SetNextWindowSizeConstraints(ImVec2{ cbe::wg_consts::HMODAL_POPUP_SIZE.x, 0.0f }, Vector2{ std::numeric_limits<float>::max() });
if (!ImGui::BeginPopupModal(PROGRESS_TRACKER_MODAL_POPUP, nullptr, ImGuiWindowFlags_NoDecoration))
{
return;
}
auto drawProg = [](ProgressDrawInfo *progTracker)
{
const uint32 dotCount = static_cast<uint32>(
(Time::timeNow() / static_cast<TickRep>(PROGRESS_DOT_ANIM_TICKS * cbe::wg_consts::ANIMATION_TIME_SCALE)) % 4u
);
ImGui::Text("%s%.*s", progTracker->jobName.c_str(), dotCount, "...");
const uint32 currProgress = progTracker->currProgress.load(std::memory_order::relaxed);
const float progress = static_cast<float>(currProgress) / progTracker->maxSteps;
if (!progTracker->bContinuous)
{
ImGui::Text("%u/%u %s", currProgress, progTracker->maxSteps, progTracker->stepDesc.c_str());
}
const Vector2 buttonSize = cbe::ImGuiHelpers::calcButtonSize(cbe::mat_icon::CANCEL);
const float progressSize = ImGui::GetContentRegionAvail().x - buttonSize.x;
const float cursorPosY = ImGui::GetCursorPosY();
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, cbe::style::ED_COLOR_PRIMARY);
if (progTracker->bContinuous)
{
ImGui::ProgressBar(progress, ImVec2(progressSize, 0.0f));
}
else
{
constexpr float PROGRESS_HEIGHT = 4.0f;
/* Vertically align the progress bar. Reset after drawing */
ImGui::SetCursorPosY(cursorPosY + ((buttonSize.y - PROGRESS_HEIGHT) * 0.5f));
ImGui::ProgressBar(progress, ImVec2(progressSize, PROGRESS_HEIGHT), "");
ImGui::SetCursorPosY(cursorPosY);
}
ImGui::PopStyleColor(1);
/* No spacing since button has padding and it is transparent */
ImGui::SameLine(0.0f, 0.0f);
/* Must set after SameLine since same line restores the Cursor Y from prev line. */
ImGui::SetCursorPosY(cursorPosY);
ImGui::BeginDisabled(progTracker->bCancelReq);
ImGui::PushStyleColor(ImGuiCol_Text, LinearColorConst::RED);
if (cbe::ImGuiHelpers::buttonNoBg(cbe::mat_icon::CANCEL))
{
progTracker->bCancelReq = true;
}
ImGui::PopStyleColor(1);
ImGui::SetItemTooltip("Try cancel this task!");
ImGui::EndDisabled();
};
drawProg(progTracker);
progTracker = LinkedListHelpers::next(progTracker);
while (progTracker != nullptr)
{
/* Only draw non background progress tracker here. */
if (progTracker->stickyId == 0)
{
ImGui::Separator();
drawProg(progTracker);
}
progTracker = LinkedListHelpers::next(progTracker);
}
ImGui::EndPopup();
}
void WgEditorImGuiLayer::drawBackgroundProgressTracker(cbe::ProgressTrackerHnd hnd) const
{
if (!progTrackerAlloc.isValid(hnd))
{
return;
}
ProgressDrawInfo *progTracker = progTrackerAlloc.getAllocAt(hnd);
const uint32 currProgress = progTracker->currProgress.load(std::memory_order::relaxed);
const float progress = static_cast<float>(currProgress) / progTracker->maxSteps;
const Vector2 buttonSize = cbe::ImGuiHelpers::calcButtonSize(cbe::mat_icon::CANCEL);
const float progressSize = ImGui::GetContentRegionAvail().x - buttonSize.x;
constexpr float PROGRESS_HEIGHT = 2.0f;
const float cursorPosY = ImGui::GetCursorPosY();
/* Vertically align the progress bar. Reset after drawing */
ImGui::SetCursorPosY(cursorPosY + ((buttonSize.y - PROGRESS_HEIGHT) * 0.5f));
ImGui::PushStyleColor(ImGuiCol_PlotHistogram, cbe::style::ED_COLOR_PRIMARY);
ImGui::ProgressBar(progress, ImVec2(progressSize, PROGRESS_HEIGHT), "");
ImGui::PopStyleColor(1);
ImGui::SetCursorPosY(cursorPosY);
if (progTracker->bContinuous)
{
ImGui::SetItemTooltip("Progress %.0f%%", progress);
}
else
{
ImGui::SetItemTooltip("%u/%u %s", currProgress, progTracker->maxSteps, progTracker->stepDesc.c_str());
}
/* No spacing since button has padding and it is transparent */
ImGui::SameLine(0.0f, 0.0f);
/* Must set after SameLine since same line restores the Cursor Y from prev line. */
ImGui::SetCursorPosY(cursorPosY);
ImGui::BeginDisabled(progTracker->bCancelReq);
ImGui::PushStyleColor(ImGuiCol_Text, LinearColorConst::RED);
if (cbe::ImGuiHelpers::buttonNoBg(cbe::mat_icon::CANCEL))
{
progTracker->bCancelReq = true;
}
ImGui::PopStyleColor(1);
ImGui::SetItemTooltip("Try cancel this task!");
ImGui::EndDisabled();
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -38,6 +38,19 @@ struct NotificationInfo
/* Optional */ /* Optional */
NotificationCb callback; NotificationCb callback;
}; };
using ProgressTrackerHnd = uint64;
struct ProgressTrackerInfo
{
String name;
/* If zero assumes continuous, uses 16bit/65536 values to represent continuous data. */
uint32 stepsCount = 0;
/* If true the task will be displayed as sticky notification. */
bool bBackground = false;
};
} // namespace cbe } // namespace cbe
class WgEditorImGuiLayer : public IImGuiLayer class WgEditorImGuiLayer : public IImGuiLayer
@@ -60,6 +73,34 @@ public:
void removeMenuExtender(const TChar *menuName, DelegateHandle handle); void removeMenuExtender(const TChar *menuName, DelegateHandle handle);
void addNotification(const cbe::NotificationInfo &info); void addNotification(const cbe::NotificationInfo &info);
/**
* @brief Notifications that sticks until explicitly removed by Sticky ID.
* @param info Notification info.
*/
uint64 addStickyNotification(const cbe::NotificationInfo &info);
void removeStickyNotification(uint64 stickyId);
/**
* @brief Adds a new progress tracker and returns tracker handle that can used to update progress.
* @param info Progress tracker information.
* @return Progress tracker handle.
*/
cbe::ProgressTrackerHnd addProgressTracker(const cbe::ProgressTrackerInfo &info);
/**
* @brief Progresses the tracker by provided steps count and updates the next step with new description.
* @param hnd Progress tracker handle.
* @param stepsCount Number of steps to progress.
* @param stepDesc If empty previous description is used.
*/
void progressProgressTracker(cbe::ProgressTrackerHnd hnd, uint32 stepsCount, StringView stepDesc);
/**
* @brief Sets the continuous progress.
* @param hnd Progress tracker handle.
* @param progress Progress value between 0.0 - 1.0.
*/
void setProgressTracerProgress(cbe::ProgressTrackerHnd hnd, float progress);
bool isProgressTrackerCancelling(cbe::ProgressTrackerHnd hnd) const;
void cancelProgressTracker(cbe::ProgressTrackerHnd hnd);
void addAssetEditor(const cbe::ICoreAssetEditorRef &assetEditor); void addAssetEditor(const cbe::ICoreAssetEditorRef &assetEditor);
/* Returns true if removed, false means remove cannot be done now. /* Returns true if removed, false means remove cannot be done now.
@@ -68,18 +109,6 @@ public:
void tickAssetEditors(float deltaTime); void tickAssetEditors(float deltaTime);
cbe::ETryExit tryExitEditor(); cbe::ETryExit tryExitEditor();
private:
void drawEditorWindows();
void addMenubar();
void aboutWindow();
void jobSystemJobsStats();
void handleShortcuts();
void drawNotifications();
void undo();
void redo();
private: private:
std::unordered_map<std::string, ImGuiDrawInterfaceCallback> menuExtenders; std::unordered_map<std::string, ImGuiDrawInterfaceCallback> menuExtenders;
@@ -99,6 +128,10 @@ private:
WindowingManager *wndManager; WindowingManager *wndManager;
Color wndFrameColor; Color wndFrameColor;
//////////////////////////////////////////////////////////////////////////
// Notifications
//////////////////////////////////////////////////////////////////////////
/** /**
* Notification gets drawn only in scene window. * Notification gets drawn only in scene window.
* This is because that is the only window that can never be undocked and moved. * This is because that is the only window that can never be undocked and moved.
@@ -109,21 +142,87 @@ private:
std::string name; std::string name;
TickRep endAt; TickRep endAt;
cbe::NotificationCb cb; cbe::NotificationCb cb;
/* Necessary to offset self to avoid overlapping with other notifications below. */
float wndHeight = 0;
bool bSticky:1 = false;
NotifyDrawInfo *next = nullptr; NotifyDrawInfo *next = nullptr;
NotifyDrawInfo *prev = nullptr; NotifyDrawInfo *previous = nullptr;
}; };
constexpr static const uint32 NOTIFICATION_LINES_COUNT = 2; constexpr static const uint32 NOTIFICATION_LINES_COUNT = 2;
constexpr static const uint32 NOTIFICATION_LINE_CHAR_COUNT = 20; constexpr static const uint32 NOTIFICATION_LINE_CHAR_COUNT = 20;
constexpr static const uint32 NOTIFICATION_POOL_COUNT = 16;
/* Keeping as pool allocator to allow resizing if window size changes. */ /* Keeping as pool allocator to allow resizing if window size changes. */
PoolAllocator<NotifyDrawInfo, 16> notifyAlloc; PoolAllocator<NotifyDrawInfo, NOTIFICATION_POOL_COUNT> notifyAlloc;
uint32 maxNotifies = 16; uint32 maxNotifies = NOTIFICATION_POOL_COUNT;
uint32 currNotifyCount = 0; uint32 currNotifyCount = 0;
NotifyDrawInfo *notifyHead = nullptr; NotifyDrawInfo *notifyHead = nullptr;
bool bShowDemo = false; //////////////////////////////////////////////////////////////////////////
// Progress trackers
//////////////////////////////////////////////////////////////////////////
struct ProgressDrawInfo
{
std::atomic_uint32_t currProgress;
uint32 maxSteps;
std::string stepDesc;
std::string jobName;
/* Progress that gets drawn as part of notification widgets. ID to sticky notification. */
uint64 stickyId = 0;
/* Becomes true when cancel is requested */
bool bCancelReq = false;
/* If the progress is continuous */
bool bContinuous:1 = false;
ProgressDrawInfo *next = nullptr;
ProgressDrawInfo *previous = nullptr;
};
constexpr static const uint32 PROG_TRACKER_POOL_COUNT = 16;
PoolAllocator<ProgressDrawInfo, PROG_TRACKER_POOL_COUNT> progTrackerAlloc;
ProgressDrawInfo *progTrackerHead = nullptr;
bool bShowStyleEditor = false; bool bShowStyleEditor = false;
bool bShowImGuiMetricWnd = false;
bool bShowDebugLog = false;
bool bShowImGuiIdStack = false;
bool bShowAbout = false; bool bShowAbout = false;
bool bShowJobQueueStats = false; bool bShowJobQueueStats = false;
struct DemoWindowData
{
bool bShowImGuiDemo = false;
bool bShowEngineWgDemo = false;
cbe::ProgressTrackerHnd progHnd = 0;
cbe::ProgressTrackerHnd progHndBg = 0;
/* Continuous progress handles */
cbe::ProgressTrackerHnd cProgHnd = 0;
float cProg = 0.0f;
cbe::ProgressTrackerHnd cProgHndBg = 0;
float cProgBg = 0.0f;
} demoData;
private:
void drawEngineWidgetDemo();
void drawEditorWindows();
void addMenubar();
void aboutWindow();
void jobSystemJobsStats();
void handleShortcuts();
void drawNotifications();
void remNotification(NotifyDrawInfo *rmNotification);
void drawProgressTrackers();
void drawBackgroundProgressTracker(cbe::ProgressTrackerHnd hnd) const;
void undo();
void redo();
}; };

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -114,6 +114,9 @@ void WgViewportImGuiLayer::drawImGui()
ImGui::PopStyleVar(2); ImGui::PopStyleVar(2);
if (window) if (window)
{ {
/* Pump any current frame messages. This ensure any queued messages gets processed before core logic. */
ownerRoot->pumpMessages();
cbe::TransactionsLedger &ledger = getTransactionsLedger(); cbe::TransactionsLedger &ledger = getTransactionsLedger();
ImGuiWindow *currWindow = ImGui::GetCurrentWindow(); ImGuiWindow *currWindow = ImGui::GetCurrentWindow();
@@ -234,7 +237,7 @@ void WgViewportImGuiLayer::drawImGui()
/* Clear selection if Esc is pressed */ /* Clear selection if Esc is pressed */
if (ImGui::IsKeyReleased(ImGuiKey::ImGuiKey_Escape)) if (ImGui::IsKeyReleased(ImGuiKey::ImGuiKey_Escape))
{ {
LOG("LogTemp", "Cleared selections!"); CBE_LOG("LogTemp", "Cleared selections!");
worldViewport.clearSelections(); worldViewport.clearSelections();
ownerRoot->pushMessage(cbe::CoreEdMessage_SelectionChanged); ownerRoot->pushMessage(cbe::CoreEdMessage_SelectionChanged);
} }
@@ -396,7 +399,7 @@ void WgViewportImGuiLayer::drawViewportToolBar()
bPlaying = true; bPlaying = true;
} }
ImGui::BeginDisabled(!ownerRoot->isAssetDirt() || bPlaying); ImGui::BeginDisabled(!ownerRoot->isAssetDirty() || bPlaying);
if (bDrawingMainEd) if (bDrawingMainEd)
{ {
if (ImGui::Button(cbe::mat_icon::SAVE)) if (ImGui::Button(cbe::mat_icon::SAVE))
@@ -1413,6 +1416,12 @@ bool WorldViewportTools::drawTfGizmo(bool bCanHandleInputs, Short2 frameSize, Wo
return false; return false;
} }
/* If sub selection is active but there is nothing to transform do not draw the transform gizmo */
if (!selComps.empty() && (selTfComps.size() + selTfProxies.size()) == 0)
{
return false;
}
/* Handle all the inputs first */ /* Handle all the inputs first */
TfGizmoProxy *tfSelProxy = nullptr; TfGizmoProxy *tfSelProxy = nullptr;
if (hoverSelProxy && (hoverSelProxy->isType<TfGizmoScaleOrMove>() || hoverSelProxy->isType<TfGizmoRotate>())) if (hoverSelProxy && (hoverSelProxy->isType<TfGizmoScaleOrMove>() || hoverSelProxy->isType<TfGizmoRotate>()))
@@ -1957,6 +1966,15 @@ bool WorldViewportTools::drawTfGizmo(bool bCanHandleInputs, Short2 frameSize, Wo
void WorldViewportTools::TfGizmoScaleOrMove::setupReference() void WorldViewportTools::TfGizmoScaleOrMove::setupReference()
{ {
if (bScale)
{
viewportTools->gizmoRefValue = viewportTools->gizmoTf.getScale();
}
else
{
viewportTools->gizmoRefValue = viewportTools->gizmoTf.getTranslation();
}
if (!viewportTools->selTfComps.empty() || !viewportTools->selTfProxies.empty()) if (!viewportTools->selTfComps.empty() || !viewportTools->selTfProxies.empty())
{ {
if (bScale) if (bScale)
@@ -1996,8 +2014,6 @@ void WorldViewportTools::TfGizmoScaleOrMove::setupReference()
{ {
if (bScale) if (bScale)
{ {
viewportTools->gizmoRefValue = viewportTools->gizmoTf.getScale();
for (SizeT selIdx = 0; selIdx < viewportTools->selectedActors.size(); ++selIdx) for (SizeT selIdx = 0; selIdx < viewportTools->selectedActors.size(); ++selIdx)
{ {
cbe::Actor *selActor = viewportTools->selectedActors[selIdx]; cbe::Actor *selActor = viewportTools->selectedActors[selIdx];
@@ -2007,8 +2023,6 @@ void WorldViewportTools::TfGizmoScaleOrMove::setupReference()
} }
else else
{ {
viewportTools->gizmoRefValue = viewportTools->gizmoTf.getTranslation();
for (SizeT selIdx = 0; selIdx < viewportTools->selectedActors.size(); ++selIdx) for (SizeT selIdx = 0; selIdx < viewportTools->selectedActors.size(); ++selIdx)
{ {
cbe::Actor *selActor = viewportTools->selectedActors[selIdx]; cbe::Actor *selActor = viewportTools->selectedActors[selIdx];
@@ -2312,38 +2326,103 @@ void WorldViewportTools::TfGizmoScaleOrMove::transformSelection(
worldOffset = Math::sign(offsetVec | gizmoAxis) * gizmoAxis * offsetVec.size(); worldOffset = Math::sign(offsetVec | gizmoAxis) * gizmoAxis * offsetVec.size();
} }
if (bScale) /* When not scaling update the gizmo transform */
if (!bScale)
{ {
for (cbe::Actor *actor : viewportTools->selectedActors) viewportTools->gizmoTf.setTranslation(viewportTools->gizmoTf.getTranslation() + worldOffset);
{ currMouse3dPt = viewportTools->gizmoTf.getTranslation();
debugAssert(actor->getRootComponent() != nullptr); }
const Vector3 worldScale = actor->getRootComponent()->getWorldScale();
Vector3 scaleDelta = worldOffset; if (!viewportTools->selTfComps.empty() || !viewportTools->selTfProxies.empty())
const Quat relativeRot = actor->getRootComponent()->getRelativeTransform().getRotation(); {
/* For scaling the relative local rotation must be applied only when we are scaling in world space. if (bScale)
* For local space we must do the scaling in actor local space so undo the applied rotation. */ {
if (viewportTools->tfGizmoSpace == GizmoSpaceLocal) for (cbe::TransformComponent *tfComp : viewportTools->selTfComps)
{ {
scaleDelta = relativeRot.inverse().rotateVector(worldOffset); const Vector3 worldScale = tfComp->getWorldScale();
Vector3 scaleDelta = worldOffset;
const Quat relativeRot = tfComp->getRelativeTransform().getRotation();
/* For scaling the relative local rotation must be applied only when we are scaling in world space.
* For local space we must do the scaling in actor local space so undo the applied rotation. */
if (viewportTools->tfGizmoSpace == GizmoSpaceLocal)
{
scaleDelta = relativeRot.inverse().rotateVector(worldOffset);
}
else
{
scaleDelta = relativeRot.rotateVector(worldOffset);
}
tfComp->setWorldScale(worldScale + (Math::abs(scaleDelta) * scaleSign));
} }
else for (const ReferenceCountPtr<TfGizmoControllableProxy> &tfProxy : viewportTools->selTfProxies)
{ {
scaleDelta = relativeRot.rotateVector(worldOffset); Transform3D srcTf = tfProxy->getTransform();
Vector3 scaleDelta = worldOffset;
const Quat relativeRot = tfProxy->getRelativeTransform().getRotation();
/* For scaling the relative local rotation must be applied only when we are scaling in world space.
* For local space we must do the scaling in actor local space so undo the applied rotation. */
if (viewportTools->tfGizmoSpace == GizmoSpaceLocal)
{
scaleDelta = relativeRot.inverse().rotateVector(worldOffset);
}
else
{
scaleDelta = relativeRot.rotateVector(worldOffset);
}
srcTf.setScale(srcTf.getScale() + (Math::abs(scaleDelta) * scaleSign));
tfProxy->setTransform(srcTf);
}
}
else
{
for (cbe::TransformComponent *tfComp : viewportTools->selTfComps)
{
const Vector3 worldLoc = tfComp->getWorldLocation();
tfComp->setWorldLocation(worldLoc + worldOffset);
}
for (const ReferenceCountPtr<TfGizmoControllableProxy> &tfProxy : viewportTools->selTfProxies)
{
Transform3D srcTf = tfProxy->getTransform();
srcTf.setTranslation(srcTf.getTranslation() + worldOffset);
tfProxy->setTransform(srcTf);
} }
actor->getRootComponent()->setWorldScale(worldScale + (Math::abs(scaleDelta) * scaleSign));
} }
} }
else else
{ {
viewportTools->gizmoTf.setTranslation(viewportTools->gizmoTf.getTranslation() + worldOffset); if (bScale)
currMouse3dPt = viewportTools->gizmoTf.getTranslation();
for (cbe::Actor *actor : viewportTools->selectedActors)
{ {
debugAssert(actor->getRootComponent() != nullptr); for (cbe::Actor *actor : viewportTools->selectedActors)
const Vector3 worldLoc = actor->getRootComponent()->getWorldLocation(); {
actor->getRootComponent()->setWorldLocation(worldLoc + worldOffset); debugAssert(actor->getRootComponent() != nullptr);
const Vector3 worldScale = actor->getRootComponent()->getWorldScale();
Vector3 scaleDelta = worldOffset;
const Quat relativeRot = actor->getRootComponent()->getRelativeTransform().getRotation();
/* For scaling the relative local rotation must be applied only when we are scaling in world space.
* For local space we must do the scaling in actor local space so undo the applied rotation. */
if (viewportTools->tfGizmoSpace == GizmoSpaceLocal)
{
scaleDelta = relativeRot.inverse().rotateVector(worldOffset);
}
else
{
scaleDelta = relativeRot.rotateVector(worldOffset);
}
actor->getRootComponent()->setWorldScale(worldScale + (Math::abs(scaleDelta) * scaleSign));
}
}
else
{
for (cbe::Actor *actor : viewportTools->selectedActors)
{
debugAssert(actor->getRootComponent() != nullptr);
const Vector3 worldLoc = actor->getRootComponent()->getWorldLocation();
actor->getRootComponent()->setWorldLocation(worldLoc + worldOffset);
}
} }
} }
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -37,7 +37,7 @@ enum class EViewportFlags : uint8
Gizmos = 0x2, ///< Allow drawing and controlling gizmo Gizmos = 0x2, ///< Allow drawing and controlling gizmo
DragDrop = 0x4, ///< Allow dragging and dropping into viewport DragDrop = 0x4, ///< Allow dragging and dropping into viewport
NoTools = 0x8, ///< If no tools must be drawn NoTools = 0x8, ///< If no tools must be drawn
NoWorldEdit = 0x10, ///< World edit is not allowed NoWorldEdit = 0x10, ///< World edit using viewport context menu is not allowed
EnableAll = BufferVisualization | Gizmos | DragDrop, EnableAll = BufferVisualization | Gizmos | DragDrop,
}; };
MAKE_ENUM_BIT_OPS(EViewportFlags) MAKE_ENUM_BIT_OPS(EViewportFlags)
@@ -354,6 +354,11 @@ public:
const Camera &getCamera() const { return defaultCamera; } const Camera &getCamera() const { return defaultCamera; }
WorldViewport &getWorldViewport() { return worldViewport; } WorldViewport &getWorldViewport() { return worldViewport; }
void resetViewport()
{
/* Right now doing selection changed is equivalent to reset viewport drawing */
onSelectionChanged();
}
void onSelectionChanged(); void onSelectionChanged();
void void
onSubselectionChanged(ArrayView<cbe::WeakObjectPtr> selectedObjs, const std::unordered_set<cbe::WorldSelectionProxyRef> &selectedProxies); onSubselectionChanged(ArrayView<cbe::WeakObjectPtr> selectedObjs, const std::unordered_set<cbe::WorldSelectionProxyRef> &selectedProxies);

View File

@@ -4,14 +4,18 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
#include <EditorEngine.hpp> #include <EditorEngine.hpp>
#include <Types/Platform/Threading/CoPaT/CoroutineUtilities.h>
#include <Modules/ModuleManager.h>
#include <CBEAssetManager.hpp>
#include <IEditorCore.h> #include <IEditorCore.h>
#include <ICBEEditor.h> #include <ICBEEditor.h>
#include <CranberryEngineApp.h>
#include <EditorHelpers.h> #include <EditorHelpers.h>
#include <GalWgWindowRenderer.h> #include <GalWgWindowRenderer.h>
#include <Types/Platform/LFS/PlatformLFS.h> #include <Types/Platform/LFS/PlatformLFS.h>
@@ -19,7 +23,6 @@
#include <Types/Platform/LFS/PathFunctions.h> #include <Types/Platform/LFS/PathFunctions.h>
#include <Types/Platform/LFS/Paths.h> #include <Types/Platform/LFS/Paths.h>
#include <IApplicationModule.h> #include <IApplicationModule.h>
#include <CbeApplication.h>
#include <Widgets/ImGui/WgImGui.h> #include <Widgets/ImGui/WgImGui.h>
#include <Widgets/WgWindow.h> #include <Widgets/WgWindow.h>
#include <Widgets/WgEditorImGuiLayer.h> #include <Widgets/WgEditorImGuiLayer.h>
@@ -31,6 +34,7 @@
#include <Widgets/NullWidget.h> #include <Widgets/NullWidget.h>
#include <Classes/WorldsManager.hpp> #include <Classes/WorldsManager.hpp>
#include <WorldViewport.h> #include <WorldViewport.h>
#include <CoreObjectGC.h>
void tempTest(); void tempTest();
void tempTickTest(const ApplicationTimeData &timeData, const Camera &camera); void tempTickTest(const ApplicationTimeData &timeData, const Camera &camera);
@@ -58,7 +62,7 @@ EngineMainEd::EngineMainEd()
.edRoot = this, .edRoot = this,
.viewportFlags = EViewportFlags::EnableAll, .viewportFlags = EViewportFlags::EnableAll,
}); });
detailsLayer = std::make_shared<WgDetailsImGuiLayer>(WgDetailsCreateInfo{ detailsLayer = std::make_shared<WgDetailsImGuiLayer>(cbe::WgDetailsCreateInfo{
.edRoot = this, .edRoot = this,
.detailWndName = cbe::wg_consts::WND_DETAILS_ID, .detailWndName = cbe::wg_consts::WND_DETAILS_ID,
}); });
@@ -92,6 +96,9 @@ EngineMainEd::EngineMainEd()
{ {
if (bIsMain) if (bIsMain)
{ {
bIsPlayInEd = false;
bEditingAssetDirty = false;
viewportLayer->resetWorld(nullptr); viewportLayer->resetWorld(nullptr);
worldLayer->setWorldViewport(nullptr, nullptr); worldLayer->setWorldViewport(nullptr, nullptr);
detailsLayer->setWorldViewport(nullptr, nullptr); detailsLayer->setWorldViewport(nullptr, nullptr);
@@ -106,7 +113,7 @@ void EngineMainEd::saveAsset()
cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld(); cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld();
cbe::World *editWorld = gCBEEngine->worldManager()->getEditorWorld(); cbe::World *editWorld = gCBEEngine->worldManager()->getEditorWorld();
if (isAssetDirt()) if (isAssetDirty())
{ {
/* Copying and directly saving works since we use ActorPrefab for even actors spawned from classes. */ /* Copying and directly saving works since we use ActorPrefab for even actors spawned from classes. */
mainWorld->copyFrom(editWorld); mainWorld->copyFrom(editWorld);
@@ -131,27 +138,49 @@ MessageResponse EngineMainEd::sendMessage(const MessageData &msgDat, ECoreEdMess
} }
case cbe::CoreEdMessage_RefreshEd: case cbe::CoreEdMessage_RefreshEd:
{ {
if (msgDat.issuerData != std::bit_cast<uint64>(detailsLayer.get()) && std::holds_alternative<MessageData::ObjectsList>(msgDat.msg)) const bool bDetailsWgRefresh = msgDat.issuerData != std::bit_cast<uint64>(detailsLayer.get());
const bool bViewportWgRefresh = msgDat.issuerData != std::bit_cast<uint64>(viewportLayer.get());
if (std::holds_alternative<MessageData::ObjectsList>(msgDat.msg))
{ {
detailsLayer->refreshDetailsDrawers(std::get<MessageData::ObjectsList>(msgDat.msg)); if (bDetailsWgRefresh)
{
detailsLayer->refreshDetailsDrawers(std::get<MessageData::ObjectsList>(msgDat.msg));
}
response.state = MessageResponse::Remove;
}
else if (std::holds_alternative<EEdRefreshType>(msgDat.msg))
{
switch (std::get<EEdRefreshType>(msgDat.msg))
{
case EEdRefreshType::VisualOnly:
if (bDetailsWgRefresh)
{
detailsLayer->refreshDetailsDrawers({});
}
break;
case EEdRefreshType::FullReset:
if (bDetailsWgRefresh)
{
detailsLayer->resetDetails();
}
if (bViewportWgRefresh)
{
viewportLayer->resetViewport();
}
break;
default:
break;
}
response.state = MessageResponse::Remove;
} }
response.state = MessageResponse::Remove;
break; break;
} }
case cbe::CoreEdMessage_SelectionChanged: case cbe::CoreEdMessage_SelectionChanged:
{ {
const std::vector<WeakObjPtr<Object>> &newSelObjs = viewportLayer->getWorldViewport().getSelectedObjects();
const std::unordered_set<WorldSelectionProxyRef> &newSelProxies = viewportLayer->getWorldViewport().getSelectedProxies();
worldLayer->onSelectionChanged(); worldLayer->onSelectionChanged();
detailsLayer->onSelectionChanged(); detailsLayer->onSelectionChanged();
viewportLayer->onSelectionChanged(); viewportLayer->onSelectionChanged();
mainWorldSelObjs = newSelObjs;
mainWorldSelProxies.clear();
mainWorldSelProxies.reserve(newSelProxies.size());
mainWorldSelProxies.insert(mainWorldSelProxies.begin(), newSelProxies.cbegin(), newSelProxies.cend());
response.state = MessageResponse::Remove; response.state = MessageResponse::Remove;
break; break;
} }
@@ -177,9 +206,6 @@ void EngineMainEd::drawEditor() {}
void EngineMainEd::collectReferencesInternal(MAYBE_UNUSED std::vector<cbe::Object *> &outObjects) const {} void EngineMainEd::collectReferencesInternal(MAYBE_UNUSED std::vector<cbe::Object *> &outObjects) const {}
void EngineMainEd::onCloseEditor() void EngineMainEd::onCloseEditor()
{ {
mainWorldSelProxies.clear();
mainWorldSelObjs.clear();
gCBEEngine->worldManager()->onWorldInitEvent().unbind(worldInitHandle); gCBEEngine->worldManager()->onWorldInitEvent().unbind(worldInitHandle);
gCBEEngine->worldManager()->onWorldUnloadEvent().unbind(worldUnloadHandle); gCBEEngine->worldManager()->onWorldUnloadEvent().unbind(worldUnloadHandle);
@@ -211,6 +237,11 @@ void EngineMainEd::onEdTick(MAYBE_UNUSED float deltaTime)
bEditingAssetDirty = cbe::isPackageDirty(world); bEditingAssetDirty = cbe::isPackageDirty(world);
bIsPlayInEd = EditorHelpers::isPlayInEd(*gCBEditorEngine->worldManager()); bIsPlayInEd = EditorHelpers::isPlayInEd(*gCBEditorEngine->worldManager());
} }
else
{
bEditingAssetDirty = false;
bIsPlayInEd = false;
}
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@@ -235,7 +266,10 @@ void EditorEngine::engineStart()
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("EditorStartup")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("EditorStartup"));
const String engContentDir = Paths::engineContentDirectory(); const String engContentDir = Paths::engineContentDirectory();
CbeApplication *application = IApplicationModule::get()->getApplication(); CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());
assetManager = application->getAssetManager();
/* Initialize font data */ /* Initialize font data */
WgImGui::WgImGuiFont fonts[2]; WgImGui::WgImGuiFont fonts[2];
std::vector<uint8> fontData[2]; std::vector<uint8> fontData[2];
@@ -300,6 +334,10 @@ void EditorEngine::engineStart()
} }
CoreObjectDelegates::broadcastContentDirectoryAdded(engContentDir); CoreObjectDelegates::broadcastContentDirectoryAdded(engContentDir);
modLoadCbHndl = ModuleManager::get()->onModuleLoad.bindObject(this, &EditorEngine::onModuleLoadCb);
modUnloadCbHndl = ModuleManager::get()->onModuleUnload.bindObject(this, &EditorEngine::onModuleUnloadCb);
refreshTypeInfo();
tempTest(); tempTest();
} }
@@ -318,6 +356,9 @@ void EditorEngine::engineExit()
{ {
tempExitTest(); tempExitTest();
ModuleManager::get()->onModuleLoad.unbind(modLoadCbHndl);
ModuleManager::get()->onModuleUnload.unbind(modUnloadCbHndl);
mainEd->closeEditor(); mainEd->closeEditor();
/* GC is the last reference holder */ /* GC is the last reference holder */
debugAssert(mainEd->refCount() <= 2); debugAssert(mainEd->refCount() <= 2);
@@ -328,23 +369,91 @@ void EditorEngine::engineExit()
FileHelper::deleteDir(transactionDir); FileHelper::deleteDir(transactionDir);
} }
void EditorEngine::destroy() {} void EditorEngine::onDestructed() {}
copat::JobSystemFuncAwaiter asyncOpenScene(copat::JobSystem &jobSystem, StringView inObjPath)
{
debugAssert(copat::JobSystem::get()->isInThread(copat::EJobThreadType::MainThread));
AssetManager *assetManager = gCBEditorEngine->getAssetManager();
CoreObjectGC &gc = ICoreObjectsModule::get()->getGC();
AssetManager::PackageAllocator::AllocHandle pkgAllocHnd = {};
{
StringView outerObjPath;
StringView objName;
const StringView packagePath = ObjectPathHelper::getPathComponents(outerObjPath, objName, inObjPath);
const AssetManager::TreeNodeIdx pkgFolderFolderTreeIdx = assetManager->pathToFolderIdxInFolderTree(packagePath);
const AssetManager::TreeNodeIdx assetPkgFolderTreeIdx = assetManager->findAssetUnder(objName, pkgFolderFolderTreeIdx, false);
debugAssert(assetManager->isPackageNode(assetPkgFolderTreeIdx));
pkgAllocHnd = std::get<AssetManager::FolderTree_Package>(assetManager->getFolderInfo(assetPkgFolderTreeIdx)->nodeData);
}
std::vector<AssetManager::PackageAllocator::AllocHandle> allPkgsToLoad;
{
allPkgsToLoad.emplace_back(pkgAllocHnd);
SizeT startIdx = 0;
SizeT endIdx = allPkgsToLoad.size();
while (startIdx < endIdx)
{
for (SizeT i = startIdx; i < endIdx; ++i)
{
assetManager->getPackageDepsFromRefTree(allPkgsToLoad, assetManager->getAssetPackage(allPkgsToLoad[i])->folderTreeIdx);
}
startIdx = endIdx;
endIdx = allPkgsToLoad.size();
}
}
/* Now start the progress, +1 for Preparing world, +1 after opening the world and keep progress showing. */
const cbe::ProgressTrackerHnd progTracker = gCBEditorEngine->addProgressTracker(cbe::ProgressTrackerInfo{
.name = TCHAR("Loading scene objects"),
.stepsCount = static_cast<uint32>(allPkgsToLoad.size() + 2),
});
/* To not clear the loaded objects while Async loading */
gc.pauseGc();
co_await copat::SwitchJobSystemThreadAwaiter{ jobSystem, copat::EJobThreadType::WorkerThreads };
/* Last load will be the world */
Object *lastLoadedObj = nullptr;
for (auto itr = allPkgsToLoad.rbegin(); itr != allPkgsToLoad.rend(); ++itr)
{
const AssetManager::AssetPackage *assetPkg = assetManager->getAssetPackage(*itr);
const StringView objName = ObjectPathHelper::getObjectName(assetPkg->rootObjectPath);
gCBEditorEngine->progressProgressTracker(progTracker, 1, STR_FORMAT("Loading {}", objName));
lastLoadedObj = cbe::getOrLoad(assetPkg->rootObjectPath, assetPkg->rootObjectClass);
co_await copat::YieldAwaiter{};
}
gCBEditorEngine->progressProgressTracker(progTracker, 1, STR_FORMAT("Preparing World {}", lastLoadedObj->getObjectData().name));
co_await copat::SwitchJobSystemThreadAwaiter{ *copat::JobSystem::get(), copat::EJobThreadType::MainThread };
debugAssert(lastLoadedObj != nullptr);
gc.resumeGc();
gCBEditorEngine->openAssetEditor(lastLoadedObj);
gCBEditorEngine->progressProgressTracker(progTracker, 1, {});
}
ICoreAssetEditorRef EditorEngine::openAssetEditor(Object *obj) ICoreAssetEditorRef EditorEngine::openAssetEditor(Object *obj)
{ {
debugAssert(obj); debugAssert(obj);
/* Worlds must be loaded and unloaded into world Manager and editors will listen to events and process them. */ /* Worlds must be loaded and unloaded into world Manager and editors will listen to events and process them. */
if (cbe::World *newWorld = cbe::cast<cbe::World>(obj)) if (cbe::World *newWorld = cbe::cast<cbe::World>(obj))
{ {
cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld(); WorldsManager *worldMan = gCBEEngine->worldManager();
cbe::World *mainWorld = worldMan->getMainWorld();
if (newWorld != mainWorld) if (newWorld != mainWorld)
{ {
if (mainWorld != nullptr) if (mainWorld != nullptr)
{ {
gCBEEngine->worldManager()->unloadWorld(mainWorld); worldMan->unloadWorld(mainWorld);
} }
const bool bAsMainWorld = true; const bool bAsMainWorld = true;
gCBEEngine->worldManager()->initWorld(newWorld, bAsMainWorld); worldMan->initWorld(newWorld, bAsMainWorld);
ICoreObjectsModule::get()->getGC().waitGc();
} }
return nullptr; return nullptr;
@@ -371,6 +480,19 @@ ICoreAssetEditorRef EditorEngine::openAssetEditor(Object *obj)
mainEd->editorLayer->addAssetEditor(assetEditor); mainEd->editorLayer->addAssetEditor(assetEditor);
return assetEditor; return assetEditor;
} }
ICoreAssetEditorRef EditorEngine::openAssetEditor(StringView objPath, CBEClass objClass)
{
if (PropertyHelper::isChildOf<World>(objClass))
{
asyncOpenScene(*copat::JobSystem::get(), objPath);
return nullptr;
}
cbe::Object *asset = cbe::getOrLoad(objPath, objClass);
return openAssetEditor(asset);
}
ICoreAssetEditorRef EditorEngine::openAssetEditor(const ObjectPath &objPath) { return openAssetEditor(objPath.getObject()); }
void EditorEngine::closeAssetEditor(const ICoreAssetEditorRef &editor) void EditorEngine::closeAssetEditor(const ICoreAssetEditorRef &editor)
{ {
if (!mainEd->editorLayer->removeAssetEditor(editor)) if (!mainEd->editorLayer->removeAssetEditor(editor))
@@ -410,6 +532,68 @@ void EditorEngine::refreshAssetEditor(Object *obj)
} }
void EditorEngine::addNotification(const cbe::NotificationInfo &info) const { mainEd->editorLayer->addNotification(info); } void EditorEngine::addNotification(const cbe::NotificationInfo &info) const { mainEd->editorLayer->addNotification(info); }
uint64 EditorEngine::addStickyNotification(const cbe::NotificationInfo &info) { return mainEd->editorLayer->addStickyNotification(info); }
void EditorEngine::removeStickyNotification(uint64 stickyId) { mainEd->editorLayer->removeStickyNotification(stickyId); }
static_assert(std::same_as<uint64, cbe::ProgressTrackerHnd>, "Update the progress tracker handle type!");
uint64 EditorEngine::addProgressTracker(const cbe::ProgressTrackerInfo &info) { return mainEd->editorLayer->addProgressTracker(info); }
void EditorEngine::progressProgressTracker(uint64 hnd, uint32 stepsCount, StringView stepDesc)
{
mainEd->editorLayer->progressProgressTracker(hnd, stepsCount, stepDesc);
}
void EditorEngine::setProgressTracerProgress(uint64 hnd, float progress) { mainEd->editorLayer->setProgressTracerProgress(hnd, progress); }
bool EditorEngine::isProgressTrackerCancelling(uint64 hnd) const { return mainEd->editorLayer->isProgressTrackerCancelling(hnd); }
void EditorEngine::cancelProgressTracker(uint64 hnd) { mainEd->editorLayer->cancelProgressTracker(hnd); }
void EditorEngine::onModuleLoadCb(const String &) { refreshTypeInfo(); }
void EditorEngine::onModuleUnloadCb(const String &) { refreshTypeInfo(); }
void EditorEngine::refreshTypeInfo()
{
IReflectionRuntimeModule &rttiModule = *IReflectionRuntimeModule::get();
/* Erase if class cannot be instantiated */
auto isAbstractClass = [](CBEClass clazz)
{
return clazz->constructors.empty();
};
auto fillClasses
= [&rttiModule, isAbstractClass]<bool InsertBase>(EdClassCacheList &cacheList, CBEClass baseClass, ValueToType<InsertBase>)
{
cacheList.classList.clear();
cacheList.classDrawInfo.clear();
if constexpr (InsertBase)
{
cacheList.classList.emplace_back(baseClass);
}
rttiModule.getChildsOf(baseClass, cacheList.classList, true);
std::erase_if(cacheList.classList, isAbstractClass);
cacheList.classDrawInfo.reserve(cacheList.classList.size());
for (CBEClass clazz : cacheList.classList)
{
EdClassCacheDrawInfo &drawInf = cacheList.classDrawInfo.emplace_back();
std::array<TChar, wg_consts::CLASS_INITIALS_COUNT + 1> initials = {};
PropertyHelper::fillTypeInitials({ initials.data(), wg_consts::CLASS_INITIALS_COUNT }, clazz->nameString);
std::copy_n(TCHAR_TO_UTF8(initials.data()).data(), wg_consts::CLASS_INITIALS_COUNT, drawInf.classInitials.data());
drawInf.typeColor = PropertyHelper::getTypeColor(clazz);
}
cacheList.filteredBits.resize(cacheList.classList.size(), std::numeric_limits<uint64>::max());
};
fillClasses(actorClasses, cbe::Actor::staticType(), ValueToType<true>{});
fillClasses(logicCompClasses, cbe::LogicComponent::staticType(), ValueToType<false>{});
fillClasses(tfLeafCompClasses, cbe::TransformLeafComponent::staticType(), ValueToType<false>{});
fillClasses(tfCompClass, cbe::TransformComponent::staticType(), ValueToType<true>{});
}
} // namespace cbe } // namespace cbe
@@ -443,20 +627,11 @@ COMPILER_PRAGMA(COMPILER_DISABLE_WARNING(WARN_UNREACHABLE_CODE WARN_UNUSED_LOCAL
#include "CbeRendererTypes.h" #include "CbeRendererTypes.h"
#include "ICbeRendererModule.h" #include "ICbeRendererModule.h"
#include "GCReferenceCollector.h" #include "GCReferenceCollector.h"
#include "CoreObjectGC.h"
std::vector<cbe::Actor *> worldCubes; std::vector<cbe::Actor *> worldCubes;
cbe::ActorPrefab *cubeActorPrefab = nullptr; cbe::ActorPrefab *cubeActorPrefab = nullptr;
constexpr uint32 MATS_COUNT = 17; // TODO(ASAP) : Keep this until the actual materials are ready.
const std::array<const TChar *, MATS_COUNT> texNames{
TCHAR("Bricks059"), TCHAR("Bricks065"), TCHAR("Gravel022"), TCHAR("Ground037"), TCHAR("Ground042"), TCHAR("Leather028"),
TCHAR("Marble006"), TCHAR("Metal034"), TCHAR("Metal038"), TCHAR("MetalPlates006"), TCHAR("PaintedPlaster016"), TCHAR("Rock035"),
TCHAR("Tiles074"), TCHAR("Tiles086"), TCHAR("Tiles108"), TCHAR("Wood051"), TCHAR("WoodFloor043"),
};
std::array<cbe::MaterialInstance *, MATS_COUNT> matInsts;
std::array<cbe::Texture2D *, MATS_COUNT * 2> textures;
CBE_MATERIAL_STRUCT_BEGIN(SampleMaterialBuffer, false) CBE_MATERIAL_STRUCT_BEGIN(SampleMaterialBuffer, false)
CBE_MATERIAL_STRUCT_FIELD_TEXTURE(color, Linear) CBE_MATERIAL_STRUCT_FIELD_TEXTURE(color, Linear)
CBE_MATERIAL_STRUCT_FIELD_TEXTURE(normal, Nearest) CBE_MATERIAL_STRUCT_FIELD_TEXTURE(normal, Nearest)
@@ -473,122 +648,29 @@ cbe::physics::Body pHeightBody;
std::vector<cbe::physics::Body> pWorldBodies; std::vector<cbe::physics::Body> pWorldBodies;
bool bUseCubeScene = false; bool bUseCubeScene = false;
bool bUseMaterials = false;
bool bStartTestScene = false; bool bStartTestScene = false;
class TempRefCollector : public ISelfRegisterReferenceCollector
{
public:
void clearReferences(ArrayView<cbe::Object *> deletedObjects) override { debugAssert(deletedObjects.empty()); }
void collectReferences(std::vector<cbe::Object *> &outObjects) const override
{
if (bUseCubeScene && bUseMaterials)
{
outObjects.reserve(MATS_COUNT * 3);
outObjects.insert(outObjects.cend(), matInsts.cbegin(), matInsts.cend());
outObjects.insert(outObjects.cend(), textures.cbegin(), textures.cend());
}
}
};
ReferenceCountPtr<TempRefCollector> tempRefCollector;
void tempTestScenes() void tempTestScenes()
{ {
String dir = Paths::engineContentDirectory(); String dir = Paths::engineContentDirectory();
String name = Paths::applicationName(); String name = Paths::applicationName();
#if 0
if (BasicPackagedObject *obj = cbe::load<BasicPackagedObject>(name))
{
LOG("Test", "Loaded object {} nameVal {}", obj->getObjectData().path, obj->nameVal);
}
else
{
cbe::Package *package = cbe::Package::createPackage(name, dir, false);
cbe::Package *package2 = cbe::Package::createPackage(name + TCHAR("2"), dir, false);
BasicPackagedObject *packedObj2 = cbe::create<BasicPackagedObject>(name, package2);
packedObj2->dt = 0.56f;
packedObj2->nameVal = TCHAR("Its connected object");
packedObj2->structData = { .a = 4124111.06f, .b = 2026, .testStr = "This must be connected to another package" };
BasicPackagedObject *packedObj = cbe::create<BasicPackagedObject>(name, package);
packedObj->dt = 0.28f;
packedObj->id = STRID("Hello Subity & Jeslas");
packedObj->nameVal = TCHAR("Its Me Jeslas");
packedObj->idxToStr = {
{ 1, TCHAR("Jeslas Pravin") },
{ 2, TCHAR("Subity Jerald") }
};
packedObj->structData = { .a = 8235.28f, .b = 834435, .testStr = "3528" };
packedObj->interLinked = packedObj2;
BasicFieldSerializedObject *testTemp = cbe::create<BasicFieldSerializedObject>(name, package);
testTemp->dt = 101.111f;
testTemp->id = STRID("HEll Let lOsE");
testTemp->interLinked = packedObj;
testTemp->nameVal = TCHAR("Test All field serialization!");
testTemp->structData = { .a = 4321, .b = 1234, .testStr = "Not a default value here!" };
testTemp->idxToStr[10] = {
{ TCHAR("ABC"), 123 },
{ TCHAR("CBA"), 321 }
};
testTemp->idxToStr[5] = {
{ TCHAR("XYZ"), 55667788 },
{ TCHAR("ZYX"), 8235 }
};
IInterfaceExample *interface1 = cbe::cast<IInterfaceExample>(static_cast<cbe::Object *>(testTemp));
IInterfaceExample2 *interface2 = cbe::cast<IInterfaceExample2>(static_cast<cbe::Object *>(testTemp));
IInterfaceExample2 *interface3 = cbe::cast<IInterfaceExample2>(interface1);
BasicFieldSerializedObject *ixToClassObj = cbe::cast<BasicFieldSerializedObject>(interface1);
cbe::Object *ix1ToClassObj = cbe::cast<cbe::Object>(interface1);
cbe::Object *ix2ToClassObj = cbe::cast<cbe::Object>(interface2);
BasicPackagedObject *failingCast = cbe::cast<BasicPackagedObject>(interface1);
BasicFieldSerializedObject *copied = cbe::cast<BasicFieldSerializedObject>(cbe::duplicateObject(testTemp, package));
cbe::save(packedObj);
cbe::save(packedObj2);
}
#endif
#if 1 #if 1
String importContentTo = PathFunctions::combinePath(Paths::engineRuntimeRoot(), TCHAR("Content")); String importContentTo = Paths::engineContentDirectory();
// Create cube first // Create cube first
cbe::StaticMesh *cubeMesh = cbe::getOrLoad<cbe::StaticMesh>(TCHAR("Meshes/Cube:Cube")); cbe::StaticMesh *cubeMesh = cbe::getOrLoad<cbe::StaticMesh>(TCHAR("Meshes/Cube:Cube"));
if (!cbe::isValid(cubeMesh)) if (!cbe::isValid(cubeMesh))
{ {
#if 0 // Enable below if want to import cube from obj file
const TChar *cubeObjPath = TCHAR("D:/Assets/EngineAssets/Cube.obj");
ImportOption opt;
opt.filePath = cubeObjPath;
opt.importContentPath = importContentTo;
opt.relativeDirPath = TCHAR("Meshes");
if (AssetImporterBase *importer = IEditorCore::get()->findAssetImporter(opt))
{
bool bImportAsScene = false;
float scale = 0.01f;
static_cast<const MemberFieldWrapper *>(PropertyHelper::findField(opt.optionsStructType, STRID("bImportAsScene"))->fieldPtr)
->setTypeless(&bImportAsScene, opt.optionsStruct);
static_cast<const MemberFieldWrapper *>(PropertyHelper::findField(opt.optionsStructType, STRID("scale"))->fieldPtr)
->setTypeless(&scale, opt.optionsStruct);
std::vector<cbe::Object *> objs = *importer->tryImporting(opt);
debugAssert(objs.size() == 1);
cubeMesh = cbe::cast<cbe::StaticMesh>(objs[0]);
cbe::save(cubeMesh);
}
#else
cbe::SMCreateInfo createInfo; cbe::SMCreateInfo createInfo;
cbe::EditorGeomHelpers::createCube(createInfo, 1.0f); cbe::EditorGeomHelpers::createCube(createInfo, 1.0f);
cubeMesh = EditorHelpers::createStaticMesh(TCHAR("Meshes/Cube"), importContentTo, TCHAR("Cube"), std::move(createInfo)); cubeMesh = EditorHelpers::createStaticMesh(TCHAR("Meshes/Cube"), importContentTo, TCHAR("Cube"), std::move(createInfo));
cbe::save(cubeMesh); cbe::save(cubeMesh);
#endif
} }
// Create or get cube actor prefab // Create or get cube actor prefab
debugAssert(cubeMesh); debugAssert(cubeMesh);
const TChar *cubeActorPath = TCHAR("Prefabs/CubeActor:CubeActor"); const TChar *cubeActorPath = TCHAR("Prefabs/CubeActor:CubeActor");
cubeActorPrefab = cbe::getOrLoad<cbe::ActorPrefab>(cubeActorPath); cubeActorPrefab = cbe::getOrLoad<cbe::ActorPrefab>(cubeActorPath);
if (cubeActorPrefab == nullptr) if (cubeActorPrefab == nullptr)
@@ -621,65 +703,6 @@ void tempTestScenes()
cbe::save(cubeActorPrefab); cbe::save(cubeActorPrefab);
} }
if (!bUseCubeScene)
{
const TChar *meshObjPath = TCHAR("D:/Assets/Scenes/CrytekSponza/sponza.obj");
const TChar *meshEnginePath = TCHAR("Scenes/sponza:sponza");
// const TChar *meshObjPath = TCHAR("D:/Assets/Scenes/LumberyardBistro/Exterior/LumberyardBistroExterior.obj");
// const TChar *meshEnginePath = TCHAR("Scenes/LumberyardBistroExterior:LumberyardBistroExterior");
cbe::World *sceneObj = cbe::getOrLoad<cbe::World>(meshEnginePath);
if (sceneObj == nullptr && FileSystemFunctions::fileExists(meshObjPath))
{
ImportOption opt;
opt.filePath = meshObjPath;
opt.importContentPath = importContentTo;
opt.relativeDirPath = TCHAR("Scenes");
if (AssetImporterBase *importer = IEditorCore::get()->findAssetImporter(opt))
{
bool bImportAsScene = true;
bool bIsYUp = true;
float scale = 0.01f;
static_cast<const MemberFieldWrapper *>(PropertyHelper::findField(opt.optionsStructType, STRID("bImportAsScene"))->fieldPtr)
->setTypeless(&bImportAsScene, opt.optionsStruct);
static_cast<const MemberFieldWrapper *>(PropertyHelper::findField(opt.optionsStructType, STRID("bFromYUp"))->fieldPtr)
->setTypeless(&bIsYUp, opt.optionsStruct);
static_cast<const MemberFieldWrapper *>(PropertyHelper::findField(opt.optionsStructType, STRID("scale"))->fieldPtr)
->setTypeless(&scale, opt.optionsStruct);
std::vector<cbe::Object *> objs = *importer->tryImporting(opt);
for (cbe::Object *obj : objs)
{
cbe::save(obj);
}
if (!objs.empty())
{
sceneObj = cbe::cast<cbe::World>(objs[0]);
}
}
}
if (sceneObj)
{
gCBEEngine->worldManager()->initWorld(sceneObj, true);
EditorHelpers::addComponentToPrefab(cubeActorPrefab, test::TestLogicComp::staticType(), TCHAR("TestLogic"));
cbe::ActorPrefab *smActorPrefab = cbe::ActorPrefab::prefabFromActor(
EditorHelpers::addActorToWorld(gCBEEngine->worldManager()->getEditorWorld(), cubeActorPrefab, TCHAR("TestCube"), 0)
);
smActorPrefab = cbe::get<cbe::ActorPrefab>(
ObjectPathHelper::getFullPath(smActorPrefab->getObjectData().name, gCBEEngine->worldManager()->getEditorWorld())
);
debugAssert(smActorPrefab);
worldCubes.emplace_back(smActorPrefab->getActorTemplate());
smActorPrefab->getActorTemplate()->setWorldLocation({ 1.50f, 0, 1.00f });
}
else
{
bUseCubeScene = true;
}
}
if (bUseCubeScene) if (bUseCubeScene)
{ {
const TChar *scenePath = TCHAR("Scenes/TestCubes:TestCubes"); const TChar *scenePath = TCHAR("Scenes/TestCubes:TestCubes");
@@ -772,71 +795,6 @@ void tempTestScenes()
{ {
bUseCubeScene = false; bUseCubeScene = false;
} }
if (bUseMaterials)
{
tempRefCollector = new TempRefCollector();
/* If using cube scene add few textures and materials */
const TChar *texturesPathBase = TCHAR("D:/Assets/EngineAssets/Textures");
for (SizeT i = 0; i < MATS_COUNT; ++i)
{
const SizeT texIdx = i * 2;
const String engineColorTexturePath = ObjectPathHelper::combinePathComponents(
STR_FORMAT("Textures/{}_D", texNames[i]), TCHAR(""), STR_FORMAT("{}_D", texNames[i])
);
const String engineNormalTexturePath = ObjectPathHelper::combinePathComponents(
STR_FORMAT("Textures/{}_N", texNames[i]), TCHAR(""), STR_FORMAT("{}_N", texNames[i])
);
const String engineMatInstPath
= ObjectPathHelper::combinePathComponents(STR_FORMAT("Materials/{}", texNames[i]), TCHAR(""), texNames[i]);
textures[texIdx + 0] = cbe::getOrLoad<cbe::Texture2D>(engineColorTexturePath);
if (textures[texIdx + 0] != nullptr)
{
matInsts[i] = cbe::getOrLoad<cbe::MaterialInstance>(engineMatInstPath);
textures[texIdx + 1] = cbe::getOrLoad<cbe::Texture2D>(engineNormalTexturePath);
debugAssert(matInsts[i] != nullptr && textures[texIdx + 1] != nullptr);
}
else
{
/* Import textures and create materials. */
const String colorTexturePath
= PathFunctions::combinePath(texturesPathBase, texNames[i], STR_FORMAT("{}_D.jpg", texNames[i]));
const String normalTexturePath
= PathFunctions::combinePath(texturesPathBase, texNames[i], STR_FORMAT("{}_N.jpg", texNames[i]));
ImportOption opt;
opt.filePath = colorTexturePath;
opt.importContentPath = importContentTo;
opt.relativeDirPath = TCHAR("Textures");
if (AssetImporterBase *importer = IEditorCore::get()->findAssetImporter(opt))
{
std::vector<cbe::Object *> objs = *importer->tryImporting(opt);
debugAssert(objs.size() == 1);
textures[texIdx + 0] = cbe::cast<cbe::Texture2D>(objs[0]);
}
opt.filePath = normalTexturePath;
if (AssetImporterBase *importer = IEditorCore::get()->findAssetImporter(opt))
{
std::vector<cbe::Object *> objs = *importer->tryImporting(opt);
debugAssert(objs.size() == 1);
textures[texIdx + 1] = cbe::cast<cbe::Texture2D>(objs[0]);
}
cbe::save(textures[texIdx + 0]);
cbe::save(textures[texIdx + 1]);
cbe::MaterialInstance *matInst
= EditorHelpers::createMaterialInstance(STR_FORMAT("Materials/{}", texNames[i]), importContentTo, texNames[i]);
matInsts[i] = matInst;
matInst->data.resize(sizeof(SampleMaterialBuffer));
matInst->dataLayoutName = SampleMaterialBuffer::bufferLayout().name;
matInst->materialName = TCHAR("SampleLit");
matInst->texture2dRefs.emplace_back(TCHAR("color"), textures[texIdx + 0]);
matInst->texture2dRefs.emplace_back(TCHAR("normal"), textures[texIdx + 1]);
cbe::save(matInst);
}
}
}
} }
#endif #endif
} }
@@ -850,7 +808,6 @@ void tempTest()
else else
{ {
bUseCubeScene = false; bUseCubeScene = false;
bUseMaterials = false;
} }
} }
@@ -894,15 +851,6 @@ void tempTickTest(const ApplicationTimeData &timeData, const Camera &camera)
)); ));
worldCubes.emplace_back(smActorPrefab->getActorTemplate()); worldCubes.emplace_back(smActorPrefab->getActorTemplate());
smActorPrefab->getActorTemplate()->setWorldLocation(offset[worldCubes.size() % 4]); smActorPrefab->getActorTemplate()->setWorldLocation(offset[worldCubes.size() % 4]);
/* Set a new material */
std::vector<cbe::TransformLeafComponent *> leafComps;
cbe::WACHelpers::getComponentLeafs(smActorPrefab->getRootComponent(), leafComps);
debugAssert(leafComps.size() == 1);
if (bUseMaterials)
{
cbe::StaticMeshComponent *smComp = cbe::cast<cbe::StaticMeshComponent>(leafComps[0]);
smComp->setMaterial(matInsts[worldCubes.size() % MATS_COUNT]);
}
} }
pWorldBodies.resize(startIdx + 4); pWorldBodies.resize(startIdx + 4);
@@ -922,45 +870,10 @@ void tempTickTest(const ApplicationTimeData &timeData, const Camera &camera)
app->getPhysicsBackend()->addBodies(pWorld, { pWorldBodies.data() + startIdx, 4 }, true); app->getPhysicsBackend()->addBodies(pWorld, { pWorldBodies.data() + startIdx, 4 }, true);
} }
} }
else if (!worldCubes.empty())
{
for (auto itr = worldCubes.begin(); itr != worldCubes.end();)
{
cbe::Actor *ac = *itr;
if (!cbe::isValid(ac))
{
itr = worldCubes.erase(itr);
continue;
}
int32 i = 1;
while (i > 0)
{
Rotation r = ac->getWorldRotation();
static Vector3 n = Vector3::ONE.normalized() * Vector3(-1, 1, 1);
Quat rQ = Quat::fromRotation(r);
Quat delQ = Quat::fromAngleAxis(180 * timeData.deltaTime, n);
Quat newQ = delQ * rQ;
Rotation newR = newQ.toRotation();
alertAlways(!newR.isNan() && newR.isFinite());
Vector3 l = ac->getWorldLocation();
l.x = 2 * MathEasing::sharpTooth(Math::frac(Time::asSeconds(Time::timeNow()) / 3.0f));
ac->setWorldRotation(newR);
ac->setWorldLocation(l);
i--;
}
++itr;
}
}
} }
void tempExitTest() void tempExitTest()
{ {
tempRefCollector.reset();
if (bUseCubeScene) if (bUseCubeScene)
{ {
CranberryEngineApp *app = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication()); CranberryEngineApp *app = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -17,9 +17,10 @@
#include <Classes/EngineBase.hpp> #include <Classes/EngineBase.hpp>
#include <Classes/Actor.hpp> #include <Classes/Actor.hpp>
#include <ObjectPtrs.h> #include <ObjectPtrs.h>
#include <CbeWorldRenderer/WorldSelectionProxy.hpp>
#include <Transaction/TransactionsLedger.hpp> #include <Transaction/TransactionsLedger.hpp>
#include <ICoreAssetEditor.hpp> #include <ICoreAssetEditor.hpp>
#include <EditorTypes.h>
#include <Widgets/EditorWidgetsHelper.hpp>
#include "EditorEngine.gen.hpp" #include "EditorEngine.gen.hpp"
@@ -43,7 +44,9 @@ class Texture2D;
class StaticMesh; class StaticMesh;
struct NotificationInfo; struct NotificationInfo;
struct ProgressTrackerInfo;
class EditorEngine; class EditorEngine;
class AssetManager;
/** /**
* @brief Inheriting ICoreAssetEditor in order to support the message queue in unified manner. * @brief Inheriting ICoreAssetEditor in order to support the message queue in unified manner.
@@ -88,10 +91,6 @@ private:
DelegateHandle worldInitHandle; DelegateHandle worldInitHandle;
DelegateHandle worldUnloadHandle; DelegateHandle worldUnloadHandle;
/* For tracking selections only. */
std::vector<WeakObjPtr<Object>> mainWorldSelObjs;
std::vector<WorldSelectionProxyRef> mainWorldSelProxies;
TransactionsLedger undoRedoLedger; TransactionsLedger undoRedoLedger;
bool bIsPlayInEd:1 = false; bool bIsPlayInEd:1 = false;
@@ -109,7 +108,7 @@ public:
cbe::ETryExit engineTryExit() override; cbe::ETryExit engineTryExit() override;
void engineExit() override; void engineExit() override;
/* EngineBase overrides */ /* EngineBase overrides */
void destroy() override; void onDestructed() override;
/* Override ends */ /* Override ends */
/* Do not hold reference to pointer */ /* Do not hold reference to pointer */
@@ -117,22 +116,75 @@ public:
EngineMainEd *getMainEd() const { return mainEd.get(); } EngineMainEd *getMainEd() const { return mainEd.get(); }
const String &transactionFolder() const { return transactionDir; } const String &transactionFolder() const { return transactionDir; }
TransactionsLedger &getTransactionLedger() const { return *mainEd->getTransactionLedger(); }
AssetManager *getAssetManager() const { return assetManager; }
/* Asset editor helpers */ /* Asset editor helpers */
ICoreAssetEditorRef openAssetEditor(Object *obj); ICoreAssetEditorRef openAssetEditor(Object *obj);
ICoreAssetEditorRef openAssetEditor(StringView objPath, CBEClass objClass);
ICoreAssetEditorRef openAssetEditor(const ObjectPath &objPath);
void closeAssetEditor(const ICoreAssetEditorRef &editor); void closeAssetEditor(const ICoreAssetEditorRef &editor);
ICoreAssetEditorRef getAssetEditor(Object *obj); ICoreAssetEditorRef getAssetEditor(Object *obj);
void refreshAssetEditor(Object *obj); void refreshAssetEditor(Object *obj);
void addNotification(const cbe::NotificationInfo &info) const; void addNotification(const cbe::NotificationInfo &info) const;
TransactionsLedger &getTransactionLedger() const { return *mainEd->getTransactionLedger(); } /**
* @brief Notifications that sticks until explicitly removed by Sticky ID.
* @param info Notification info.
*/
uint64 addStickyNotification(const cbe::NotificationInfo &info);
void removeStickyNotification(uint64 stickyId);
/**
* @brief Adds a new progress tracker and returns tracker handle that can used to update progress.
* @param info Progress tracker information.
* @return Progress tracker handle.
*/
uint64 addProgressTracker(const cbe::ProgressTrackerInfo &info);
/**
* @brief Progresses the tracker by provided steps count and updates the next step with new description.
* @param hnd Progress tracker handle.
* @param stepsCount Number of steps to progress.
* @param stepDesc If empty previous description is used.
*/
void progressProgressTracker(uint64 hnd, uint32 stepsCount, StringView stepDesc);
/**
* @brief Sets the continuous progress.
* @param hnd Progress tracker handle.
* @param progress Progress value between 0.0 - 1.0.
*/
void setProgressTracerProgress(uint64 hnd, float progress);
bool isProgressTrackerCancelling(uint64 hnd) const;
void cancelProgressTracker(uint64 hnd);
EdClassCacheList &getActorClassesCache() { return actorClasses; }
EdClassCacheList &getTfLeafClassesCache() { return tfLeafCompClasses; }
EdClassCacheList &getLogicCompClassesCache() { return logicCompClasses; }
EdClassCacheList &getTfCompClassCache() { return tfCompClass; }
private: private:
SharedPtr<WgImGui> wgImgui; SharedPtr<WgImGui> wgImgui;
ReferenceCountPtr<EngineMainEd> mainEd; ReferenceCountPtr<EngineMainEd> mainEd;
AssetManager *assetManager;
String transactionDir; String transactionDir;
DelegateHandle modLoadCbHndl;
DelegateHandle modUnloadCbHndl;
EdClassCacheList actorClasses;
EdClassCacheList tfLeafCompClasses;
EdClassCacheList logicCompClasses;
/* Only one but it is okay to follow same code path for similar logic */
EdClassCacheList tfCompClass;
private:
void onModuleLoadCb(const String &);
void onModuleUnloadCb(const String &);
/* Refreshes editor cached RTTI data */
void refreshTypeInfo();
} META_ANNOTATE(NoExport); } META_ANNOTATE(NoExport);
CBEEDITOR_EXPORT extern EditorEngine *gCBEditorEngine; CBEEDITOR_EXPORT extern EditorEngine *gCBEditorEngine;

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2025 * \date August 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -54,16 +54,22 @@ void EditorGeomHelpers::createCube(SMCreateInfo &outCreateInfo, float size, Vect
texCoord[0] = (km + 1) / 2; texCoord[0] = (km + 1) / 2;
texCoord[1] = (jm + 1) / 2; texCoord[1] = (jm + 1) / 2;
/* Left Handedness: Flip along Y - Axis */
position.y = -position.y;
normal.y = -normal.y;
tangent.y = -tangent.y;
outCreateInfo.vertsS0.emplace_back(vert0); outCreateInfo.vertsS0.emplace_back(vert0);
outCreateInfo.vertsS1.emplace_back(vert1); outCreateInfo.vertsS1.emplace_back(vert1);
} }
outCreateInfo.indices.emplace_back(startVertIdx + 0); /* Left Handedness: Reverse winding order */
outCreateInfo.indices.emplace_back(startVertIdx + 2);
outCreateInfo.indices.emplace_back(startVertIdx + 1); outCreateInfo.indices.emplace_back(startVertIdx + 1);
outCreateInfo.indices.emplace_back(startVertIdx + 0);
outCreateInfo.indices.emplace_back(startVertIdx + 3);
outCreateInfo.indices.emplace_back(startVertIdx + 2); outCreateInfo.indices.emplace_back(startVertIdx + 2);
outCreateInfo.indices.emplace_back(startVertIdx + 0); outCreateInfo.indices.emplace_back(startVertIdx + 0);
outCreateInfo.indices.emplace_back(startVertIdx + 2);
outCreateInfo.indices.emplace_back(startVertIdx + 3);
} }
for (uint32 i = 0; i < outCreateInfo.vertsS0.size(); ++i) for (uint32 i = 0; i < outCreateInfo.vertsS0.size(); ++i)
@@ -79,6 +85,8 @@ void EditorGeomHelpers::createCube(SMCreateInfo &outCreateInfo, float size, Vect
}); });
} }
// TODO(Jeslas) : Make everything left handed. Only cube is converted to left handed. Right handed mesh has flip textures in Camera view.
void EditorGeomHelpers::createSphere(SMCreateInfo &outCreateInfo, float diameter, uint32 latSegCount, uint32 longSegCount) void EditorGeomHelpers::createSphere(SMCreateInfo &outCreateInfo, float diameter, uint32 latSegCount, uint32 longSegCount)
{ {
longSegCount = Math::max(longSegCount, 4); longSegCount = Math::max(longSegCount, 4);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -66,6 +66,30 @@ void EditorHelpers::makePackageUnique(String &inOutPackagePath, StringView suffi
inOutPackagePath = outPath; inOutPackagePath = outPath;
} }
void EditorHelpers::makeComponentUnique(cbe::Actor *actor, String &inOutCompName, StringView suffix, const CoreObjectsDB &objDb)
{
std::vector<cbe::Object *> comps = getActorCompSubObjects(actor, objDb);
std::unordered_set<NameString> objNames;
objNames.reserve(comps.size());
for (cbe::Object *obj : comps)
{
objNames.insert(obj->getObjectData().name);
}
String outName = inOutCompName;
outName.append(suffix);
uint32 counter = 0;
while (objNames.contains(NameString(outName)))
{
counter++;
outName = inOutCompName + String::toString(counter);
outName.append(suffix);
}
inOutCompName = outName;
}
void EditorHelpers::renamePkgObjsToMatchPkg( void EditorHelpers::renamePkgObjsToMatchPkg(
const CoreObjectsDB &objectsDb, StringView orgPkgName, StringView orgRootObjName, cbe::Package *pkg, bool bRecurse const CoreObjectsDB &objectsDb, StringView orgPkgName, StringView orgRootObjName, cbe::Package *pkg, bool bRecurse
) )
@@ -195,7 +219,7 @@ std::pair<String, std::vector<cbe::Object *>> EditorHelpers::redoTransaction(cbe
} }
cbe::Object *EditorHelpers::createAsset( cbe::Object *EditorHelpers::createAsset(
cbe::ICoreAssetFactory *assetFactory, CBEClass clazz, StringView packageRelPath, StringView contentDir, ArrayView<cbe::ICoreAssetFactory *> assetFactories, CBEClass clazz, StringView packageRelPath, StringView contentDir,
const SingleCastDelegate<cbe::Object *, const cbe::AssetCreateInfo &> &createCb const SingleCastDelegate<cbe::Object *, const cbe::AssetCreateInfo &> &createCb
) )
{ {
@@ -209,24 +233,36 @@ cbe::Object *EditorHelpers::createAsset(
const StringView objName = ObjectPathHelper::getObjectName(packageRelPath); const StringView objName = ObjectPathHelper::getObjectName(packageRelPath);
cbe::Object *obj = nullptr; cbe::Object *obj = nullptr;
if (assetFactory != nullptr)
const cbe::AssetCreateInfo assetCi{
.assetName = objName,
.clazz = clazz,
.outer = package,
.flags = cbe::ObjFlag_PackageLoaded,
};
if (createCb.isBound())
{ {
const cbe::AssetCreateInfo assetCi{ obj = createCb(assetCi);
.assetName = objName, }
.clazz = clazz,
.outer = package, if (obj == nullptr && !assetFactories.empty())
.flags = cbe::ObjFlag_PackageLoaded, {
}; for (cbe::ICoreAssetFactory *factory : assetFactories)
if (createCb.isBound())
{ {
obj = createCb(assetCi); if (!factory->canCreateAsset())
} {
else continue;
{ }
obj = assetFactory->createAsset(assetCi); obj = factory->createAsset(assetCi);
if (obj != nullptr)
{
break;
}
} }
} }
else
if (obj == nullptr)
{ {
obj = cbe::create(clazz, objName, package, cbe::ObjFlag_PackageLoaded); obj = cbe::create(clazz, objName, package, cbe::ObjFlag_PackageLoaded);
} }
@@ -278,12 +314,12 @@ void EditorHelpers::saveEditingAsset(cbe::ICoreAssetEditor *assetEditor, cbe::IC
ArrayArchiveStream archiveStream; ArrayArchiveStream archiveStream;
EPackageLoadSaveResult result EPackageLoadSaveResult result
= PackageSaver::saveFromPackage(static_cast<cbe::Package *>(editingAsset->getOuterMost()), archiveStream); = PackageSaver::saveFromPackage(static_cast<cbe::Package *>(editingAsset->getOuterMost()), archiveStream);
LOG_ERROR_C( CBE_LOG_ERROR_C(
result != EPackageLoadSaveResult::Success, LOG_CATEGORY, "Failed to serialize editing asset {}", result != EPackageLoadSaveResult::Success, LOG_CATEGORY, "Failed to serialize editing asset {}",
editingAsset->getObjectData().path editingAsset->getObjectData().path
); );
result = PackageLoader::loadIntoPackage(archiveStream, static_cast<cbe::Package *>(asset->getOuterMost())); result = PackageLoader::loadIntoPackage(archiveStream, static_cast<cbe::Package *>(asset->getOuterMost()));
LOG_ERROR_C( CBE_LOG_ERROR_C(
result != EPackageLoadSaveResult::Success, LOG_CATEGORY, "Failed to deserialize into asset {}", asset->getObjectData().path result != EPackageLoadSaveResult::Success, LOG_CATEGORY, "Failed to deserialize into asset {}", asset->getObjectData().path
); );
@@ -342,22 +378,6 @@ cbe::Texture2D *EditorHelpers::createTexture2D(
return texture2d; return texture2d;
} }
cbe::MaterialInstance *EditorHelpers::createMaterialInstance(const String &packageRelPath, const String &packagePath, StringView name)
{
cbe::Package *package = cbe::Package::createPackage(packageRelPath, packagePath, false);
if (package == nullptr)
{
return nullptr;
}
cbe::markPackageDirty(package);
cbe::INTERNAL_ObjectCoreAccessors::addFlags(package, cbe::ObjFlag_PackageLoaded);
cbe::MaterialInstance *matInst = cbe::create<cbe::MaterialInstance>(name, package, cbe::ObjFlag_PackageLoaded);
debugAssert(matInst);
return matInst;
}
cbe::StaticMesh * cbe::StaticMesh *
EditorHelpers::createStaticMesh(const String &packageRelPath, const String &packagePath, StringView name, cbe::SMCreateInfo &&createInfo) EditorHelpers::createStaticMesh(const String &packageRelPath, const String &packagePath, StringView name, cbe::SMCreateInfo &&createInfo)
{ {
@@ -385,12 +405,12 @@ cbe::Actor *EditorHelpers::addStaticMeshesToWorld(
if (rootActorName.empty()) if (rootActorName.empty())
{ {
LOG_WARN(LOG_CATEGORY, "Root actor name must be valid! Cannot add static meshes to the world"); CBE_LOG_WARN(LOG_CATEGORY, "Root actor name must be valid! Cannot add static meshes to the world");
return nullptr; return nullptr;
} }
if (staticMeshes.empty() || world == nullptr) if (staticMeshes.empty() || world == nullptr)
{ {
LOG_WARN(LOG_CATEGORY, "World or staticMeshes are invalid! Cannot add static meshes to the world"); CBE_LOG_WARN(LOG_CATEGORY, "World or staticMeshes are invalid! Cannot add static meshes to the world");
return nullptr; return nullptr;
} }
@@ -425,13 +445,14 @@ cbe::Actor *EditorHelpers::addStaticMeshToWorld(cbe::StaticMesh *sm, const Trans
cbe::Object *modifyingComp cbe::Object *modifyingComp
= modifyPrefabCompField(PropertyHelper::findField(smComp->getType(), GET_MEMBER_ID_CHECKED(cbe::StaticMeshComponent, mesh)), smComp); = modifyPrefabCompField(PropertyHelper::findField(smComp->getType(), GET_MEMBER_ID_CHECKED(cbe::StaticMeshComponent, mesh)), smComp);
debugAssert(modifyingComp == smComp); debugAssert(modifyingComp == smComp);
/* Setting mesh also modifies batchMaterials */
modifyingComp = modifyPrefabCompField( modifyingComp = modifyPrefabCompField(
PropertyHelper::findField(smComp->getType(), GET_MEMBER_ID_CHECKED(cbe::StaticMeshComponent, batchMaterials)), smComp PropertyHelper::findField(smComp->getType(), GET_MEMBER_ID_CHECKED(cbe::StaticMeshComponent, batchMaterials)), smComp
); );
debugAssert(modifyingComp == smComp); debugAssert(modifyingComp == smComp);
smComp->setMesh(sm); smComp->setMesh(sm);
/* Attach static mesh to root even though in Prefab, added component will get attached to root by default */ /* Attach static mesh to root even though in Prefab, added component will get attached to root by default. */
cbe::WACHelpers::attachComponent(smComp, smActorPrefab->getRootComponent()); cbe::WACHelpers::attachComponent(smComp, smActorPrefab->getRootComponent());
modifyingComp = modifyPrefabCompField( modifyingComp = modifyPrefabCompField(
@@ -502,8 +523,12 @@ cbe::Actor *EditorHelpers::addActorToWorld(cbe::World *world, cbe::ActorPrefab *
cbe::markPackageDirty(world); cbe::markPackageDirty(world);
return prefab->getActorTemplate(); return prefab->getActorTemplate();
} }
void EditorHelpers::removeActorFromWorld(cbe::World *world, cbe::Actor *actor) { postRemoveActorFromWorld(world, actor); } void EditorHelpers::removeActorFromWorld(cbe::World *world, cbe::Actor *actor) { postRemoveActorFromWorld(world, actor); }
void EditorHelpers::attachActor(cbe::World *world, cbe::Actor *actor, cbe::TransformComponent *attachTo)
{
debugAssert(world != nullptr && actor->getWorld() == world);
cbe::WACHelpers::attachActor(actor, attachTo);
}
cbe::Object *EditorHelpers::addComponentToPrefab(cbe::ActorPrefab *prefab, CBEClass compClass, const String &compName) cbe::Object *EditorHelpers::addComponentToPrefab(cbe::ActorPrefab *prefab, CBEClass compClass, const String &compName)
{ {
@@ -550,6 +575,8 @@ void EditorHelpers::removeComponentFromPrefab(cbe::ActorPrefab *prefab, cbe::Obj
cbe::markPackageDirty(prefab); cbe::markPackageDirty(prefab);
} }
prefab->removeComponent(comp); prefab->removeComponent(comp);
// TODO(Jeslas) : Patch up the world transform hierarchy based on prefab's tf hierarchy
} }
cbe::Object *EditorHelpers::modifyComponentInPrefab(cbe::ActorPrefab *prefab, cbe::Object *modifyingComp) cbe::Object *EditorHelpers::modifyComponentInPrefab(cbe::ActorPrefab *prefab, cbe::Object *modifyingComp)
@@ -558,6 +585,45 @@ cbe::Object *EditorHelpers::modifyComponentInPrefab(cbe::ActorPrefab *prefab, cb
return modifiedComp; return modifiedComp;
} }
void EditorHelpers::attachComponent(
std::variant<cbe::TransformComponent *, cbe::TransformLeafComponent *> thisComp, cbe::TransformComponent *attachToComp
)
{
/* If not transform component it must be a leaf */
if (std::holds_alternative<cbe::TransformComponent *>(thisComp))
{
cbe::TransformComponent *tfComp = std::get<cbe::TransformComponent *>(thisComp);
cbe::ActorPrefab *prefab = cbe::ActorPrefab::prefabFromComponent(tfComp);
debugAssert(prefab != nullptr);
prefab->setComponentAttachedTo(tfComp, attachToComp);
/* If world is prepared already then the attachment changes must be propagated there */
if (cbe::World *world = prefab->getActorTemplate()->getWorld();
world != nullptr && cbe::EWorldState::isPreparedState(world->getState()))
{
cbe::WACHelpers::attachComponent(tfComp, attachToComp);
}
}
else
{
cbe::TransformLeafComponent *leafComp = std::get<cbe::TransformLeafComponent *>(thisComp);
cbe::ActorPrefab *prefab = cbe::ActorPrefab::prefabFromComponent(leafComp);
debugAssert(prefab != nullptr);
prefab->setLeafAttachedTo(leafComp, attachToComp);
/* If world is prepared already then the attachment changes must be propagated there */
if (cbe::World *world = prefab->getActorTemplate()->getWorld();
world != nullptr && cbe::EWorldState::isPreparedState(world->getState()))
{
cbe::WACHelpers::attachComponent(leafComp, attachToComp);
}
}
}
cbe::Object *EditorHelpers::modifyPrefabCompField(const FieldProperty *prop, cbe::Object *comp) cbe::Object *EditorHelpers::modifyPrefabCompField(const FieldProperty *prop, cbe::Object *comp)
{ {
debugAssert(comp && prop); debugAssert(comp && prop);
@@ -640,6 +706,11 @@ std::vector<cbe::Object *> EditorHelpers::getActorCompSubObjects(cbe::Actor *act
for (cbe::Object *child : children) for (cbe::Object *child : children)
{ {
if (!cbe::isValidFast(child))
{
continue;
}
if (child->getType() != cbe::ObjectTemplate::staticType()) if (child->getType() != cbe::ObjectTemplate::staticType())
{ {
continue; continue;
@@ -835,10 +906,11 @@ void EditorHelpers::postRemoveActorFromWorld(cbe::World *world, cbe::Actor *acto
void EditorHelpers::componentAddedToWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bAddToActorPending) void EditorHelpers::componentAddedToWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bAddToActorPending)
{ {
const bool bWorldPrepared = cbe::EWorldState::isPreparedState(world->getState());
/* Must broadcast if world is not prepared or if component is only added to actor prefab in a prepared world. /* Must broadcast if world is not prepared or if component is only added to actor prefab in a prepared world.
* Must add to component list only if dealing with component added to actor prefab and world is already prepared. */ * Must add to component list only if dealing with component added to actor prefab and world is already prepared. */
const bool bBroadcast = !cbe::EWorldState::isPreparedState(world->getState()) || bAddToActorPending; const bool bBroadcast = !bWorldPrepared || (bWorldPrepared && bAddToActorPending);
const bool bAddToCompList = cbe::EWorldState::isPreparedState(world->getState()) && bAddToActorPending; const bool bAddToCompList = bWorldPrepared && bAddToActorPending;
if (!bBroadcast) if (!bBroadcast)
{ {
return; return;
@@ -846,27 +918,40 @@ void EditorHelpers::componentAddedToWorld(cbe::World *world, cbe::Actor *actor,
if (PropertyHelper::isChildOf<cbe::TransformComponent>(component->getType())) if (PropertyHelper::isChildOf<cbe::TransformComponent>(component->getType()))
{ {
world->broadcastTfCompAdded(component); cbe::TransformComponent *tfComponent = static_cast<cbe::TransformComponent *>(component);
/* Only setup transform component's world setup if we need to add to component list.
* This is because in other cases component should get setup during actor/world setup. */
if (bAddToCompList) if (bAddToCompList)
{ {
actor->transformComps.insert(static_cast<cbe::TransformComponent *>(component)); debugAssert(!world->compToTf.contains(tfComponent));
/* Insert into component list first */
world->compToTf[tfComponent] = world->txHierarchy.add(cbe::ComponentWorldTF{ tfComponent, tfComponent->getRelativeTransform() });
if (tfComponent != actor->getRootComponent())
{
world->updateTfAttachment(tfComponent, cbe::ActorPrefab::prefabFromActor(actor)->getAttachedToComp(tfComponent), false);
}
actor->transformComps.insert(tfComponent);
} }
world->broadcastTfCompAdded(tfComponent);
} }
else if (PropertyHelper::isChildOf<cbe::LogicComponent>(component->getType())) else if (PropertyHelper::isChildOf<cbe::LogicComponent>(component->getType()))
{ {
world->broadcastLogicCompAdded(component);
if (bAddToCompList) if (bAddToCompList)
{ {
actor->logicComps.insert(static_cast<cbe::LogicComponent *>(component)); actor->logicComps.insert(static_cast<cbe::LogicComponent *>(component));
} }
world->broadcastLogicCompAdded(component);
} }
else if (PropertyHelper::isChildOf<cbe::TransformLeafComponent>(component->getType())) else if (PropertyHelper::isChildOf<cbe::TransformLeafComponent>(component->getType()))
{ {
world->broadcastLeafCompAdded(component);
if (bAddToCompList) if (bAddToCompList)
{ {
actor->leafComps.insert(static_cast<cbe::TransformLeafComponent *>(component)); actor->leafComps.insert(static_cast<cbe::TransformLeafComponent *>(component));
} }
world->broadcastLeafCompAdded(component);
} }
else else
{ {
@@ -879,28 +964,45 @@ void EditorHelpers::componentAddedToWorld(cbe::World *world, cbe::Actor *actor,
void EditorHelpers::componentRemovedFromWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bRemoveFromActorPending) void EditorHelpers::componentRemovedFromWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bRemoveFromActorPending)
{ {
const bool bWorldPrepared = cbe::EWorldState::isPreparedState(world->getState());
/* Must broadcast if world is not prepared or if actor prefab's component removal is requested in a prepared world. /* Must broadcast if world is not prepared or if actor prefab's component removal is requested in a prepared world.
* Must remove from component list only if dealing with component added to actor prefab and world is already prepared. */ * Must remove from component list only if dealing with component added to actor prefab and world is already prepared. */
const bool bBroadcast = cbe::EWorldState::isPreparedState(world->getState()) || bRemoveFromActorPending; const bool bBroadcast = !bWorldPrepared || (bWorldPrepared && bRemoveFromActorPending);
const bool bRemoveFromCompList = cbe::EWorldState::isPreparedState(world->getState()) && bRemoveFromActorPending; /* Not sure if we need to check condition to remove from component list we could just remove always just for removal. */
const bool bRemoveFromCompList = bWorldPrepared && bRemoveFromActorPending;
if (!bBroadcast) if (!bBroadcast)
{ {
return; return;
} }
/* Just to be safe and not have any hanging references */
if (cbe::TransformComponent *tfComponent = cbe::cast<cbe::TransformComponent>(component)) if (cbe::TransformComponent *tfComponent = cbe::cast<cbe::TransformComponent>(component))
{ {
auto compWorldTfItr = world->compToTf.find(tfComponent); if (bWorldPrepared)
cbe::World::TFHierarchyIdx compTfIdx;
if (compWorldTfItr != world->compToTf.end())
{ {
compTfIdx = compWorldTfItr->second; auto compWorldTfItr = world->compToTf.find(tfComponent);
#if DEBUG_VALIDATIONS_ENABLED debugAssert(compWorldTfItr != world->compToTf.end());
std::vector<cbe::World::TFHierarchyIdx> directAttachments;
world->txHierarchy.getChildren(directAttachments, compTfIdx, false); cbe::World::TFHierarchyIdx compTfIdx = compWorldTfItr->second;
/* So if ever below assert fails. There is desync in logic on how world components are attached and tree updates */
debugAssert(directAttachments.empty()); /* Do not try to reattach since root do not have in actor parent to reattach to.
#endif // DEBUG_VALIDATIONS_ENABLED * Also hierarchy tree becomes invalid when parent gets removed thereby invalidating the compTfIdx */
if (actor->getRootComponent() != tfComponent && world->txHierarchy.isValid(compTfIdx))
{
std::vector<cbe::TransformLeafComponent *> leaves;
cbe::WACHelpers::getComponentLeafs(tfComponent, leaves);
cbe::World::TFHierarchyIdx compTfParentIdx = world->txHierarchy.getNode(compTfIdx).parent;
for (cbe::TransformLeafComponent *leaf : leaves)
{
cbe::WACHelpers::attachComponent(leaf, world->txHierarchy[compTfParentIdx].component);
}
std::vector<cbe::World::TFHierarchyIdx> children = world->txHierarchy.getChildren(compTfIdx, false);
for (cbe::World::TFHierarchyIdx tfIdx : children)
{
cbe::WACHelpers::attachComponent(world->txHierarchy[tfIdx].component, world->txHierarchy[compTfParentIdx].component);
}
}
world->txHierarchy.remove(compTfIdx); world->txHierarchy.remove(compTfIdx);
world->compToTf.erase(compWorldTfItr); world->compToTf.erase(compWorldTfItr);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -38,6 +38,7 @@ class StaticMesh;
class World; class World;
class Actor; class Actor;
class TransformComponent; class TransformComponent;
class TransformLeafComponent;
class MaterialInstance; class MaterialInstance;
class WorldsManager; class WorldsManager;
@@ -67,6 +68,8 @@ public:
/* Name must be a full path to package */ /* Name must be a full path to package */
static void makePackageUnique(String &inOutPackagePath) { makePackageUnique(inOutPackagePath, {}); } static void makePackageUnique(String &inOutPackagePath) { makePackageUnique(inOutPackagePath, {}); }
static void makePackageUnique(String &inOutPackagePath, StringView suffix); static void makePackageUnique(String &inOutPackagePath, StringView suffix);
static void makeComponentUnique(cbe::Actor *actor, String &inOutCompName, StringView suffix, const CoreObjectsDB &objDb);
/* When package is renames/copied/moved there is chance new package name and object name might not match. /* When package is renames/copied/moved there is chance new package name and object name might not match.
* Calling this will make sure object names matches the new package name. * Calling this will make sure object names matches the new package name.
* Pass bRecurse to true to allow renaming all the objects that matches the package name. */ * Pass bRecurse to true to allow renaming all the objects that matches the package name. */
@@ -77,8 +80,9 @@ public:
static std::pair<String, std::vector<cbe::Object *>> undoTransaction(cbe::TransactionsLedger &ledger); static std::pair<String, std::vector<cbe::Object *>> undoTransaction(cbe::TransactionsLedger &ledger);
static std::pair<String, std::vector<cbe::Object *>> redoTransaction(cbe::TransactionsLedger &ledger); static std::pair<String, std::vector<cbe::Object *>> redoTransaction(cbe::TransactionsLedger &ledger);
/* Any asset that can be created manually in editor must use this routine to create in code */
static cbe::Object *createAsset( static cbe::Object *createAsset(
cbe::ICoreAssetFactory *assetFactory, CBEClass clazz, StringView packageRelPath, StringView contentDir, ArrayView<cbe::ICoreAssetFactory *> assetFactories, CBEClass clazz, StringView packageRelPath, StringView contentDir,
const SingleCastDelegate<cbe::Object *, const cbe::AssetCreateInfo &> &createCb const SingleCastDelegate<cbe::Object *, const cbe::AssetCreateInfo &> &createCb
); );
static cbe::Object *createEditingAsset(cbe::Object *asset); static cbe::Object *createEditingAsset(cbe::Object *asset);
@@ -91,8 +95,6 @@ public:
static cbe::Texture2D * static cbe::Texture2D *
createTexture2D(const String &packageRelPath, const String &packagePath, StringView name, cbe::Texture2DCreateInfo &&createInfo); createTexture2D(const String &packageRelPath, const String &packagePath, StringView name, cbe::Texture2DCreateInfo &&createInfo);
static cbe::MaterialInstance *createMaterialInstance(const String &packageRelPath, const String &packagePath, StringView name);
static cbe::StaticMesh * static cbe::StaticMesh *
createStaticMesh(const String &packageName, const String &packagePath, StringView name, cbe::SMCreateInfo &&createInfo); createStaticMesh(const String &packageName, const String &packagePath, StringView name, cbe::SMCreateInfo &&createInfo);
// Returns the root actor to which all this static mesh actors are attached to // Returns the root actor to which all this static mesh actors are attached to
@@ -107,6 +109,7 @@ public:
static cbe::Actor *addActorToWorld(cbe::World *world, CBEClass actorClass, const String &actorName, EObjectFlags flags); static cbe::Actor *addActorToWorld(cbe::World *world, CBEClass actorClass, const String &actorName, EObjectFlags flags);
static cbe::Actor *addActorToWorld(cbe::World *world, cbe::ActorPrefab *inPrefab, const String &name, EObjectFlags flags); static cbe::Actor *addActorToWorld(cbe::World *world, cbe::ActorPrefab *inPrefab, const String &name, EObjectFlags flags);
static void removeActorFromWorld(cbe::World *world, cbe::Actor *actor); static void removeActorFromWorld(cbe::World *world, cbe::Actor *actor);
static void attachActor(cbe::World *world, cbe::Actor *actor, cbe::TransformComponent *attachTo);
/* Below two supposed to be used by editor to create actor from the prefab and issues transaction into ledger. /* Below two supposed to be used by editor to create actor from the prefab and issues transaction into ledger.
* Copy from prefab must not be any actual actor in world. It must be a transient actor prefab that is only used to spawn prefab. */ * Copy from prefab must not be any actual actor in world. It must be a transient actor prefab that is only used to spawn prefab. */
@@ -118,6 +121,18 @@ public:
static cbe::Object *addComponentToPrefab(cbe::ActorPrefab *prefab, cbe::ObjectTemplate *compTemplate, const String &compName); static cbe::Object *addComponentToPrefab(cbe::ActorPrefab *prefab, cbe::ObjectTemplate *compTemplate, const String &compName);
static void removeComponentFromPrefab(cbe::ActorPrefab *prefab, cbe::Object *comp); static void removeComponentFromPrefab(cbe::ActorPrefab *prefab, cbe::Object *comp);
static cbe::Object *modifyComponentInPrefab(cbe::ActorPrefab *prefab, cbe::Object *modifyingComp); static cbe::Object *modifyComponentInPrefab(cbe::ActorPrefab *prefab, cbe::Object *modifyingComp);
/* Just helper. Does only attachment change and updates necessary in world */
static void
attachComponent(std::variant<cbe::TransformComponent *, cbe::TransformLeafComponent *> thisComp, cbe::TransformComponent *attachToComp);
/* Needed helper since world attachment information must be refreshed when reverting or applying.
* Expects all the components to be of same/similar types */
static void removeComponentFromPrefabs(
ArrayView<cbe::ActorPrefab *> prefabs, ArrayView<cbe::Object *> compPerPrefab, bool bLeafComp, bool bLogicComp,
cbe::Transaction *transaction
);
static void
setRootComponent(ArrayView<cbe::ActorPrefab *> prefabs, ArrayView<cbe::TransformComponent *> newRootComps, cbe::Transaction *transaction);
/* Helpers that composes and adds to modifyComponent(), Must be called before modifying for component. Actors always has overrides. */ /* Helpers that composes and adds to modifyComponent(), Must be called before modifying for component. Actors always has overrides. */
static cbe::Object *modifyPrefabCompField(const FieldProperty *prop, cbe::Object *comp); static cbe::Object *modifyPrefabCompField(const FieldProperty *prop, cbe::Object *comp);
@@ -143,6 +158,16 @@ private:
static void postAddActorToWorld(cbe::World *world, cbe::ActorPrefab *prefab); static void postAddActorToWorld(cbe::World *world, cbe::ActorPrefab *prefab);
static void postRemoveActorFromWorld(cbe::World *world, cbe::Actor *actor); static void postRemoveActorFromWorld(cbe::World *world, cbe::Actor *actor);
/**
* @brief Adds component to the world and broadcasts necessary event.
* Ensures component is ready for use by various component in world for world's current state.
*
* @param world
* @param actor
* @param component
* @param bAddToActorPending If component could be added to actor.
* Usually need to be false if world actor setup in world is not done already.
*/
static void componentAddedToWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bAddToActorPending); static void componentAddedToWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bAddToActorPending);
static void componentRemovedFromWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bRemoveFromActorPending); static void componentRemovedFromWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bRemoveFromActorPending);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date December 2025 * \date December 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -291,6 +291,8 @@ public:
static String beforeSaveFileName(StringView baseName) { return STR_FORMAT("{}_BeforeSave", baseName) + TCHAR(".") + cbe::PACKAGE_EXT; } static String beforeSaveFileName(StringView baseName) { return STR_FORMAT("{}_BeforeSave", baseName) + TCHAR(".") + cbe::PACKAGE_EXT; }
static String afterSaveFileName(StringView baseName) { return STR_FORMAT("{}_AfterSave", baseName) + TCHAR(".") + cbe::PACKAGE_EXT; } static String afterSaveFileName(StringView baseName) { return STR_FORMAT("{}_AfterSave", baseName) + TCHAR(".") + cbe::PACKAGE_EXT; }
static String beforeEditFileName(StringView baseName) { return STR_FORMAT("{}_BeforeEdit", baseName) + TCHAR(".") + cbe::PACKAGE_EXT; }
static String afterEditFileName(StringView baseName) { return STR_FORMAT("{}_AfterEdit", baseName) + TCHAR(".") + cbe::PACKAGE_EXT; }
GenericSaveAssetAction::~GenericSaveAssetAction() GenericSaveAssetAction::~GenericSaveAssetAction()
{ {
@@ -351,6 +353,510 @@ void GenericSaveAssetAction::endTransaction()
debugAssert(bWritten); debugAssert(bWritten);
} }
//////////////////////////////////////////////////////////////////////////
// ActorPrefabBackupAction implementations
//////////////////////////////////////////////////////////////////////////
static cbe::ActorPrefab *createActorPrefab(cbe::ActorPrefab *copyFromPrefab, cbe::Object *outer, StringView prefabName)
{
const cbe::ObjectPrivateDataView objDatV = copyFromPrefab->getObjectData();
cbe::ActorPrefab *prefab = nullptr;
if (cbe::ActorPrefab *parentPrefab = copyFromPrefab->getParentPrefab())
{
prefab = cbe::create<cbe::ActorPrefab, cbe::ActorPrefab *, String>(
prefabName, outer, objDatV.flags, parentPrefab, copyFromPrefab->getActorTemplate()->getObjectData().name
);
}
else
{
prefab = cbe::create<cbe::ActorPrefab, StringID, String>(
prefabName, outer, objDatV.flags, copyFromPrefab->getActorClass()->name, copyFromPrefab->getActorTemplate()->getObjectData().name
);
}
prefab->copyFrom(copyFromPrefab);
return prefab;
}
static void serializePrefabsToFile(ArrayView<cbe::ActorPrefab *> prefabs, bool bPostEdit, const String &backupDir)
{
debugAssert(!backupDir.empty());
for (cbe::ActorPrefab *prefab : prefabs)
{
const String prefabName = prefab->getObjectData().name;
cbe::Package *prefabPkg = cast<Package>(prefab->getOuterMost());
debugAssert(prefabPkg != nullptr);
const String prefabBackupDir = PathFunctions::combinePath(backupDir, prefabPkg->getPackagePath());
if (!FileSystemFunctions::dirExists(prefabBackupDir.getChar()))
{
FileHelper::makeDir(prefabBackupDir);
}
ArrayArchiveStream archiveStream;
/* If prefab is part of a world we have to create dummy prefab to serialize as a package. */
if (prefab->getActorTemplate()->getWorld())
{
cbe::Package *dummyPkg = cbe::Package::createTransientPackage(
PathFunctions::combinePathWithSep(ObjectPathHelper::OBJECT_OBJECT_SEPARATOR, prefabPkg->getObjectData().path, prefabName)
);
cbe::ActorPrefab *serializePrefab = createActorPrefab(prefab, dummyPkg, prefabName);
debugAssert(serializePrefab != nullptr);
/* Serialize the dummy package */
const EPackageLoadSaveResult result = PackageSaver::saveFromPackage(dummyPkg, archiveStream);
debugAssert(result == EPackageLoadSaveResult::Success);
/* No need for the dummy package and prefab anymore */
dummyPkg->beginDestroy();
}
else
{
const EPackageLoadSaveResult result = PackageSaver::saveFromPackage(prefabPkg, archiveStream);
debugAssert(result == EPackageLoadSaveResult::Success);
}
const bool bWritten = FileHelper::writeBytes(
archiveStream.getBuffer(),
PathFunctions::combinePath(prefabBackupDir, bPostEdit ? afterEditFileName(prefabName) : beforeEditFileName(prefabName))
);
debugAssert(bWritten);
}
}
ActorPrefabsBackupAction::ActorPrefabsBackupAction()
: objsDb(&ICoreObjectsModule::get()->objectsDB())
{}
ActorPrefabsBackupAction::~ActorPrefabsBackupAction()
{
for (const ObjectPath &actorPrefabPath : actorPrefabPaths)
{
if (!actorPrefabPath.getObjectName().empty() && FileSystemFunctions::dirExists(backupDir.c_str()))
{
FileHelper::deleteFile(PathFunctions::combinePath(backupDir, beforeEditFileName(actorPrefabPath.getObjectName())));
FileHelper::deleteFile(PathFunctions::combinePath(backupDir, afterEditFileName(actorPrefabPath.getObjectName())));
}
}
}
void ActorPrefabsBackupAction::setEditingPrefabs(ArrayView<cbe::ActorPrefab *> prefabs)
{
actorPrefabPaths.clear();
actorPrefabPaths.reserve(prefabs.size());
for (cbe::ActorPrefab *prefab : prefabs)
{
actorPrefabPaths.emplace_back(prefab);
}
serializePrefabsToFile(prefabs, false, backupDir);
}
void ActorPrefabsBackupAction::collectReferences(std::vector<Object *> &outObjects) const { cbe::ignoreUnused(outObjects); }
void ActorPrefabsBackupAction::apply(TransactedEntriesCollector &outEntriesCollector)
{
for (const ObjectPath &actorPrefabPath : actorPrefabPaths)
{
debugAssert(actorPrefabPath.isValid());
cbe::ActorPrefab *copyToPrefab = actorPrefabPath.getObject<cbe::ActorPrefab>();
const String prefabName = copyToPrefab->getObjectData().name;
cbe::Package *prefabPkg = cast<Package>(copyToPrefab->getOuterMost());
debugAssert(prefabPkg != nullptr);
const String loadFromPath
= PathFunctions::combinePath(backupDir, prefabPkg->getPackagePath(), afterEditFileName(actorPrefabPath.getObjectName()));
/* If prefab is part of a world we have to create dummy prefab to serialize as a package and then copy it to original prefab. */
if (copyToPrefab->getActorTemplate()->getWorld())
{
cbe::Package *dummyPkg = cbe::Package::createTransientPackage(PathFunctions::combinePathWithSep(
ObjectPathHelper::OBJECT_OBJECT_SEPARATOR, prefabPkg->getObjectData().path, actorPrefabPath.getObjectName()
));
/* We have to create dummy from current prefab in order to retain transient fields like fields that gets init at world prepare.
* Any field that got modified as part of template will still get overwritten.
* Ensure no such a field gets transiently written to. */
ActorPrefab *dummyPrefab = createActorPrefab(copyToPrefab, dummyPkg, prefabName);
/* Serialize the dummy package */
const EPackageLoadSaveResult result = PackageLoader::loadIntoPackage(loadFromPath, dummyPkg);
debugAssert(result == EPackageLoadSaveResult::Success);
std::vector<cbe::Object *> pkgChildren;
objsDb->getChildren(pkgChildren, dummyPkg->getDbIdx());
debugAssert(pkgChildren.size() == 1);
debugAssert(dummyPrefab != nullptr && pkgChildren[0] == dummyPrefab);
copyToPrefab->copyFrom(dummyPrefab);
/* No need for the dummy package and prefab anymore */
dummyPkg->beginDestroy();
}
else
{
const EPackageLoadSaveResult result = PackageLoader::loadIntoPackage(loadFromPath, prefabPkg);
debugAssert(result == EPackageLoadSaveResult::Success);
}
outEntriesCollector.addTransactedObj(copyToPrefab);
}
}
void ActorPrefabsBackupAction::revert(TransactedEntriesCollector &outEntriesCollector)
{
for (const ObjectPath &actorPrefabPath : actorPrefabPaths)
{
debugAssert(actorPrefabPath.isValid());
cbe::ActorPrefab *copyToPrefab = actorPrefabPath.getObject<cbe::ActorPrefab>();
const String prefabName = copyToPrefab->getObjectData().name;
cbe::Package *prefabPkg = cast<Package>(copyToPrefab->getOuterMost());
debugAssert(prefabPkg != nullptr);
const String loadFromPath
= PathFunctions::combinePath(backupDir, prefabPkg->getPackagePath(), beforeEditFileName(actorPrefabPath.getObjectName()));
/* If prefab is part of a world we have to create dummy prefab to serialize as a package and then copy it to original prefab. */
if (copyToPrefab->getActorTemplate()->getWorld())
{
cbe::Package *dummyPkg = cbe::Package::createTransientPackage(PathFunctions::combinePathWithSep(
ObjectPathHelper::OBJECT_OBJECT_SEPARATOR, prefabPkg->getObjectData().path, actorPrefabPath.getObjectName()
));
/* We have to create dummy from current prefab in order to retain transient fields like fields that gets init at world prepare.
* Any field that got modified as part of template will still get overwritten.
* Ensure no such a field gets transiently written to. */
ActorPrefab *dummyPrefab = createActorPrefab(copyToPrefab, dummyPkg, prefabName);
/* Serialize the dummy package */
const EPackageLoadSaveResult result = PackageLoader::loadIntoPackage(loadFromPath, dummyPkg);
debugAssert(result == EPackageLoadSaveResult::Success);
std::vector<cbe::Object *> pkgChildren;
objsDb->getChildren(pkgChildren, dummyPkg->getDbIdx());
debugAssert(pkgChildren.size() == 1);
debugAssert(dummyPrefab != nullptr && pkgChildren[0] == dummyPrefab);
copyToPrefab->copyFrom(dummyPrefab);
/* No need for the dummy package and prefab anymore */
dummyPkg->beginDestroy();
}
else
{
const EPackageLoadSaveResult result = PackageLoader::loadIntoPackage(loadFromPath, prefabPkg);
debugAssert(result == EPackageLoadSaveResult::Success);
}
outEntriesCollector.addTransactedObj(copyToPrefab);
}
}
bool ActorPrefabsBackupAction::isValid() const
{
if (objsDb == nullptr)
{
return false;
}
for (const ObjectPath &actorPrefabPath : actorPrefabPaths)
{
if (!actorPrefabPath.isValid())
{
return false;
}
}
return true;
}
void ActorPrefabsBackupAction::endTransaction()
{
std::vector<cbe::ActorPrefab *> prefabs;
prefabs.reserve(actorPrefabPaths.size());
for (const ObjectPath &actorPrefabPath : actorPrefabPaths)
{
debugAssert(actorPrefabPath.isValid());
prefabs.emplace_back(actorPrefabPath.getObject<cbe::ActorPrefab>());
}
serializePrefabsToFile(prefabs, true, backupDir);
}
//////////////////////////////////////////////////////////////////////////
// Remove Components from selected prefabs actions
//////////////////////////////////////////////////////////////////////////
class RemoveCompsFromActorPrefabsAction : public TransactionCustomAction
{
public:
RemoveCompsFromActorPrefabsAction() = default;
RemoveCompsFromActorPrefabsAction(RemoveCompsFromActorPrefabsAction &&other)
: world(std::move(other.world))
, prefabs(std::move(other.prefabs))
{}
~RemoveCompsFromActorPrefabsAction() = default;
/* TransactionCustomAction overrides */
void collectReferences(std::vector<Object *> &outObjects) const final;
void apply(TransactedEntriesCollector &outEntriesCollector) final;
void revert(TransactedEntriesCollector &outEntriesCollector) final;
bool isValid() const final;
/* Overrides ends */
public:
WeakObjPtr<World> world;
struct PerPrefabData
{
ObjectPath prefab;
ObjectPath compRemoved;
/* Will be valid only if the another component actually was attached to removed component. */
std::vector<ObjectPath> attachedActors;
};
std::vector<PerPrefabData> prefabs;
};
void RemoveCompsFromActorPrefabsAction::collectReferences(std::vector<Object *> &) const {}
void RemoveCompsFromActorPrefabsAction::apply(TransactedEntriesCollector &outEntriesCollector)
{
if (world.isSet())
{
outEntriesCollector.addTransactedObj(world.get());
}
for (const PerPrefabData &prefabData : prefabs)
{
ActorPrefab *prefab = prefabData.prefab.getObject<ActorPrefab>();
EditorHelpers::removeComponentFromPrefab(prefab, prefabData.compRemoved.getObject());
outEntriesCollector.addTransactedObj(prefab);
}
}
void RemoveCompsFromActorPrefabsAction::revert(TransactedEntriesCollector &outEntriesCollector)
{
/* Add back must be handled separately */
if (!world.isSet())
{
return;
}
/* Do not need to broadcast if the world is not prepared */
if (!EWorldState::isPreparedState(world->getState()))
{
return;
}
outEntriesCollector.addTransactedObj(world.get());
for (const PerPrefabData &prefabData : prefabs)
{
Object *compRemoved = prefabData.compRemoved.getObject();
if (compRemoved->getType() != cbe::TransformComponent::staticType())
{
continue;
}
TransformComponent *tfComp = static_cast<TransformComponent *>(compRemoved);
ActorPrefab *prefab = prefabData.prefab.getObject<ActorPrefab>();
debugAssert(prefab);
/* Force update the attached components. To update the tf tree and other systems */
std::vector<TransformComponent *> tfComps;
std::vector<TransformLeafComponent *> leafComps;
prefab->getCompAttaches(tfComp, tfComps);
prefab->getCompAttaches(tfComp, leafComps);
for (TransformComponent *attachedComp : tfComps)
{
EditorHelpers::attachComponent(attachedComp, tfComp);
}
for (TransformLeafComponent *leafComp : leafComps)
{
EditorHelpers::attachComponent(leafComp, tfComp);
}
/* Force refresh attachment change update which in turn push transformed updates to world renderer and others. */
TransformComponent *attachedToComp = prefab->getAttachedToComp(tfComp);
if (attachedToComp != nullptr)
{
EditorHelpers::attachComponent(tfComp, attachedToComp);
}
outEntriesCollector.addTransactedObj(prefab);
for (const ObjectPath &actorPath : prefabData.attachedActors)
{
if (Actor *attachedActor = actorPath.getObject<Actor>())
{
EditorHelpers::attachActor(world.get(), attachedActor, tfComp);
}
}
}
}
bool RemoveCompsFromActorPrefabsAction::isValid() const { return !world.isSet() || world.isValid(); }
class RemoveCompsAddBackCompAction : public TransactionCustomAction
{
public:
RemoveCompsAddBackCompAction() = default;
RemoveCompsAddBackCompAction(RemoveCompsAddBackCompAction &&other)
: prefabs(std::move(other.prefabs))
{}
~RemoveCompsAddBackCompAction() = default;
/* TransactionCustomAction overrides */
void collectReferences(std::vector<Object *> &outObjects) const final;
void apply(TransactedEntriesCollector &outEntriesCollector) final;
void revert(TransactedEntriesCollector &outEntriesCollector) final;
bool isValid() const final { return true; }
/* Overrides ends */
public:
struct PerPrefabData
{
ObjectPath prefab;
String compName;
std::variant<CBEClass, ObjectPath> compTemplate;
};
std::vector<PerPrefabData> prefabs;
};
void RemoveCompsAddBackCompAction::collectReferences(std::vector<Object *> &) const {}
void RemoveCompsAddBackCompAction::apply(TransactedEntriesCollector &) {}
void RemoveCompsAddBackCompAction::revert(TransactedEntriesCollector &outEntriesCollector)
{
for (const PerPrefabData &prefabData : prefabs)
{
ActorPrefab *prefab = prefabData.prefab.getObject<ActorPrefab>();
if (std::holds_alternative<CBEClass>(prefabData.compTemplate))
{
EditorHelpers::addComponentToPrefab(prefab, std::get<CBEClass>(prefabData.compTemplate), prefabData.compName);
}
else
{
EditorHelpers::addComponentToPrefab(
prefab, std::get<ObjectPath>(prefabData.compTemplate).getObject<ObjectTemplate>(), prefabData.compName
);
}
outEntriesCollector.addTransactedObj(prefab);
}
}
/**
* @brief Root component when changed must be propagated to world as well.
* This works in conjunction with ActorPrefabsBackupAction to restore attachment information in world.
*/
class RootCompsReattachAction : public TransactionCustomAction
{
public:
RootCompsReattachAction() = default;
RootCompsReattachAction(RootCompsReattachAction &&other)
: prefabs(std::move(other.prefabs))
, worldPtr(std::move(other.worldPtr))
{}
~RootCompsReattachAction() = default;
/* TransactionCustomAction overrides */
void collectReferences(std::vector<Object *> &outObjects) const final;
void apply(TransactedEntriesCollector &outEntriesCollector) final;
void revert(TransactedEntriesCollector &outEntriesCollector) final;
bool isValid() const final { return worldPtr.isValid(); }
/* Overrides ends */
public:
struct PerPrefabData
{
ObjectPath prefab;
ObjectPath oldRoot;
ObjectPath newRoot;
/* Transform component old root was attached to. To setup world actor attachment. */
ObjectPath oldRootAttachedTo;
};
std::vector<PerPrefabData> prefabs;
WeakObjPtr<World> worldPtr;
/* If want to do the appropriate action at apply */
bool bAtApply:1 = true;
/* If want to do the appropriate action at revert */
bool bAtRevert:1 = true;
};
void RootCompsReattachAction::collectReferences(std::vector<Object *> &) const {}
void RootCompsReattachAction::apply(TransactedEntriesCollector &outEntriesCollector)
{
if (!bAtApply)
{
return;
}
World *world = worldPtr.get();
for (const PerPrefabData &prefabData : prefabs)
{
ActorPrefab *prefab = prefabData.prefab.getObject<ActorPrefab>();
debugAssert(prefab != nullptr);
TransformComponent *oldRoot = prefabData.oldRoot.getObject<TransformComponent>();
TransformComponent *newRoot = prefabData.newRoot.getObject<TransformComponent>();
TransformComponent *oldRootAttachedTo = prefabData.oldRootAttachedTo.getObject<TransformComponent>();
debugAssert(oldRoot != nullptr && newRoot != nullptr);
/* Just set the root component to world's actor root component. That means newRoot must have been setup already. */
debugAssert(prefab->getRootComponent() == newRoot);
prefab->setRootComponent(newRoot, true);
/* Attach the old root to new root. */
debugAssert(prefab->getAttachedToComp(oldRoot) == newRoot);
/* First detach the newRoot from oldRoot or its child to avoid Cyclic Graph */
world->tfAttachmentChanged(newRoot, nullptr);
EditorHelpers::attachComponent(oldRoot, newRoot);
/* Attach the actor to previously attached actor */
EditorHelpers::attachActor(world, prefab->getActorTemplate(), oldRootAttachedTo);
outEntriesCollector.addTransactedObj(prefab);
}
outEntriesCollector.addTransactedObj(world);
}
void RootCompsReattachAction::revert(TransactedEntriesCollector &outEntriesCollector)
{
if (!bAtRevert)
{
return;
}
World *world = worldPtr.get();
for (const PerPrefabData &prefabData : prefabs)
{
ActorPrefab *prefab = prefabData.prefab.getObject<ActorPrefab>();
debugAssert(prefab != nullptr);
TransformComponent *oldRoot = prefabData.oldRoot.getObject<TransformComponent>();
TransformComponent *newRoot = prefabData.newRoot.getObject<TransformComponent>();
TransformComponent *oldRootAttachedTo = prefabData.oldRootAttachedTo.getObject<TransformComponent>();
debugAssert(oldRoot != nullptr && newRoot != nullptr);
/* Just set the root component to world's actor root component. That means oldRoot must have been setup already. */
debugAssert(prefab->getRootComponent() == oldRoot);
prefab->setRootComponent(oldRoot, true);
/* First detach the oldRoot from oldRoot newRoot to avoid Cyclic Graph */
world->tfAttachmentChanged(oldRoot, nullptr);
/* In world attach the new root to its attached to component. */
EditorHelpers::attachComponent(newRoot, prefab->getAttachedToComp(newRoot));
/* Attach the actor to previously attached actor */
EditorHelpers::attachActor(world, prefab->getActorTemplate(), oldRootAttachedTo);
outEntriesCollector.addTransactedObj(prefab);
}
outEntriesCollector.addTransactedObj(world);
}
} // namespace cbe } // namespace cbe
cbe::ActorPrefab * cbe::ActorPrefab *
@@ -497,3 +1003,200 @@ void EditorHelpers::removeActorsFromWorld(cbe::World *world, ArrayView<cbe::Acto
ledger.endTransaction(transaction); ledger.endTransaction(transaction);
} }
void EditorHelpers::removeComponentFromPrefabs(
ArrayView<cbe::ActorPrefab *> prefabs, ArrayView<cbe::Object *> compPerPrefab, bool bLeafComp, bool bLogicComp,
cbe::Transaction *transaction
)
{
if (transaction == nullptr)
{
/* Do regular remove without any transaction */
for (uint32 i = 0; i < prefabs.size(); ++i)
{
removeComponentFromPrefab(prefabs[i], compPerPrefab[i]);
}
return;
}
cbe::World *world = prefabs.front()->getActorTemplate()->getWorld();
#if DEBUG_VALIDATIONS_ENABLED
if (world != nullptr)
{
for (cbe::ActorPrefab *prefab : prefabs)
{
debugAssert(world == prefab->getActorTemplate()->getWorld());
}
}
#endif
bool bAnyActorDetached = false;
cbe::RemoveCompsFromActorPrefabsAction rmCompsAction;
cbe::RemoveCompsAddBackCompAction addCompsAction;
rmCompsAction.world = world;
rmCompsAction.prefabs.reserve(prefabs.size());
addCompsAction.prefabs.reserve(prefabs.size());
for (uint32 i = 0; i < prefabs.size(); ++i)
{
cbe::ObjectTemplate *compTemplate = cbe::ActorPrefab::objectTemplateFromObj(compPerPrefab[i]);
debugAssert(compTemplate != nullptr);
cbe::RemoveCompsAddBackCompAction::PerPrefabData addBackData{
.prefab = cbe::ObjectPath(prefabs[i]),
.compName = compPerPrefab[i]->getObjectData().name,
};
if (cbe::ObjectTemplate *parentTemplate = compTemplate->getParentTemplate())
{
addBackData.compTemplate.emplace<cbe::ObjectPath>(parentTemplate);
}
else
{
addBackData.compTemplate.emplace<CBEClass>(compTemplate->getTemplateClass());
}
addCompsAction.prefabs.emplace_back(std::move(addBackData));
rmCompsAction.prefabs.emplace_back(cbe::RemoveCompsFromActorPrefabsAction::PerPrefabData{
.prefab = cbe::ObjectPath(prefabs[i]),
.compRemoved = cbe::ObjectPath(compPerPrefab[i]),
});
}
/* Fill the actions with attachment information if TransformComponent is removed */
if (!bLeafComp && !bLogicComp && world != nullptr)
{
for (uint32 i = 0; i < prefabs.size(); ++i)
{
cbe::TransformComponent *tfComp = static_cast<cbe::TransformComponent *>(compPerPrefab[i]);
auto tfToHierarchyIdxItr = world->compToTf.find(tfComp);
debugAssert(tfToHierarchyIdxItr != world->compToTf.cend());
for (cbe::World::TFHierarchyIdx hierarchyIdx : world->txHierarchy.getChildren(tfToHierarchyIdxItr->second, false))
{
cbe::TransformComponent *otherComp = world->txHierarchy[hierarchyIdx].component;
/* Actor is attached to this component gather it. */
if (cbe::ActorPrefab *otherPrefab = cbe::ActorPrefab::prefabFromComponent(otherComp); otherPrefab != prefabs[i])
{
bAnyActorDetached = true;
rmCompsAction.prefabs[i].attachedActors.emplace_back(otherPrefab->getActorTemplate());
}
}
}
}
cbe::RemoveCompsFromActorPrefabsAction *finalRmAction
= transaction->addCustomAction<cbe::RemoveCompsFromActorPrefabsAction>(nullptr, std::move(rmCompsAction));
/* Must be done after the setup component remove and world attachment action */
if (!bLeafComp && !bLogicComp && world != nullptr)
{
transaction->captureObjectBaseline(world);
}
cbe::ActorPrefabsBackupAction *prefabBackupAction = transaction->addCustomAction<cbe::ActorPrefabsBackupAction>(world);
prefabBackupAction->backupDir = EditorHelpers::getTransactionBackupDir(TCHAR("RemComp"));
prefabBackupAction->setEditingPrefabs(prefabs);
if (bAnyActorDetached)
{
transaction->editObject(
world, PropertyHelper::findField(cbe::World::staticType(), GET_MEMBER_ID_CHECKED(cbe::World, actorAttachedTo))
);
}
transaction->addCustomAction<cbe::RemoveCompsAddBackCompAction>(nullptr, std::move(addCompsAction));
/* Finally apply the remove */
cbe::TransactedEntriesCollector dummyCollector;
finalRmAction->apply(dummyCollector);
}
void EditorHelpers::setRootComponent(
ArrayView<cbe::ActorPrefab *> prefabs, ArrayView<cbe::TransformComponent *> newRootComps, cbe::Transaction *transaction
)
{
cbe::World *world = prefabs.front()->getActorTemplate()->getWorld();
#if DEBUG_VALIDATIONS_ENABLED
if (world != nullptr)
{
for (cbe::ActorPrefab *prefab : prefabs)
{
debugAssert(world == prefab->getActorTemplate()->getWorld());
}
}
#endif
std::vector<cbe::RootCompsReattachAction::PerPrefabData> prefabsData;
prefabsData.reserve(prefabs.size());
if (transaction != nullptr)
{
for (uint32 i = 0; i < prefabs.size(); ++i)
{
cbe::ActorPrefab *prefab = prefabs[i];
cbe::TransformComponent *newRoot = newRootComps[i];
cbe::TransformComponent *oldRootAttachedTo = nullptr;
cbe::TransformComponent *oldRoot = prefab->getRootComponent();
if (world != nullptr)
{
oldRootAttachedTo = oldRoot->getAttachedTo();
}
prefabsData.emplace_back(cbe::RootCompsReattachAction::PerPrefabData{
.prefab = cbe::ObjectPath(prefab),
.oldRoot = cbe::ObjectPath(oldRoot),
.newRoot = cbe::ObjectPath(newRoot),
.oldRootAttachedTo = cbe::ObjectPath(oldRootAttachedTo),
});
}
if (world != nullptr)
{
cbe::RootCompsReattachAction *postRevertAction = transaction->addCustomAction<cbe::RootCompsReattachAction>(nullptr);
postRevertAction->bAtApply = false;
postRevertAction->bAtRevert = true;
postRevertAction->prefabs = prefabsData;
postRevertAction->worldPtr = world;
}
cbe::ActorPrefabsBackupAction *prefabBackupAction = transaction->addCustomAction<cbe::ActorPrefabsBackupAction>(world);
prefabBackupAction->backupDir = EditorHelpers::getTransactionBackupDir(TCHAR("SetRootComp"));
prefabBackupAction->setEditingPrefabs(prefabs);
if (world != nullptr)
{
cbe::RootCompsReattachAction *postApplyAction = transaction->addCustomAction<cbe::RootCompsReattachAction>(nullptr);
postApplyAction->bAtApply = true;
postApplyAction->bAtRevert = false;
postApplyAction->prefabs = prefabsData;
postApplyAction->worldPtr = world;
}
}
/* Do regular root setup without any transaction */
for (uint32 i = 0; i < prefabs.size(); ++i)
{
cbe::ActorPrefab *prefab = prefabs[i];
cbe::TransformComponent *newRoot = newRootComps[i];
cbe::TransformComponent *oldRootAttachedTo = nullptr;
cbe::TransformComponent *oldRoot = prefab->getRootComponent();
if (world != nullptr)
{
oldRootAttachedTo = oldRoot->getAttachedTo();
}
prefab->setRootComponent(newRoot, true);
cbe::markPackageDirty(prefab);
if (world != nullptr)
{
/* Attach the old root to new root. */
debugAssert(prefab->getAttachedToComp(oldRoot) == newRoot);
/* First detach the newRoot from oldRoot or its child to avoid Cyclic Graph */
world->tfAttachmentChanged(newRoot, nullptr);
EditorHelpers::attachComponent(oldRoot, newRoot);
/* Attach the actor to previously attached actor */
EditorHelpers::attachActor(world, prefab->getActorTemplate(), oldRootAttachedTo);
}
}
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date December 2025 * \date December 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -13,9 +13,12 @@
#include <CBEEditorExports.h> #include <CBEEditorExports.h>
#include <Transaction/Transaction.hpp> #include <Transaction/Transaction.hpp>
#include <ICoreAssetEditor.hpp>
class CoreObjectsDB;
namespace cbe namespace cbe
{ {
class ActorPrefab;
/** /**
* @brief Asset save action can use this to transact editor saving asset to disk. * @brief Asset save action can use this to transact editor saving asset to disk.
@@ -51,4 +54,90 @@ private:
cbe::Object *obj; cbe::Object *obj;
cbe::Package *objPkg; cbe::Package *objPkg;
}; };
/**
* @brief Saves the given actor prefab's original form to a provided backup directory.
* At end transaction takes the post edit backup.
*/
class CBEEDITOR_EXPORT ActorPrefabsBackupAction final : public TransactionCustomAction
{
public:
ActorPrefabsBackupAction();
ActorPrefabsBackupAction(ActorPrefabsBackupAction &&other)
: backupDir(std::move(other.backupDir))
, actorPrefabPaths(std::move(other.actorPrefabPaths))
, objsDb(other.objsDb)
{
other.objsDb = nullptr;
}
~ActorPrefabsBackupAction();
void setEditingPrefabs(ArrayView<cbe::ActorPrefab *> prefabs);
/* TransactionCustomAction overrides */
void collectReferences(std::vector<Object *> &outObjects) const final;
void apply(TransactedEntriesCollector &outEntriesCollector) final;
void revert(TransactedEntriesCollector &outEntriesCollector) final;
bool isValid() const final;
void endTransaction() final;
/* Overrides ends */
public:
String backupDir;
private:
std::vector<cbe::ObjectPath> actorPrefabPaths;
const CoreObjectsDB *objsDb;
};
class AssetEditorTxnEventAction final : public TransactionCustomAction
{
public:
using EventCb = SingleCastEvent<
AssetEditorTxnEventAction, void, const ICoreAssetEditorRef & /* editorRef */, uint64 /* userData */, bool /* bIsApply */>;
AssetEditorTxnEventAction(ICoreAssetEditorRef inEditorRef, const ObjectPath &inObj, EventCb &&inCallback, uint64 inUserData)
: editorRef(inEditorRef)
, validationObject(inObj)
, callback(inCallback)
, userData(inUserData)
{}
AssetEditorTxnEventAction(AssetEditorTxnEventAction &&other)
: editorRef(std::move(other.editorRef))
, validationObject(std::move(other.validationObject))
, callback(std::move(other.callback))
, userData(other.userData)
{
other.userData = 0;
}
~AssetEditorTxnEventAction() = default;
/* TransactionCustomAction overrides */
void collectReferences(std::vector<Object *> &) const final {}
void apply(TransactedEntriesCollector &) final
{
if (callback.isBound())
{
callback.invoke(editorRef, userData, true);
}
}
void revert(TransactedEntriesCollector &) final
{
if (callback.isBound())
{
callback.invoke(editorRef, userData, false);
}
}
bool isValid() const final { return validationObject.isValid(); }
/* Overrides ends */
private:
ICoreAssetEditorRef editorRef;
ObjectPath validationObject;
EventCb callback;
uint64 userData;
};
} // namespace cbe } // namespace cbe

View File

@@ -4,15 +4,39 @@
* \author Jeslas * \author Jeslas
* \date August 2022 * \date August 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2024 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
#pragma once #pragma once
#include "Types/Delegates/Delegate.h" #include <Types/Delegates/Delegate.h>
#include <CBEObjectTypes.h>
#include <Types/Colors.h>
class ImGuiDrawInterface; class ImGuiDrawInterface;
using ImGuiDrawInterfaceCallback = SimpleDelegate; using ImGuiDrawInterfaceCallback = SimpleDelegate;
namespace cbe
{
struct EdClassCacheDrawInfo
{
/* Not using constants header to reduce include pollution */
std::array<AChar, 4> classInitials;
Color typeColor;
};
struct EdClassCacheList
{
static_assert(!IsTCharWide::value, "Wide character support is not optimized here! It will work but performs bad");
std::vector<CBEClass> classList;
std::vector<EdClassCacheDrawInfo> classDrawInfo;
String filter;
BitArray<uint64> filteredBits;
};
} // namespace cbe

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -65,6 +65,8 @@ void ObjectsDetailsDrawer::drawWidgets(const DrawWidgetArgs &args) const
if (!args.bDrawHeader || cbe::ImGuiHelpers::collapsingHeaderInTable(headerName, PROPS_DETAIL_OBJ_HEADER_FLAGS)) if (!args.bDrawHeader || cbe::ImGuiHelpers::collapsingHeaderInTable(headerName, PROPS_DETAIL_OBJ_HEADER_FLAGS))
{ {
ImGui::PushID(modelObjData.sid.getID());
/* Draw all class level customizations first */ /* Draw all class level customizations first */
for (const CustomizerContext &cntx : classCustomizers) for (const CustomizerContext &cntx : classCustomizers)
{ {
@@ -122,6 +124,8 @@ void ObjectsDetailsDrawer::drawWidgets(const DrawWidgetArgs &args) const
.bFilterPassed = bFilterPassed, .bFilterPassed = bFilterPassed,
}); });
} }
ImGui::PopID();
} }
} }
@@ -295,7 +299,7 @@ bool StructDetailsDrawer::drawWidgets(const DrawWidgetArgs &args) const
String filter = args.filter; String filter = args.filter;
filter.toLower(); filter.toLower();
LOG_WARN_C(!filter.empty(), "StructDetailsDrawer", "Struct details filtering is not implemented yet. Filter {}", args.filter); CBE_LOG_WARN_C(!filter.empty(), "StructDetailsDrawer", "Struct details filtering is not implemented yet. Filter {}", args.filter);
if (headerName.empty() || cbe::ImGuiHelpers::collapsingHeaderInTable(headerName, PROPS_DETAIL_OBJ_HEADER_FLAGS)) if (headerName.empty() || cbe::ImGuiHelpers::collapsingHeaderInTable(headerName, PROPS_DETAIL_OBJ_HEADER_FLAGS))
{ {

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -15,6 +15,7 @@
#include <CBEAssetManager.hpp> #include <CBEAssetManager.hpp>
#include <Property/Property.h> #include <Property/Property.h>
#include <Classes/ActorPrefab.hpp> #include <Classes/ActorPrefab.hpp>
#include <EditorTypes.h>
namespace cbe namespace cbe
{ {
@@ -462,4 +463,98 @@ void EditorWidgetsHelper::drawPackageIcon(Rect bb, std::string_view classInitial
); );
} }
CBEClass EditorWidgetsHelper::drawClassMenuList(EdClassCacheList &classList, float popupItemWidth, bool bShowFilter)
{
if (bShowFilter)
{
const float filterStartPos = ImGui::GetCursorStartPos().y;
ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
if (cbe::ImGuiHelpers::inputTextWithHint(
"###AssetFilterText", "Filter Asset", &classList.filter,
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EscapeClearsAll | ImGuiInputTextFlags_NoHorizontalScroll
))
{
/* Scroll back to filter every time filter is modified. */
ImGui::SetScrollFromPosY(filterStartPos, 0.0f);
const String filter = classList.filter.toLowerCopy();
for (SizeT i = 0; i < classList.classList.size(); ++i)
{
String className = String(classList.classList[i]->nameString);
className.toLower();
classList.filteredBits[i] = filter.empty() || className.find(filter) != String::npos;
}
}
/* Scroll to text filter the moment it becomes active. */
if (ImGui::IsItemActivated())
{
ImGui::SetScrollFromPosY(filterStartPos, 0.0f);
}
}
ImDrawList *drawList = ImGui::GetWindowDrawList();
const ImGuiStyle &imguiStyle = ImGui::GetStyle();
const Vector2 selectableSize{
popupItemWidth,
Math::max(wg_consts::ASSET_SELECTOR_ICON_SIZE, 2 * ImGui::GetTextLineHeight()) + (2 * imguiStyle.FramePadding.y),
};
CBEClass selectedClazz = nullptr;
for (uint64 i = 0; i < classList.classList.size(); ++i)
{
if (!classList.filteredBits[i])
{
continue;
}
CBEClass clazz = classList.classList[i];
const EdClassCacheDrawInfo &clazzDrawInf = classList.classDrawInfo[i];
ImGui::PushID(std::bit_cast<int32>(clazz->name.getID()));
const Vector2 contentStartPos = ImGui::GetCursorScreenPos();
const bool bSelected = ImGui::Selectable("", false, ImGuiSelectableFlags_None, selectableSize);
/* Draw Package icon, center align in Y */
const float iconAlignOffsetY = (selectableSize.y - (2 * imguiStyle.FramePadding.y) - wg_consts::ASSET_SELECTOR_ICON_SIZE) * 0.5f;
const Vector2 iconRectStartPos = contentStartPos + Vector2(imguiStyle.FramePadding) + Vector2(0, iconAlignOffsetY);
const Vector2 iconRectEndPos = iconRectStartPos + wg_consts::ASSET_SELECTOR_ICON_SIZE;
cbe::EditorWidgetsHelper::drawPackageIcon(
{ iconRectStartPos, iconRectEndPos }, clazzDrawInf.classInitials.data(), clazzDrawInf.typeColor,
wg_consts::ASSET_SELECTOR_ICON_FONT_SCALE
);
/* Center align text, some logic can be moved out of loop */
const float labelStartX = iconRectEndPos.x + imguiStyle.ItemInnerSpacing.x;
const float labelWidth = selectableSize.x - (labelStartX - contentStartPos.x) - imguiStyle.FramePadding.x;
const float labelEndX = labelStartX + labelWidth;
const float labelHeight = ImGui::CalcTextSize(TCHAR_TO_UTF8(clazz->nameString).data(), nullptr, false, labelWidth).y;
const float labelAlignOffsetY = (selectableSize.y - (2 * imguiStyle.FramePadding.y) - labelHeight) * 0.5f;
const float labelStartY = contentStartPos.y + imguiStyle.FramePadding.y + labelAlignOffsetY;
const float labelEndY = labelStartY + labelHeight;
const ImVec4 clipRect{
labelStartX,
labelStartY,
labelEndX,
labelEndY,
};
drawList->AddText(
ImGui::GetFont(), ImGui::GetFontSize(), ImVec2(clipRect.x, clipRect.y), ImGui::GetColorU32(ImGuiCol_Text),
TCHAR_TO_UTF8(clazz->nameString).data(), nullptr, labelWidth, &clipRect
);
ImGui::PopID();
if (bSelected)
{
selectedClazz = clazz;
}
}
return selectedClazz;
}
} // namespace cbe } // namespace cbe

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -12,6 +12,7 @@
#pragma once #pragma once
#include <CBEEditorExports.h> #include <CBEEditorExports.h>
#include <CBEObjectTypes.h>
#include <Widgets/WgEditorConstants.hpp> #include <Widgets/WgEditorConstants.hpp>
#include <String/String.h> #include <String/String.h>
#include <Widgets/ImGui/CbeImGui.hpp> #include <Widgets/ImGui/CbeImGui.hpp>
@@ -24,6 +25,7 @@ class Transform3D;
namespace cbe namespace cbe
{ {
struct EdClassCacheList;
class Object; class Object;
class AssetManager; class AssetManager;
enum class ECloseConfirmationState : uint8 enum class ECloseConfirmationState : uint8
@@ -87,6 +89,8 @@ public:
/* Only draws the icon using drawlist does not add any bb items. */ /* Only draws the icon using drawlist does not add any bb items. */
static void drawPackageIcon(Rect bb, std::string_view classInitials, Color bgColor, float fontScale = 1.0f); static void drawPackageIcon(Rect bb, std::string_view classInitials, Color bgColor, float fontScale = 1.0f);
static CBEClass drawClassMenuList(EdClassCacheList &classList, float popupItemWidth, bool bShowFilter);
private: private:
EditorWidgetsHelper() = default; EditorWidgetsHelper() = default;
}; };

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -39,6 +39,8 @@
#define CBE_MAT_ICON_COPY "\xEE\x85\x8D" #define CBE_MAT_ICON_COPY "\xEE\x85\x8D"
/* \ue14f */ /* \ue14f */
#define CBE_MAT_ICON_PASTE "\xEE\x85\x8F" #define CBE_MAT_ICON_PASTE "\xEE\x85\x8F"
/* \ue5c9 */
#define CBE_MAT_ICON_CANCEL "\xEE\x97\x89"
/* \ue2c7 */ /* \ue2c7 */
#define CBE_MAT_ICON_FOLDER_CLOSED "\xEE\x8B\x87" #define CBE_MAT_ICON_FOLDER_CLOSED "\xEE\x8B\x87"
@@ -103,6 +105,7 @@ constexpr const AChar *REMOVE = CBE_MAT_ICON_REMOVE;
constexpr const AChar *CUT = CBE_MAT_ICON_CUT; constexpr const AChar *CUT = CBE_MAT_ICON_CUT;
constexpr const AChar *COPY = CBE_MAT_ICON_COPY; constexpr const AChar *COPY = CBE_MAT_ICON_COPY;
constexpr const AChar *PASTE = CBE_MAT_ICON_PASTE; constexpr const AChar *PASTE = CBE_MAT_ICON_PASTE;
constexpr const AChar *CANCEL = CBE_MAT_ICON_CANCEL;
constexpr const AChar *FOLDER_CLOSED = CBE_MAT_ICON_FOLDER_CLOSED; constexpr const AChar *FOLDER_CLOSED = CBE_MAT_ICON_FOLDER_CLOSED;
constexpr const AChar *FOLDER_OPEN = CBE_MAT_ICON_FOLDER_OPEN; constexpr const AChar *FOLDER_OPEN = CBE_MAT_ICON_FOLDER_OPEN;
@@ -150,6 +153,17 @@ constexpr const uint32 DEFAULT_LAYER_DEPTH = 1;
constexpr ImGuiTreeNodeFlags PROP_TREE_NODE_FLAGS constexpr ImGuiTreeNodeFlags PROP_TREE_NODE_FLAGS
= ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_LabelSpanAllColumns | ImGuiTreeNodeFlags_Framed; = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_LabelSpanAllColumns | ImGuiTreeNodeFlags_Framed;
constexpr ImGuiChildFlags INLINE_FITTING_CHILD_FLAGS = ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_ResizeY | ImGuiChildFlags_Borders; constexpr ImGuiChildFlags INLINE_FITTING_CHILD_FLAGS = ImGuiChildFlags_AutoResizeY | ImGuiChildFlags_ResizeY | ImGuiChildFlags_Borders;
constexpr const uint8 CLASS_INITIALS_COUNT = 3;
constexpr const float ASSET_SELECTOR_ICON_SIZE = 40;
constexpr const float ASSET_SELECTOR_ICON_FONT_SCALE = 1.5f;
constexpr const float MENUITEM_WIDTH = 200.f;
constexpr Vector2 HMODAL_POPUP_SIZE = { 500, 200 };
constexpr Vector2 VMODAL_POPUP_SIZE = { 300, 500 };
constexpr const float ANIMATION_TIME_SCALE = 1.0f;
} // namespace wg_consts } // namespace wg_consts
namespace style namespace style
@@ -173,6 +187,9 @@ constexpr static const Color HOVER_COLOR = ColorConst::YELLOW;
constexpr static const Color ACTIVE_COLOR = ColorConst::YELLOW4; constexpr static const Color ACTIVE_COLOR = ColorConst::YELLOW4;
constexpr static const Color DISABLED_COLOR = Color(160, 160, 160, 100); constexpr static const Color DISABLED_COLOR = Color(160, 160, 160, 100);
constexpr static const Color ED_COLOR_PRIMARY = Color(51, 105, 173);
constexpr static const Color ED_COLOR_SECONDARY = Color(35, 67, 108);
constexpr static const Color NEUTRAL_COLOR_PRIMARY = ColorConst::WHITE; constexpr static const Color NEUTRAL_COLOR_PRIMARY = ColorConst::WHITE;
constexpr static const Color NEUTRAL_COLOR_SECONDARY = ColorConst::WHEAT; constexpr static const Color NEUTRAL_COLOR_SECONDARY = ColorConst::WHEAT;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2025 * \date June 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -27,12 +27,14 @@ node positionOffset
node diffuseColor node diffuseColor
{ {
return CBE_MAT_SAMPLE_TEXTURE(color, CBE_TEXTURE_COORD()); return
CBE_MAT_SAMPLE_TEXTURE(color, CBE_TEXTURE_COORD());
} }
node normalOffset node normalOffset
{ {
return (CBE_MAT_SAMPLE_TEXTURE(normal, CBE_TEXTURE_COORD()).xyz - 0.5) * 2; return (
CBE_MAT_SAMPLE_TEXTURE(normal, CBE_TEXTURE_COORD()).xyz - 0.5) * 2;
} }

View File

@@ -7,8 +7,6 @@ set(cpp_target_sources
Private/AssetEditors/IEngineAssetEditor.hpp Private/AssetEditors/IEngineAssetEditor.hpp
Private/AssetEditors/MaterialInstanceAssetEditor.cpp Private/AssetEditors/MaterialInstanceAssetEditor.cpp
Private/AssetEditors/MaterialInstanceAssetEditor.hpp Private/AssetEditors/MaterialInstanceAssetEditor.hpp
Private/AudioImporter.cpp
Private/AudioImporter.hpp
Private/CBEEditorModule.cpp Private/CBEEditorModule.cpp
Private/DetailCustomizers/GenericFieldCustomizer.cpp Private/DetailCustomizers/GenericFieldCustomizer.cpp
Private/DetailCustomizers/GenericFieldCustomizer.hpp Private/DetailCustomizers/GenericFieldCustomizer.hpp
@@ -16,10 +14,16 @@ set(cpp_target_sources
Private/DetailCustomizers/MaterialInstanceCustomizer.hpp Private/DetailCustomizers/MaterialInstanceCustomizer.hpp
Private/DetailCustomizers/TransformComponentCustomizer.cpp Private/DetailCustomizers/TransformComponentCustomizer.cpp
Private/DetailCustomizers/TransformComponentCustomizer.hpp Private/DetailCustomizers/TransformComponentCustomizer.hpp
Private/ObjStaticMeshImporter.cpp Private/Importers/AssimpImporter.cpp
Private/StaticMeshImporter.hpp Private/Importers/AssimpImporter.hpp
Private/Texture2DImporter.cpp Private/Importers/AudioImporter.cpp
Private/Texture2DImporter.hpp Private/Importers/AudioImporter.hpp
Private/Importers/ObjStaticMeshImporter.cpp
Private/Importers/StaticMeshImporter.hpp
Private/Importers/Texture2DImporter.cpp
Private/Importers/Texture2DImporter.hpp
Private/Payloads/EditorDragPayloads.cpp
Private/Payloads/EditorDragPayloads.hpp
Private/ThirdParties/StbWrapper.cpp Private/ThirdParties/StbWrapper.cpp
Private/ThirdParties/StbWrapper.hpp Private/ThirdParties/StbWrapper.hpp
Private/ViewportDrawers/TransformComponentViewportDrawer.cpp Private/ViewportDrawers/TransformComponentViewportDrawer.cpp

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2022 * \date July 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -208,7 +208,7 @@ void EditorCoreModule::registerStructDetailsCustomizer(cbe::StructDetailsCustomi
auto itr = structCustomizers.find(customizer->getCustomizingClass()); auto itr = structCustomizers.find(customizer->getCustomizingClass());
if (itr != structCustomizers.cend()) if (itr != structCustomizers.cend())
{ {
LOG_ERROR(IEditorCore::LOG_CATEGORY, "Customizer exists for struct {}", customizer->getCustomizingClass()->nameString); CBE_LOG_ERROR(IEditorCore::LOG_CATEGORY, "Customizer exists for struct {}", customizer->getCustomizingClass()->nameString);
return; return;
} }
@@ -243,7 +243,7 @@ void EditorCoreModule::registerAssetFactory(cbe::ICoreAssetFactory *editorFactor
auto itr = assetFactories.find(editorFactory->getAssetClass()); auto itr = assetFactories.find(editorFactory->getAssetClass());
if (itr != assetFactories.cend()) if (itr != assetFactories.cend())
{ {
LOG_ERROR(IEditorCore::LOG_CATEGORY, "Asset editor factory exists for asset type {}", editorFactory->getAssetClass()->nameString); CBE_LOG_ERROR(IEditorCore::LOG_CATEGORY, "Asset editor factory exists for asset type {}", editorFactory->getAssetClass()->nameString);
return; return;
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2022 * \date July 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -12,6 +12,7 @@
#pragma once #pragma once
#include <EditorCoreExports.h> #include <EditorCoreExports.h>
#include <Types/Delegates/Delegate.h>
#include <Types/Platform/PlatformAssertionErrors.h> #include <Types/Platform/PlatformAssertionErrors.h>
#include <Memory/SmartPointers.h> #include <Memory/SmartPointers.h>
#include <CBEObjectTypes.h> #include <CBEObjectTypes.h>
@@ -31,9 +32,16 @@ struct ImportOption
/* Path to directory in which to store the package relative to importContentPath. */ /* Path to directory in which to store the package relative to importContentPath. */
String relativeDirPath; String relativeDirPath;
/* Callback with progress count */
SingleCastDelegate<void, uint32, StringView> progressCb;
/* Before prepare */
uint32 prepareProgressCount;
void *optionsStruct; void *optionsStruct;
CBEClass optionsStructType; CBEClass optionsStructType;
/* After prepare */
uint32 importProgressCount;
/* This could be null if the importer do not have any parsed options based on the asset itself. */ /* This could be null if the importer do not have any parsed options based on the asset itself. */
void *cntxOptionsStruct; void *cntxOptionsStruct;
CBEClass cntxOptionsStructType; CBEClass cntxOptionsStructType;
@@ -42,9 +50,16 @@ struct ImportOption
class AssetImporterBase class AssetImporterBase
{ {
public: public:
static constexpr const TChar *MATERIAL_INST_PREFIX = TCHAR("MI_");
static constexpr const TChar *TEXTURE_PREFIX = TCHAR("T_");
static constexpr const TChar *AUDIO_SRC_PREFIX = TCHAR("AS_");
static constexpr const TChar *STATIC_MESH_PREFIX = TCHAR("SM_");
static constexpr const TChar *WORLD_PREFIX = TCHAR("W_");
struct ImportContextDtor struct ImportContextDtor
{ {
AssetImporterBase *importer; AssetImporterBase *importer;
constexpr void operator() (void *ptr) const noexcept constexpr void operator() (void *ptr) const noexcept
{ {
if (importer != nullptr) if (importer != nullptr)
@@ -70,7 +85,8 @@ public:
using ImportResult = std::variant<ImportError, ImportSuccess>; using ImportResult = std::variant<ImportError, ImportSuccess>;
public: public:
constexpr static const uint32 AllocSlotCount = 2; AssetImporterBase() = default;
MAKE_TYPE_NONCOPY_NONMOVE(AssetImporterBase)
virtual ~AssetImporterBase() = default; virtual ~AssetImporterBase() = default;
virtual bool supportsImporting(ImportOption & /*inOutOptions*/) { return false; } virtual bool supportsImporting(ImportOption & /*inOutOptions*/) { return false; }
@@ -89,9 +105,6 @@ public:
return err; return err;
} }
// TODO(Jeslas) : Remove below once new import apis are ready and UI available.
virtual std::optional<std::vector<cbe::Object *>> tryImporting(const ImportOption & /*importOptions*/) const { return {}; }
protected: protected:
virtual void destructImportContext(void *cntx) = 0; virtual void destructImportContext(void *cntx) = 0;
}; };

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date September 2025 * \date September 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -70,14 +70,81 @@ void ICoreAssetEditor::closeEditor()
{ {
factory->onCloseAssetEditor(this); factory->onCloseAssetEditor(this);
} }
editingAsset = nullptr;
asset = nullptr;
} }
MAKE_ENUM_ARITHMETIC_OPS(ECoreEdMessageType) MAKE_ENUM_ARITHMETIC_OPS(ECoreEdMessageType)
void ICoreAssetEditor::edTick(float deltaTime) void ICoreAssetEditor::edTick(float deltaTime)
{
pumpMessages();
if (editingAsset != nullptr)
{
bEditingAssetDirty = cbe::isPackageDirty(editingAsset);
}
else
{
bEditingAssetDirty = false;
}
onEdTick(deltaTime);
}
void ICoreAssetEditor::pushMessage(MessageData msgDat, ECoreEdMessageType msgType)
{
msgDat.lastHandleState = MessageResponse::None;
if (!std::holds_alternative<NullType>(messages[msgType].msg))
{
const TChar *editObjName = asset != nullptr ? asset->getObjectData().name : TCHAR("Editor");
if (messages[msgType].lastHandleState == MessageResponse::NotHandled)
{
alertOncef(
messages[msgType].lastHandleState != MessageResponse::NotHandled, "Message of type {} has no handlers in object {}", msgType,
editObjName
);
}
return;
}
bAnyMsgs = true;
messages[msgType] = msgDat;
}
void ICoreAssetEditor::pushMessage(ECoreEdMessageType msgType)
{
const MessageData msgDat{ .msg = EmptyType{} };
pushMessage(msgDat, msgType);
}
void ICoreAssetEditor::pushMessageOverwrite(MessageData msgDat, ECoreEdMessageType msgType)
{
msgDat.lastHandleState = MessageResponse::None;
if (!std::holds_alternative<NullType>(messages[msgType].msg))
{
const TChar *editObjName = asset != nullptr ? asset->getObjectData().name : TCHAR("Editor");
CBE_LOG_WARN_C(
messages[msgType].lastHandleState == MessageResponse::None, IEditorCore::LOG_CATEGORY,
"Message of type {}[Last state: {}] is being overwritten in object {}", msgType, messages[msgType].lastHandleState, editObjName
);
if (messages[msgType].lastHandleState == MessageResponse::NotHandled)
{
alertOncef(
messages[msgType].lastHandleState != MessageResponse::NotHandled, "Message of type {} has no handlers in object {}", msgType,
editObjName
);
}
}
bAnyMsgs = true;
messages[msgType] = msgDat;
}
void ICoreAssetEditor::pumpMessages()
{ {
/* Process until all the messages are cleared. Break when there is sticky message to allow app to be responsive. */ /* Process until all the messages are cleared. Break when there is sticky message to allow app to be responsive. */
while (bAnyMsgs) while (bAnyMsgs)
{ {
/* Immediately clear the any messages flags to allow processing messages sent by another message. */
bAnyMsgs = false;
bool bAnySticky = false; bool bAnySticky = false;
std::array<MessageData, CoreEdMessage_MaxCount> msgsCopy = messages; std::array<MessageData, CoreEdMessage_MaxCount> msgsCopy = messages;
@@ -110,7 +177,7 @@ void ICoreAssetEditor::edTick(float deltaTime)
/* Since messages will only have msgs enqueued in current frame. */ /* Since messages will only have msgs enqueued in current frame. */
debugAssert(messages[msgType].lastHandleState == MessageResponse::None); debugAssert(messages[msgType].lastHandleState == MessageResponse::None);
const TChar *editObjName = asset != nullptr ? asset->getObjectData().name : TCHAR("Editor"); const TChar *editObjName = asset != nullptr ? asset->getObjectData().name : TCHAR("Editor");
LOG_WARN( CBE_LOG_WARN(
IEditorCore::LOG_CATEGORY, "Message of type {}[State: {}] is being dropped in object {}", msgType, IEditorCore::LOG_CATEGORY, "Message of type {}[State: {}] is being dropped in object {}", msgType,
msgsCopy[msgType].lastHandleState, editObjName msgsCopy[msgType].lastHandleState, editObjName
); );
@@ -125,49 +192,12 @@ void ICoreAssetEditor::edTick(float deltaTime)
/* If anything became sticky */ /* If anything became sticky */
break; break;
} }
else
{
bAnyMsgs = false;
}
} }
if (editingAsset != nullptr)
{
bEditingAssetDirty = cbe::isPackageDirty(editingAsset);
}
onEdTick(deltaTime);
}
void ICoreAssetEditor::pushMessage(MessageData msgDat, ECoreEdMessageType msgType)
{
msgDat.lastHandleState = MessageResponse::None;
if (!std::holds_alternative<NullType>(messages[msgType].msg))
{
const TChar *editObjName = asset != nullptr ? asset->getObjectData().name : TCHAR("Editor");
LOG_WARN_C(
messages[msgType].lastHandleState == MessageResponse::None, IEditorCore::LOG_CATEGORY,
"Message of type {}[Last state: {}] is being overwritten in object {}", msgType, messages[msgType].lastHandleState, editObjName
);
if (messages[msgType].lastHandleState == MessageResponse::NotHandled)
{
alertOncef(
messages[msgType].lastHandleState != MessageResponse::NotHandled, "Message of type {} has no handlers in object {}", msgType,
editObjName
);
}
}
bAnyMsgs = true;
messages[msgType] = msgDat;
}
void ICoreAssetEditor::pushMessage(ECoreEdMessageType msgType)
{
const MessageData msgDat{ .msg = EmptyType{} };
pushMessage(msgDat, msgType);
} }
void ICoreAssetEditor::clearReferences(ArrayView<cbe::Object *> deletedObjects) void ICoreAssetEditor::clearReferences(ArrayView<cbe::Object *> deletedObjects)
{ {
debugAssertf(deletedObjects.empty(), "References clearing after asset load is unhandled!"); debugAssertf(editingAsset == nullptr || deletedObjects.empty(), "References clearing after asset load is unhandled!");
} }
void ICoreAssetEditor::collectReferences(std::vector<cbe::Object *> &outObjects) const void ICoreAssetEditor::collectReferences(std::vector<cbe::Object *> &outObjects) const
{ {

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date September 2025 * \date September 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -107,7 +107,7 @@ public:
* @brief If asset factory supports custom additional asset creation menus must be drawn here. * @brief If asset factory supports custom additional asset creation menus must be drawn here.
* @return Callback that must used to create the asset instead of regular create callback. * @return Callback that must used to create the asset instead of regular create callback.
*/ */
virtual AssetCreateCustomCallback drawCustomCreateMenus() const { return {}; } virtual AssetCreateCustomCallback drawCustomCreateMenus() { return {}; }
/* Returns true if committed else default handling must be used. /* Returns true if committed else default handling must be used.
* Caller must handle the false case. * Caller must handle the false case.
@@ -154,7 +154,7 @@ enum ECoreEdMessageType : uint8
CoreEdMessage_SelectionTransformed, ///< When selection gets moved interactively. CoreEdMessage_SelectionTransformed, ///< When selection gets moved interactively.
CoreEdMessage_Custom, ///< Custom messages from this and beyond. CoreEdMessage_Custom, ///< Custom messages from this and beyond.
CoreEdMessage_MaxCount = std::numeric_limits<uint8>::max(), CoreEdMessage_MaxCount = std::numeric_limits<uint8>::max(),
CoreEdMessage_Begin = CoreEdMessage_RefreshEd, CoreEdMessage_Begin = CoreEdMessage_ObjsEdited,
CoreEdMessage_End = CoreEdMessage_MaxCount, CoreEdMessage_End = CoreEdMessage_MaxCount,
}; };
struct MessageResponse struct MessageResponse
@@ -169,9 +169,15 @@ struct MessageResponse
EType state; EType state;
}; };
enum EEdRefreshType
{
VisualOnly, ///< Refresh does not changes underlying data, only draw data gets refreshed.
FullReset, ///< Full refresh where the visualizing data gets fetched and entire draw data gets regenerated.
};
struct MessageData struct MessageData
{ {
using ObjectsList = std::vector<cbe::Object *>; using ObjectsList = std::vector<WeakObjectPtr>;
struct SubSelectionData struct SubSelectionData
{ {
std::vector<WeakObjectPtr> objs; std::vector<WeakObjectPtr> objs;
@@ -179,7 +185,7 @@ struct MessageData
}; };
/* EmptyType is if the message has no data associated to it like refresh. /* EmptyType is if the message has no data associated to it like refresh.
* Pointer for Custom messages. */ * Pointer for Custom messages. */
using DataType = std::variant<NullType, ObjectsList, SubSelectionData, void *, EmptyType>; using DataType = std::variant<NullType, ObjectsList, SubSelectionData, void *, EEdRefreshType, EmptyType>;
/* Data to identify who the issuer is. This is to ensure messages do not get enqueued cyclically. */ /* Data to identify who the issuer is. This is to ensure messages do not get enqueued cyclically. */
uint64 issuerData = 0; uint64 issuerData = 0;
/* If the receiver data is valid the one who handle the data must also remove it. */ /* If the receiver data is valid the one who handle the data must also remove it. */
@@ -201,15 +207,29 @@ public:
Object *getAsset() const { return asset; } Object *getAsset() const { return asset; }
Object *getEditingAsset() const { return editingAsset; } Object *getEditingAsset() const { return editingAsset; }
bool isAssetDirt() const { return bEditingAssetDirty; } bool isAssetDirty() const { return bEditingAssetDirty; }
ICoreAssetFactory *getOwningFactory() const { return factory; } ICoreAssetFactory *getOwningFactory() const { return factory; }
/* Called when asset editor is about to be closed. */ /* Called when asset editor is about to be closed. */
void closeEditor(); void closeEditor();
void edTick(float deltaTime); void edTick(float deltaTime);
/**
* @brief Pushes the message in without overwrite.
* Decided to keep using single message per frame rather than queue since we would not need more than that right now.
*
* @param msgDat Message data
* @param msgType Message type
*/
void pushMessage(MessageData msgDat, ECoreEdMessageType msgType); void pushMessage(MessageData msgDat, ECoreEdMessageType msgType);
void pushMessage(ECoreEdMessageType msgType); void pushMessage(ECoreEdMessageType msgType);
void pushMessageOverwrite(MessageData msgDat, ECoreEdMessageType msgType);
/**
* @brief Goes through message queue and sends messages. Can be called whenever necessary to empty the msg queue.
* Also gets called once every frame.
*/
void pumpMessages();
/* ISelfRegisterReferenceCollector overrides */ /* ISelfRegisterReferenceCollector overrides */
void clearReferences(ArrayView<cbe::Object *> deletedObjects) final; void clearReferences(ArrayView<cbe::Object *> deletedObjects) final;
@@ -220,7 +240,7 @@ public:
/* Interfaces */ /* Interfaces */
public: public:
virtual cbe::TransactionsLedger *getTransactionLedger() { return nullptr; } virtual cbe::TransactionsLedger *getTransactionLedger() { return nullptr; }
/* Sends message to the asset editor */ /* Sends message to the asset editor. Use this if want to send messages immediately */
virtual MessageResponse sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType) virtual MessageResponse sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType)
{ {
return MessageResponse{ .state = MessageResponse::NotHandled }; return MessageResponse{ .state = MessageResponse::NotHandled };

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date December 2025 * \date December 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -267,6 +267,7 @@ class TfGizmoControllableProxy : public ViewportImDebugDrawProxy
CBE_SIMPLE_RTTI_CLASS(TfGizmoControllableProxy, ViewportImDebugDrawProxy) CBE_SIMPLE_RTTI_CLASS(TfGizmoControllableProxy, ViewportImDebugDrawProxy)
public: public:
virtual Transform3D getRelativeTransform() const { return getTransform(); }
/* Absolute in world space */ /* Absolute in world space */
virtual Transform3D getTransform() const = 0; virtual Transform3D getTransform() const = 0;
virtual void setTransform(const Transform3D &worldTf) = 0; virtual void setTransform(const Transform3D &worldTf) = 0;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -58,7 +58,7 @@ uint32 ApplicationModule::getThreadingConstraints()
IApplicationModule *IApplicationModule::get() IApplicationModule *IApplicationModule::get()
{ {
static WeakModulePtr appModule = ModuleManager::get()->getOrLoadModule(TCHAR("Application")); const static WeakModulePtr appModule = ModuleManager::get()->getOrLoadModule(TCHAR("Application"));
if (appModule.expired()) if (appModule.expired())
{ {
return nullptr; return nullptr;
@@ -77,22 +77,22 @@ void IApplicationModule::startAndRun(CbeApplication *app, const AppCreateInfo &a
/* Needs to be parsed asap. Help command line will not reach this point at all. */ /* Needs to be parsed asap. Help command line will not reach this point at all. */
if (!ProgramCmdLine::get().parse(app->getCmdLine())) if (!ProgramCmdLine::get().parse(app->getCmdLine()))
{ {
LOG_ERROR("Application", "Invalid command line"); CBE_LOG_ERROR("Application", "Invalid command line");
ProgramCmdLine::get().printCommandLine(); ProgramCmdLine::get().printCommandLine();
} }
ProgramCmdLine::get().setProgramDescription(TCHAR("Cranberry application - ") + appCI.applicationName); ProgramCmdLine::get().setProgramDescription(TCHAR("Cranberry application - ") + appCI.applicationName);
if (ProgramCmdLine::get().hasArg(CMDLINE_PROFILESTARTUP)) if (ProgramCmdLine::get().hasArg(CMDLINE_PROFILESTARTUP))
{ {
LOG("Application", "Waiting for profiler..."); CBE_LOG("Application", "Waiting for profiler...");
StopWatch waitTime; StopWatch waitTime;
CBEProfiler::waitForConnection(); CBEProfiler::waitForConnection();
waitTime.stop(); waitTime.stop();
LOG("Application", "Profiler connected in {:.3} secs", waitTime.duration()); CBE_LOG("Application", "Profiler connected in {:.3} secs", waitTime.duration());
} }
Logger::startLoggingTime(); cbe::Logger::startLoggingTime();
/* Not using stack space as windowing manager and job system tends to become huge and not want to use up much stack space. */ /* Not using stack space as windowing manager and job system tends to become huge and not want to use up much stack space. */
static_cast<ApplicationModule *>(this)->app = app; static_cast<ApplicationModule *>(this)->app = app;
@@ -132,40 +132,43 @@ void IApplicationModule::startAndRun(CbeApplication *app, const AppCreateInfo &a
); );
} }
/* Pre app start initialization */ /* Pre-app start initialization */
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("3: App")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("3: App"));
Logger::flushStream(); cbe::Logger::flushStream();
LOG("Application", "{} application start", appCI.applicationName); CBE_LOG("Application", "{} application start", appCI.applicationName);
app->startApp(); app->startApp();
} }
/* Post Init */ /* Post-Init */
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("4: Post App")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("4: Post App"));
ModuleManager::get()->progressLoadingStage(ModLoadingStage_PostEngineStart); ModuleManager::get()->progressLoadingStage(ModLoadingStage_PostEngineStart);
Logger::flushStream(); cbe::Logger::flushStream();
ModuleManager::get()->progressLoadingStage(ModLoadingStage_Last); ModuleManager::get()->progressLoadingStage(ModLoadingStage_Last);
Logger::flushStream(); cbe::Logger::flushStream();
} }
/* Pre app tick initialization */ /* Pre-app tick initialization */
app->timeData.tickStart(); app->timeData.tickStart();
/* Run the app and wait for it to quit */ /* Run the app and wait for it to quit */
app->getJobSystem()->joinMain(); app->getJobSystem()->joinMain();
LOG("Application", "{} application exit", appCI.applicationName); /* Exit app */
app->exitApp(); {
CBE_LOG("Application", "{} application exit", appCI.applicationName);
app->exitApp();
}
Logger::flushStream(); cbe::Logger::flushStream();
/* Finish all jobs and shutdown */ /* Finish all jobs and shutdown */
app->getJobSystem()->shutdown(); app->getJobSystem()->shutdown();
delete app->jobSystem; delete app->jobSystem;
/* Has to be done after shutdown to complete log flush */ /* Has to be done after shutdown to complete log flush */
Logger::stopLoggingTime(); cbe::Logger::stopLoggingTime();
static_cast<ApplicationModule *>(this)->app = nullptr; static_cast<ApplicationModule *>(this)->app = nullptr;
delete inputSystem; delete inputSystem;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date January 2022 * \date January 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -35,13 +35,13 @@ int32 IApplicationModule::startApplication(const AppCreateInfo &appCI)
CBE_START_PROFILER(); CBE_START_PROFILER();
CBE_PROFILER_MESSAGE_LC("Hello Profiler! Cranberry Here!", ColorConst::GREEN); CBE_PROFILER_MESSAGE_LC("Hello Profiler! Cranberry Here!", ColorConst::GREEN);
Logger::initialize(); cbe::Logger::initialize();
ModuleManager::get()->setAutoLoadEnable(true); ModuleManager::get()->setAutoLoadEnable(true);
if (bHelpRun) if (bHelpRun)
{ {
/* We do not want to print anything else than help. */ /* We do not want to print anything else than help. */
Logger::pushMuteSeverities(Logger::AllServerity); cbe::Logger::pushMuteSeverities(cbe::Logger::AllServerity);
/* Force all the modules to be loaded to register every command line entries. */ /* Force all the modules to be loaded to register every command line entries. */
ModuleManager::get()->progressLoadingStage(ModLoadingStage_Manual); ModuleManager::get()->progressLoadingStage(ModLoadingStage_Manual);
@@ -50,7 +50,7 @@ int32 IApplicationModule::startApplication(const AppCreateInfo &appCI)
} }
else else
{ {
LOG_DEBUG("CommandLine", "Command [{}]", appCI.cmdLine); CBE_LOG_DEBUG("CommandLine", "Command [{}]", appCI.cmdLine);
/* App Core */ /* App Core */
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("Load Core")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("Load Core"));
@@ -78,9 +78,9 @@ int32 IApplicationModule::startApplication(const AppCreateInfo &appCI)
ModuleManager::get()->unloadAll(false); ModuleManager::get()->unloadAll(false);
UnexpectedErrorHandler::getHandler()->unregisterFilter(); UnexpectedErrorHandler::getHandler()->unregisterFilter();
Logger::flushStream(); cbe::Logger::flushStream();
Logger::shutdown(); cbe::Logger::shutdown();
return 0; return 0;
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -51,7 +51,7 @@ void ApplicationTimeData::tickStart()
initEndTick = Time::timeNow(); initEndTick = Time::timeNow();
frameTick = lastFrameTick = initEndTick; frameTick = lastFrameTick = initEndTick;
LOG(CbeApplication::LOG_CATEGORY, "App initialized in {:0.3} seconds", Time::asSeconds(initEndTick - startTick)); CBE_LOG(CbeApplication::LOG_CATEGORY, "App initialized in {:0.3} seconds", Time::asSeconds(initEndTick - startTick));
} }
void ApplicationTimeData::progressFrame() void ApplicationTimeData::progressFrame()
@@ -184,7 +184,7 @@ NODISCARD bool CbeApplication::appTick()
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("Tick")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("Tick"));
onTick(); onTick();
Logger::flushStream(); cbe::Logger::flushStream();
timeData.progressFrame(); timeData.progressFrame();
} }
@@ -198,7 +198,7 @@ void CbeApplication::exitApp()
clearWidgets(); clearWidgets();
onExit(); onExit();
LOG(LOG_CATEGORY, "{} run time {:.3} minutes", applicationName, Time::asMinutes(Time::timeNow() - timeData.startTick)); CBE_LOG(LOG_CATEGORY, "{} run time {:.3} minutes", applicationName, Time::asMinutes(Time::timeNow() - timeData.startTick));
} }
static copat::JobSystemFuncAwaiter enqExitApp(CbeApplication *app) static copat::JobSystemFuncAwaiter enqExitApp(CbeApplication *app)

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -222,7 +222,7 @@ void CbeApplication::createMainWnd() noexcept
{ int32(windowBound.minBound.x * dpiScaling), int32(windowBound.minBound.y * dpiScaling) }, { int32(windowBound.minBound.x * dpiScaling), int32(windowBound.minBound.y * dpiScaling) },
{ int32(windowBound.maxBound.x * dpiScaling), int32(windowBound.maxBound.y * dpiScaling) }, { int32(windowBound.maxBound.x * dpiScaling), int32(windowBound.maxBound.y * dpiScaling) },
}; };
const WindowCreateInfo mainWndCi{ WindowCreateInfo mainWndCi{
.initialName = applicationName.getChar(), .initialName = applicationName.getChar(),
.region = windowBound, .region = windowBound,
.bMaximized = EDITOR_BUILD == 0, .bMaximized = EDITOR_BUILD == 0,
@@ -230,6 +230,11 @@ void CbeApplication::createMainWnd() noexcept
.bDrawTitlebar = EDITOR_BUILD, .bDrawTitlebar = EDITOR_BUILD,
.bEnableDragDrop = EDITOR_BUILD, .bEnableDragDrop = EDITOR_BUILD,
}; };
#if DEBUG_BUILD
/* Show debug build */
const String mainWndName = STR_FORMAT("{}[DEBUG]", applicationName);
mainWndCi.initialName = mainWndName.getChar();
#endif
windowManager->createWindow(mainWndCi); windowManager->createWindow(mainWndCi);
inputSystem->registerWindow(windowManager->getWindowInfo(windowManager->getMainWindow())); inputSystem->registerWindow(windowManager->getWindowInfo(windowManager->getMainWindow()));
} }
@@ -416,11 +421,11 @@ void CbeApplication::windowExternalDragEnter(CbeWindowHandle wndHndl, Short2 dra
auto itr = windowWidgets.find(wndHndl); auto itr = windowWidgets.find(wndHndl);
if (itr == windowWidgets.cend()) if (itr == windowWidgets.cend())
{ {
LOG_WARN(LOG_CATEGORY, "Window not found! Drag enter ignored"); CBE_LOG_WARN(LOG_CATEGORY, "Window not found! Drag enter ignored");
return; return;
} }
/* Only print first 256 character to keep log short */ /* Only print first 256 character to keep log short */
LOG_VERBOSE(LOG_CATEGORY, "External drag entered {}", StringView{ dragData }.substr(0, 256)); CBE_LOG_VERBOSE(LOG_CATEGORY, "External drag entered {}", StringView{ dragData }.substr(0, 256));
DragInProgress thisDragData{ DragInProgress thisDragData{
.dragStart = dragStart, .dragStart = dragStart,
@@ -476,7 +481,7 @@ void CbeApplication::windowExternalDragLeave()
{ {
return; return;
} }
LOG(LOG_CATEGORY, "External drag left"); CBE_LOG(LOG_CATEGORY, "External drag left");
wndIntxnStates.dragsInProgress.erase(itr); wndIntxnStates.dragsInProgress.erase(itr);
debugAssertf( debugAssertf(
@@ -688,7 +693,7 @@ void CbeApplication::tickWindowWidgets() noexcept
if (overDragResp.state != EInputHandleState::NotHandled && overDragResp.bDragNewPayload) if (overDragResp.state != EInputHandleState::NotHandled && overDragResp.bDragNewPayload)
{ {
debugAssert(!bEndDrag); debugAssert(!bEndDrag);
LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!"); CBE_LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!");
itr->payload = overDragResp.payload; itr->payload = overDragResp.payload;
} }
} }
@@ -735,7 +740,7 @@ void CbeApplication::tickWindowWidgets() noexcept
if (enterDragResp.state != EInputHandleState::NotHandled && enterDragResp.bDragNewPayload) if (enterDragResp.state != EInputHandleState::NotHandled && enterDragResp.bDragNewPayload)
{ {
debugAssert(!bEndDrag); debugAssert(!bEndDrag);
LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!"); CBE_LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!");
itr->payload = enterDragResp.payload; itr->payload = enterDragResp.payload;
} }
} }
@@ -800,7 +805,7 @@ void CbeApplication::tickWindowWidgets() noexcept
if (enterDragResp.bDragNewPayload) if (enterDragResp.bDragNewPayload)
{ {
debugAssert(enterDragResp.state != EInputHandleState::NotHandled); debugAssert(enterDragResp.state != EInputHandleState::NotHandled);
LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!"); CBE_LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!");
startResp.payload = enterDragResp.payload; startResp.payload = enterDragResp.payload;
} }
wndIntxnStates.dragsInProgress.emplace_back(DragInProgress{ wndIntxnStates.dragsInProgress.emplace_back(DragInProgress{

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date October 2024 * \date October 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -15,6 +15,9 @@
namespace cbe namespace cbe
{ {
/* Yanked from ImGui source */
static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;
bool ImGuiHelpers::isAnyMouseClicked() bool ImGuiHelpers::isAnyMouseClicked()
{ {
return ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseClicked(ImGuiMouseButton_Middle) return ImGui::IsMouseClicked(ImGuiMouseButton_Left) || ImGui::IsMouseClicked(ImGuiMouseButton_Middle)
@@ -182,6 +185,21 @@ std::pair<cbe::DragPayloadRef, const ImGuiPayload *> ImGuiHelpers::acceptDragDro
return { *reinterpret_cast<cbe::DragPayloadRef *>(imguiPayload->Data), imguiPayload }; return { *reinterpret_cast<cbe::DragPayloadRef *>(imguiPayload->Data), imguiPayload };
} }
bool ImGuiHelpers::isItemDragDropPressed()
{
ImGuiContext &g = *ImGui::GetCurrentContext();
if (g.DragDropActive && BIT_NOT_SET(g.DragDropSourceFlags, ImGuiDragDropFlags_SourceNoHoldToOpenOthers)
&& ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem))
{
ImGui::SetHoveredID(g.LastItemData.ID);
if (g.HoveredIdTimer - g.IO.DeltaTime <= DRAGDROP_HOLD_TO_OPEN_TIMER && g.HoveredIdTimer >= DRAGDROP_HOLD_TO_OPEN_TIMER)
{
return true;
}
}
return false;
}
bool ImGuiHelpers::inputText( bool ImGuiHelpers::inputText(
const char *label, String *str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback /*= nullptr*/, void *userData /*= nullptr*/ const char *label, String *str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback /*= nullptr*/, void *userData /*= nullptr*/
) )

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date October 2024 * \date October 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -58,9 +58,12 @@ public:
} }
cbe::DragPayloadRef &payloadRef = *reinterpret_cast<cbe::DragPayloadRef *>(imguiPayload->Data); cbe::DragPayloadRef &payloadRef = *reinterpret_cast<cbe::DragPayloadRef *>(imguiPayload->Data);
if (!pred(payloadRef)) if constexpr (std::invocable<CondLambda, decltype(payloadRef)>)
{ {
return {}; if (!pred(payloadRef))
{
return {};
}
} }
std::pair<cbe::DragPayloadRef, const ImGuiPayload *> payloadsPair = acceptDragDropPayload(static_cast<const AChar *>(nullptr), flags); std::pair<cbe::DragPayloadRef, const ImGuiPayload *> payloadsPair = acceptDragDropPayload(static_cast<const AChar *>(nullptr), flags);
@@ -68,6 +71,13 @@ public:
debugAssert(!payloadsPair.first || payloadsPair.first == payloadRef); debugAssert(!payloadsPair.first || payloadsPair.first == payloadRef);
return payloadsPair; return payloadsPair;
} }
static std::pair<cbe::DragPayloadRef, const ImGuiPayload *> acceptDragDropPayload(ImGuiDragDropFlags flags = 0)
{
return acceptDragDropPayload(EmptyType{}, flags);
}
/* Helper to determine if drag and drop presses the item.
* Uses logic obtained from button behavior to find the press on drag and drop */
static bool isItemDragDropPressed();
template <Box2Dim BoxType> template <Box2Dim BoxType>
static void drawPackedRectangles( static void drawPackedRectangles(

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -205,7 +205,7 @@ void WgImGui::construct(const WgArguments &args)
} }
else else
{ {
LOG_ERROR(LOG_CATEGORY, "No font data provided for {}", imguiName); CBE_LOG_ERROR(LOG_CATEGORY, "No font data provided for {}", imguiName);
fontConfig.OversampleH = 3; fontConfig.OversampleH = 3;
fontConfig.OversampleV = 3; fontConfig.OversampleV = 3;
fontConfig.RasterizerMultiply = 2; fontConfig.RasterizerMultiply = 2;
@@ -403,7 +403,7 @@ void WgImGui::drawWidget(ShortRect clipBound, WidgetGeomId thisId, const WidgetG
const ImDrawCmd &drawCmd = uiCmdList->CmdBuffer[cmdIdx]; const ImDrawCmd &drawCmd = uiCmdList->CmdBuffer[cmdIdx];
if (drawCmd.UserCallback != nullptr) if (drawCmd.UserCallback != nullptr)
{ {
LOG_WARN(LOG_CATEGORY, "Commands with callback is not supported"); CBE_LOG_WARN(LOG_CATEGORY, "Commands with callback is not supported");
debugAssert(drawCmd.UserCallback != nullptr); debugAssert(drawCmd.UserCallback != nullptr);
continue; continue;
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -190,7 +190,7 @@ bool WindowsKeyboardDevice::sendInRaw(const RAWINPUT &rawInput)
*/ */
if (rawInput.data.keyboard.VKey == 0xFF) if (rawInput.data.keyboard.VKey == 0xFF)
{ {
LOG_WARN( CBE_LOG_WARN(
"WindowsKeyboardDevice", "Possible multibyte key that is not handled properly : {}, Flags : {}", rawInput.data.keyboard.MakeCode, "WindowsKeyboardDevice", "Possible multibyte key that is not handled properly : {}, Flags : {}", rawInput.data.keyboard.MakeCode,
rawInput.data.keyboard.Flags rawInput.data.keyboard.Flags
); );

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -49,7 +49,7 @@ void WindowsInputSystem::registerWindow(const WindowInfo &wndInfo) const
}; };
if (!RegisterRawInputDevices(gamepadDevices.data(), static_cast<uint32>(gamepadDevices.size()), sizeof(decltype(gamepadDevices[0])))) if (!RegisterRawInputDevices(gamepadDevices.data(), static_cast<uint32>(gamepadDevices.size()), sizeof(decltype(gamepadDevices[0]))))
{ {
LOG_WARN(LOG_CATEGORY, "Failed registering gamepads for window {}", wndInfo.title); CBE_LOG_WARN(LOG_CATEGORY, "Failed registering gamepads for window {}", wndInfo.title);
} }
RAWINPUTDEVICE keyboardDevice; RAWINPUTDEVICE keyboardDevice;
@@ -59,7 +59,7 @@ void WindowsInputSystem::registerWindow(const WindowInfo &wndInfo) const
keyboardDevice.hwndTarget = (HWND)wndInfo.hndl; keyboardDevice.hwndTarget = (HWND)wndInfo.hndl;
if (!RegisterRawInputDevices(&keyboardDevice, 1, sizeof(decltype(keyboardDevice)))) if (!RegisterRawInputDevices(&keyboardDevice, 1, sizeof(decltype(keyboardDevice))))
{ {
LOG_WARN(LOG_CATEGORY, "Failed registering keyboard for window {}", wndInfo.title); CBE_LOG_WARN(LOG_CATEGORY, "Failed registering keyboard for window {}", wndInfo.title);
} }
RAWINPUTDEVICE mouseDevice; RAWINPUTDEVICE mouseDevice;
@@ -69,7 +69,7 @@ void WindowsInputSystem::registerWindow(const WindowInfo &wndInfo) const
mouseDevice.hwndTarget = (HWND)wndInfo.hndl; mouseDevice.hwndTarget = (HWND)wndInfo.hndl;
if (!RegisterRawInputDevices(&mouseDevice, 1, sizeof(decltype(mouseDevice)))) if (!RegisterRawInputDevices(&mouseDevice, 1, sizeof(decltype(mouseDevice))))
{ {
LOG_WARN(LOG_CATEGORY, "Failed registering mouse for window {}", wndInfo.title); CBE_LOG_WARN(LOG_CATEGORY, "Failed registering mouse for window {}", wndInfo.title);
} }
} }
@@ -86,7 +86,7 @@ void WindowsInputSystem::updateInputStates()
int32 currentBlocksNum = ::GetRawInputBuffer(nullptr, &bufferSize, sizeof(RAWINPUTHEADER)); int32 currentBlocksNum = ::GetRawInputBuffer(nullptr, &bufferSize, sizeof(RAWINPUTHEADER));
if (currentBlocksNum == -1) if (currentBlocksNum == -1)
{ {
LOG_ERROR(LOG_CATEGORY, "Retrieving input buffer size failed"); CBE_LOG_ERROR(LOG_CATEGORY, "Retrieving input buffer size failed");
return; return;
} }
/* Must be aligned by sizeof pointer. /* Must be aligned by sizeof pointer.
@@ -114,7 +114,7 @@ void WindowsInputSystem::updateInputStates()
if (currentBlocksNum == -1) if (currentBlocksNum == -1)
{ {
LOG_ERROR(LOG_CATEGORY, "Reading buffered raw input failed"); CBE_LOG_ERROR(LOG_CATEGORY, "Reading buffered raw input failed");
return; return;
} }
} }
@@ -129,7 +129,7 @@ void WindowsInputSystem::updateInputStates()
if (!bProcessed) if (!bProcessed)
{ {
LOG_WARN(LOG_CATEGORY, "No device found for processing raw input"); CBE_LOG_WARN(LOG_CATEGORY, "No device found for processing raw input");
::DefRawInputProc(&rawInput, 1, sizeof(RAWINPUTHEADER)); ::DefRawInputProc(&rawInput, 1, sizeof(RAWINPUTHEADER));
} }
using QWORD = uint64; using QWORD = uint64;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -63,7 +63,7 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
const auto [comInitHr, bSuccess] = comInit(false); const auto [comInitHr, bSuccess] = comInit(false);
if (!bSuccess) if (!bSuccess)
{ {
LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to setup com threading model!"); CBE_LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to setup com threading model!");
return; return;
} }
@@ -101,7 +101,7 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
HRESULT hr = ::CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd)); HRESULT hr = ::CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pfd));
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to create file dialog!"); CBE_LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to create file dialog!");
break; break;
} }
@@ -109,17 +109,17 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->GetOptions(&dwOptions); hr = pfd->GetOptions(&dwOptions);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to get file dialog options!"); CBE_LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to get file dialog options!");
break; break;
} }
dwOptions |= optionFlags; dwOptions |= optionFlags;
hr = pfd->SetOptions(dwOptions); hr = pfd->SetOptions(dwOptions);
LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog options!"); CBE_LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog options!");
if (title != nullptr) if (title != nullptr)
{ {
hr = pfd->SetTitle(TCHAR_TO_WCHAR(title).data()); hr = pfd->SetTitle(TCHAR_TO_WCHAR(title).data());
LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog title!"); CBE_LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog title!");
} }
if (bSaving || !std::get<cbe::OpenFileDialogInfo>(info).bAllowFolders) if (bSaving || !std::get<cbe::OpenFileDialogInfo>(info).bAllowFolders)
@@ -154,9 +154,9 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->SetFileTypes(static_cast<uint32>(filterSpecs.size()), filterSpecs.data()); hr = pfd->SetFileTypes(static_cast<uint32>(filterSpecs.size()), filterSpecs.data());
} }
LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog file types!"); CBE_LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog file types!");
hr = pfd->SetFileTypeIndex(1); hr = pfd->SetFileTypeIndex(1);
LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog file types index!"); CBE_LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog file types index!");
} }
/* Setup default path */ /* Setup default path */
String defPath; String defPath;
@@ -174,7 +174,7 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
if (SUCCEEDED(::SHCreateItemFromParsingName(TCHAR_TO_WCHAR(defPath).data(), nullptr, IID_PPV_ARGS(&defaultPathItem)))) if (SUCCEEDED(::SHCreateItemFromParsingName(TCHAR_TO_WCHAR(defPath).data(), nullptr, IID_PPV_ARGS(&defaultPathItem))))
{ {
hr = pfd->SetFolder(defaultPathItem); hr = pfd->SetFolder(defaultPathItem);
LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog default folder path!"); CBE_LOG_ERROR_C(!SUCCEEDED(hr), WindowsWindowingManager::LOG_CATEGORY, "Failed to set file dialog default folder path!");
pfd->SetDefaultFolder(defaultPathItem); pfd->SetDefaultFolder(defaultPathItem);
defaultPathItem->Release(); defaultPathItem->Release();
@@ -183,12 +183,12 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->Show(hwndOwner); hr = pfd->Show(hwndOwner);
if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED))
{ {
LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "File dialog cancelled!"); CBE_LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "File dialog cancelled!");
break; break;
} }
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to show the open file dialog!"); CBE_LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to show the open file dialog!");
break; break;
} }
@@ -204,14 +204,14 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->GetResult(&pResult); hr = pfd->GetResult(&pResult);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to retrieve the result!"); CBE_LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to retrieve the result!");
break; break;
} }
PWSTR pFilePath = nullptr; PWSTR pFilePath = nullptr;
if (!SUCCEEDED(pResult->GetDisplayName(SIGDN_FILESYSPATH, &pFilePath))) if (!SUCCEEDED(pResult->GetDisplayName(SIGDN_FILESYSPATH, &pFilePath)))
{ {
LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to retrieve the result file system path!"); CBE_LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to retrieve the result file system path!");
break; break;
} }
@@ -265,7 +265,7 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
while (false); while (false);
} }
LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Save to files {}", filePath); CBE_LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Save to files {}", filePath);
/* Right now not doing multithreading so direct call is fine. */ /* Right now not doing multithreading so direct call is fine. */
std::get<cbe::SaveFileDialogInfo>(info).callback(filterIdx, filePath); std::get<cbe::SaveFileDialogInfo>(info).callback(filterIdx, filePath);
} }
@@ -274,13 +274,13 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->GetResults(&pResults); hr = pfd->GetResults(&pResults);
if (!SUCCEEDED(hr)) if (!SUCCEEDED(hr))
{ {
LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to retrieve the results!"); CBE_LOG_ERROR(WindowsWindowingManager::LOG_CATEGORY, "Failed to retrieve the results!");
break; break;
} }
dword count = 0; dword count = 0;
pResults->GetCount(&count); pResults->GetCount(&count);
LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Selected {} items", count); CBE_LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Selected {} items", count);
selected.reserve(count); selected.reserve(count);
for (DWORD i = 0; i < count; i++) for (DWORD i = 0; i < count; i++)
@@ -298,7 +298,7 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
} }
} }
LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Selected files {}", selected); CBE_LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Selected files {}", selected);
/* Right now not doing multithreading so direct call is fine. */ /* Right now not doing multithreading so direct call is fine. */
std::get<cbe::OpenFileDialogInfo>(info).callback(selected); std::get<cbe::OpenFileDialogInfo>(info).callback(selected);
@@ -425,14 +425,14 @@ WindowsWindowingManager::WindowsWindowingManager(InstanceHandle instHndl)
const bool bSetDpiAwarness = !!::SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); const bool bSetDpiAwarness = !!::SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
if (!bSetDpiAwarness) if (!bSetDpiAwarness)
{ {
LOG_WARN(LOG_CATEGORY, "DPI awareness setup failed"); CBE_LOG_WARN(LOG_CATEGORY, "DPI awareness setup failed");
} }
else else
{ {
LOG(LOG_CATEGORY, "Set per monitor dpi awareness"); CBE_LOG(LOG_CATEGORY, "Set per monitor dpi awareness");
} }
/* Refreshing monitors again to get updated monitor information */ /* Refreshing monitors again to get updated monitor information */
LOG(LOG_CATEGORY, "Refresh moniotors post set per monitor dpi awareness"); CBE_LOG(LOG_CATEGORY, "Refresh moniotors post set per monitor dpi awareness");
refreshMonitors(true); refreshMonitors(true);
printDisplayDevices(); printDisplayDevices();
} }
@@ -457,7 +457,7 @@ void WindowsWindowingManager::printDisplayDevices()
for (int i = 0; (!!::EnumDisplayDevices(NULL, i, &dispDevice, 0)); i++) for (int i = 0; (!!::EnumDisplayDevices(NULL, i, &dispDevice, 0)); i++)
{ {
// clang-format off // clang-format off
LOG(LOG_CATEGORY, "Display device #{}: \n\tname: {} \n\tID: {} \n\treadable name: {} \n\tStates: {}{}{}{}{}", i, CBE_LOG(LOG_CATEGORY, "Display device #{}: \n\tname: {} \n\tID: {} \n\treadable name: {} \n\tStates: {}{}{}{}{}", i,
UTF8_TO_TCHAR(dispDevice.DeviceName), UTF8_TO_TCHAR(dispDevice.DeviceName),
UTF8_TO_TCHAR(dispDevice.DeviceID), UTF8_TO_TCHAR(dispDevice.DeviceID),
UTF8_TO_TCHAR(dispDevice.DeviceString), UTF8_TO_TCHAR(dispDevice.DeviceString),
@@ -524,7 +524,7 @@ void WindowsWindowingManager::dpiChanged(CbeWindowHandle hndl, uint16 newDpi, IR
WindowInfo &wndInfo = windows[hndl]; WindowInfo &wndInfo = windows[hndl];
const float dpiScaling = windowsDpiToEngineDpi(newDpi); const float dpiScaling = windowsDpiToEngineDpi(newDpi);
LOG(LOG_CATEGORY, "Window {} dpi changed to {}", wndInfo.title, dpiScaling); CBE_LOG(LOG_CATEGORY, "Window {} dpi changed to {}", wndInfo.title, dpiScaling);
wndInfo.dpiScaling = dpiScaling; wndInfo.dpiScaling = dpiScaling;
::SetWindowPos( ::SetWindowPos(
@@ -609,7 +609,7 @@ void WindowsWindowingManager::refreshMonitors(bool bLogMonitors)
} }
} }
LOG_C( CBE_LOG_C(
bLogMonitors, LOG_CATEGORY, bLogMonitors, LOG_CATEGORY,
"Monitor name: {}[{}] \n\tID: {} \n\tConnected to: {}, ID: {}, Name: {} \n\tIs primary: {} \n\tRegion :[{}, {}] to [{}, {}] " "Monitor name: {}[{}] \n\tID: {} \n\tConnected to: {}, ID: {}, Name: {} \n\tIs primary: {} \n\tRegion :[{}, {}] to [{}, {}] "
"\n\tWork region :[{}, {}] to [{}, {}]\n\tMonitor resolution :{}x{}\n\tRefresh freq :{}Hz\n\tDPI Scaling :{}", "\n\tWork region :[{}, {}] to [{}, {}]\n\tMonitor resolution :{}x{}\n\tRefresh freq :{}Hz\n\tDPI Scaling :{}",
@@ -666,7 +666,7 @@ CbeWindowHandle WindowsWindowingManager::platformCreateWindow(CbeWindowHandle th
if (wndInfo.hndl == nullptr) if (wndInfo.hndl == nullptr)
{ {
LOG_ERROR(LOG_CATEGORY, "Failed creating window {}, Error code {}", windowCi.initialName, GetLastError()); CBE_LOG_ERROR(LOG_CATEGORY, "Failed creating window {}, Error code {}", windowCi.initialName, GetLastError());
return WindowTree::InvalidIdx; return WindowTree::InvalidIdx;
} }
/* Since dpi awareness questions the monitor list's legitimacy */ /* Since dpi awareness questions the monitor list's legitimacy */
@@ -679,7 +679,7 @@ CbeWindowHandle WindowsWindowingManager::platformCreateWindow(CbeWindowHandle th
if (windowCi.bEnableDragDrop) if (windowCi.bEnableDragDrop)
{ {
const HRESULT dragDropRes = ::RegisterDragDrop(wndInfo.hndl, this); const HRESULT dragDropRes = ::RegisterDragDrop(wndInfo.hndl, this);
LOG_WARN_C(!SUCCEEDED(dragDropRes), LOG_CATEGORY, "Drag drop registration failed for window {}!", windowCi.initialName); CBE_LOG_WARN_C(!SUCCEEDED(dragDropRes), LOG_CATEGORY, "Drag drop registration failed for window {}!", windowCi.initialName);
} }
return thisWndHndl; return thisWndHndl;
@@ -851,8 +851,10 @@ BOOL WindowsCallbackProcs::monitorEnum(HMONITOR monitor, HDC devCntx, LPRECT mon
::MONITORINFOEX info; ::MONITORINFOEX info;
info.cbSize = sizeof(MONITORINFOEX); info.cbSize = sizeof(MONITORINFOEX);
LOG(WindowsWindowingManager::LOG_CATEGORY, "Monitor callback received! Monitor region [{}, {}] to [{}, {}]", monitorRegion->left, CBE_LOG(
monitorRegion->top, monitorRegion->right, monitorRegion->bottom); WindowsWindowingManager::LOG_CATEGORY, "Monitor callback received! Monitor region [{}, {}] to [{}, {}]", monitorRegion->left,
monitorRegion->top, monitorRegion->right, monitorRegion->bottom
);
if (::GetMonitorInfo(monitor, &info)) if (::GetMonitorInfo(monitor, &info))
{ {
MonitorInfo &mInfo = monitors.emplace_back(); MonitorInfo &mInfo = monitors.emplace_back();
@@ -916,24 +918,24 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
const uint64 userData = cbeHndl; const uint64 userData = cbeHndl;
::SetWindowLongPtr(hwnd, GWLP_USERDATA, std::bit_cast<int64>(userData)); ::SetWindowLongPtr(hwnd, GWLP_USERDATA, std::bit_cast<int64>(userData));
LOG(WindowingManager::LOG_CATEGORY, "Created window {}", wndManager->getWindowInfo(cbeHndl).title); CBE_LOG(WindowingManager::LOG_CATEGORY, "Created window {}", wndManager->getWindowInfo(cbeHndl).title);
return 0; return 0;
} }
case WM_DESTROY: case WM_DESTROY:
{ {
if (wndManager->isValidWnd(cbeHndl)) if (wndManager->isValidWnd(cbeHndl))
{ {
LOG(WindowingManager::LOG_CATEGORY, "Destroying window {}", wndManager->getWindowInfo(cbeHndl).title); CBE_LOG(WindowingManager::LOG_CATEGORY, "Destroying window {}", wndManager->getWindowInfo(cbeHndl).title);
} }
else else
{ {
LOG(WindowingManager::LOG_CATEGORY, "Destroying window"); CBE_LOG(WindowingManager::LOG_CATEGORY, "Destroying window");
} }
return 0; return 0;
} }
case WM_CLOSE: case WM_CLOSE:
{ {
LOG(WindowingManager::LOG_CATEGORY, "Quiting window {}", wndManager->getWindowInfo(cbeHndl).title); CBE_LOG(WindowingManager::LOG_CATEGORY, "Quiting window {}", wndManager->getWindowInfo(cbeHndl).title);
app->destroyWindow(cbeHndl); app->destroyWindow(cbeHndl);
return 0; return 0;
} }
@@ -943,11 +945,11 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
{ {
if (wParam == TRUE) if (wParam == TRUE)
{ {
LOG(WindowingManager::LOG_CATEGORY, "Enabled window {}", wndManager->getWindowInfo(cbeHndl).title); CBE_LOG(WindowingManager::LOG_CATEGORY, "Enabled window {}", wndManager->getWindowInfo(cbeHndl).title);
} }
else else
{ {
LOG(WindowingManager::LOG_CATEGORY, "Disabled window {}", wndManager->getWindowInfo(cbeHndl).title); CBE_LOG(WindowingManager::LOG_CATEGORY, "Disabled window {}", wndManager->getWindowInfo(cbeHndl).title);
} }
return 0; return 0;
} }
@@ -959,12 +961,12 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
{ {
if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE) if (wParam == WA_ACTIVE || wParam == WA_CLICKACTIVE)
{ {
LOG(WindowingManager::LOG_CATEGORY, "Activating window {}", wndManager->getWindowInfo(cbeHndl).title); CBE_LOG(WindowingManager::LOG_CATEGORY, "Activating window {}", wndManager->getWindowInfo(cbeHndl).title);
static_cast<WindowsWindowingManager *>(wndManager)->activateWindow(cbeHndl); static_cast<WindowsWindowingManager *>(wndManager)->activateWindow(cbeHndl);
} }
else else
{ {
LOG(WindowingManager::LOG_CATEGORY, "Deactivating window {}", wndManager->getWindowInfo(cbeHndl).title); CBE_LOG(WindowingManager::LOG_CATEGORY, "Deactivating window {}", wndManager->getWindowInfo(cbeHndl).title);
static_cast<WindowsWindowingManager *>(wndManager)->deactivateWindow(cbeHndl); static_cast<WindowsWindowingManager *>(wndManager)->deactivateWindow(cbeHndl);
} }
return 0; return 0;
@@ -1002,7 +1004,9 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
break; break;
} }
LOG(WindowingManager::LOG_CATEGORY, "Window {} resized {}x{} -> {}x{}", wndInfo.title, oldSize.x, oldSize.y, newSize.x, newSize.y); CBE_LOG(
WindowingManager::LOG_CATEGORY, "Window {} resized {}x{} -> {}x{}", wndInfo.title, oldSize.x, oldSize.y, newSize.x, newSize.y
);
if ((wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) && LOWORD(lParam) > 0 && HIWORD(lParam) > 0) if ((wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) && LOWORD(lParam) > 0 && HIWORD(lParam) > 0)
{ {
wndInfo.deferredMsgs[cbe_app_wnd_msgs::Resize].emplace<cbe_app_wnd_msgs::WndResize>(oldSize, newSize); wndInfo.deferredMsgs[cbe_app_wnd_msgs::Resize].emplace<cbe_app_wnd_msgs::WndResize>(oldSize, newSize);

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date November 2024 * \date November 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -19,11 +19,13 @@ void CbeAudioModule::init()
const std::vector devs = getDevices(); const std::vector devs = getDevices();
for (const auto &dev : devs) for (const auto &dev : devs)
{ {
LOG(LOG_CATEGORY, "Audio device found {}[Is Default: {}]", dev.name, dev.bIsDefault); CBE_LOG(LOG_CATEGORY, "Audio device found {}[Is Default: {}]", dev.name, dev.bIsDefault);
for (const auto &devProp : dev.properties) for (const auto &devProp : dev.properties)
{ {
LOG(LOG_CATEGORY, " Sample Rate: {}, Data format {}, Channels: {}, Exclusive access: {}", devProp.sampleRate, CBE_LOG(
static_cast<uint32>(devProp.fmt), devProp.numChannels, devProp.bHasExclusiveAccess); LOG_CATEGORY, " Sample Rate: {}, Data format {}, Channels: {}, Exclusive access: {}", devProp.sampleRate,
static_cast<uint32>(devProp.fmt), devProp.numChannels, devProp.bHasExclusiveAccess
);
} }
} }
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date November 2024 * \date November 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -449,7 +449,7 @@ void MiniAudioBackendContext::copyAudioInst(
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
deinitAudioPlayerDataStream(dstDataSrc.stream, framesCount); deinitAudioPlayerDataStream(dstDataSrc.stream, framesCount);
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data stream"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data stream");
return; return;
} }
} }
@@ -458,7 +458,7 @@ void MiniAudioBackendContext::copyAudioInst(
ma_result result = ma_audio_buffer_ref_init(format, numOfChannels, framesData, framesCount, &dstDataSrc.audioRef); ma_result result = ma_audio_buffer_ref_init(format, numOfChannels, framesData, framesCount, &dstDataSrc.audioRef);
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data source"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data source");
return; return;
} }
dstDataSrc.audioRef.sampleRate = sampleRate; dstDataSrc.audioRef.sampleRate = sampleRate;
@@ -468,7 +468,7 @@ void MiniAudioBackendContext::copyAudioInst(
ma_result result = ma_sound_init_ex(&dstEng.engine, &soundCfg, &dstDataSrc.sound); ma_result result = ma_sound_init_ex(&dstEng.engine, &soundCfg, &dstDataSrc.sound);
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_sound!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_sound!");
ma_audio_buffer_ref_uninit(&dstDataSrc.audioRef); ma_audio_buffer_ref_uninit(&dstDataSrc.audioRef);
if (dstDataSrc.bStreaming) if (dstDataSrc.bStreaming)
{ {
@@ -534,7 +534,7 @@ bool MiniAudioBackendContext::setSampleRateImpl(uint32 newSampleRate)
{ {
if (dev.audioDevData) if (dev.audioDevData)
{ {
LOG_WARN(ICbeAudioModule::LOG_CATEGORY, "Cannot change sample rate after engine/devices are created!"); CBE_LOG_WARN(ICbeAudioModule::LOG_CATEGORY, "Cannot change sample rate after engine/devices are created!");
return false; return false;
} }
} }
@@ -709,7 +709,7 @@ cbe::audio::DecodedOutput MiniAudioBackendContext::decodeAudioInfoImpl(cbe::audi
} }
else else
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to retrieve the audio info from data!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to retrieve the audio info from data!");
} }
return output; return output;
} }
@@ -739,7 +739,7 @@ cbe::audio::DecodedOutput MiniAudioBackendContext::decodeAudioImpl(cbe::audio::D
} }
else else
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to decode the audio data!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to decode the audio data!");
} }
return output; return output;
} }
@@ -753,7 +753,7 @@ cbe::audio::EncodedOutput MiniAudioBackendContext::encodeAudioImpl(cbe::audio::E
encodeCfg.allocationCallbacks = localAllocCallbacks; encodeCfg.allocationCallbacks = localAllocCallbacks;
if (encodeInfo.encoding != cbe::audio::EDataEncoding::Wav) if (encodeInfo.encoding != cbe::audio::EDataEncoding::Wav)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Only WAV encoding is supported right now!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Only WAV encoding is supported right now!");
return {}; return {};
} }
@@ -801,7 +801,7 @@ cbe::audio::EncodedOutput MiniAudioBackendContext::encodeAudioImpl(cbe::audio::E
ma_result result = ma_encoder_init(EncoderLocalData::write, EncoderLocalData::seek, &encoderData, &encodeCfg, &encoder); ma_result result = ma_encoder_init(EncoderLocalData::write, EncoderLocalData::seek, &encoderData, &encodeCfg, &encoder);
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create audio encoder!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create audio encoder!");
return {}; return {};
} }
@@ -811,13 +811,13 @@ cbe::audio::EncodedOutput MiniAudioBackendContext::encodeAudioImpl(cbe::audio::E
ma_encoder_uninit(&encoder); ma_encoder_uninit(&encoder);
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to encode the frames to audio data!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to encode the frames to audio data!");
return {}; return {};
} }
if (framesWritten != inFramesCount) if (framesWritten != inFramesCount)
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeAudioModule::LOG_CATEGORY, "Frames written {} does not match the expected frames count {}!", framesWritten, inFramesCount ICbeAudioModule::LOG_CATEGORY, "Frames written {} does not match the expected frames count {}!", framesWritten, inFramesCount
); );
} }
@@ -844,8 +844,10 @@ cbe::audio::EngineId MiniAudioBackendContext::createUserEngineImpl(cbe::audio::L
auto [_, devIdx] = deviceIdToIndex(ci.device); auto [_, devIdx] = deviceIdToIndex(ci.device);
if (devices[devIdx].audioDevData->userEngIdx < CBE_AUDIO_MAX_LOCAL_USER) if (devices[devIdx].audioDevData->userEngIdx < CBE_AUDIO_MAX_LOCAL_USER)
{ {
LOG(ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!", CBE_LOG(
devicesInfo[devices[devIdx].devIdx].devInfo.name); ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!",
devicesInfo[devices[devIdx].devIdx].devInfo.name
);
return retId; return retId;
} }
@@ -875,7 +877,7 @@ cbe::audio::EngineId MiniAudioBackendContext::createUserEngineImpl(cbe::audio::L
{ {
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeAudioModule::LOG_CATEGORY, "Failed to create engine using device {} for local user index {}", ICbeAudioModule::LOG_CATEGORY, "Failed to create engine using device {} for local user index {}",
devicesInfo[devIdx].devInfo.name, ci.localUserIdx devicesInfo[devIdx].devInfo.name, ci.localUserIdx
); );
@@ -961,8 +963,10 @@ bool MiniAudioBackendContext::switchUserEngineDeviceImpl(cbe::audio::EngineId en
auto [_, devIdx] = deviceIdToIndex(deviceId); auto [_, devIdx] = deviceIdToIndex(deviceId);
if (devices[devIdx].audioDevData->userEngIdx < CBE_AUDIO_MAX_LOCAL_USER) if (devices[devIdx].audioDevData->userEngIdx < CBE_AUDIO_MAX_LOCAL_USER)
{ {
LOG(ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!", CBE_LOG(
devicesInfo[devices[devIdx].devIdx].devInfo.name); ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!",
devicesInfo[devices[devIdx].devIdx].devInfo.name
);
return false; return false;
} }
@@ -1386,7 +1390,7 @@ bool MiniAudioBackendContext::createAudioPlayerFromDataSrc(
); );
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data source"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data source");
return false; return false;
} }
playerRef.bStreaming = false; playerRef.bStreaming = false;
@@ -1433,7 +1437,7 @@ bool MiniAudioBackendContext::createAudioPlayerFromDataSrc(
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
deinitAudioPlayerDataStream(playerRef.stream, framesCount); deinitAudioPlayerDataStream(playerRef.stream, framesCount);
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data stream"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_audio_buffer_ref from engine audio data stream");
return false; return false;
} }
} }
@@ -1489,7 +1493,7 @@ bool MiniAudioBackendContext::createAudioPlayerFromDataSrc(
ma_result result = ma_sound_init_ex(&eng.engine, &soundCfg, &playerRef.sound); ma_result result = ma_sound_init_ex(&eng.engine, &soundCfg, &playerRef.sound);
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_sound!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_sound!");
ma_audio_buffer_ref_uninit(&playerRef.audioRef); ma_audio_buffer_ref_uninit(&playerRef.audioRef);
if (playerRef.bStreaming) if (playerRef.bStreaming)
{ {
@@ -1547,20 +1551,20 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
dataStream.pageBackingFile = PlatformMemory::openPhysicalMemorySection(bufferingBlockSizeInBytes); dataStream.pageBackingFile = PlatformMemory::openPhysicalMemorySection(bufferingBlockSizeInBytes);
if (dataStream.pageBackingFile == nullptr) if (dataStream.pageBackingFile == nullptr)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to allocate system page backing file!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to allocate system page backing file!");
break; break;
} }
/* Need virtual address space mappable to each audio page chunk */ /* Need virtual address space mappable to each audio page chunk */
dataStream.pcmFramesAddrs = PlatformMemory::virtualAllocUnmapped(bufferingBlockSizeInBytes, totalMappableBlocks); dataStream.pcmFramesAddrs = PlatformMemory::virtualAllocUnmapped(bufferingBlockSizeInBytes, totalMappableBlocks);
if (dataStream.pcmFramesAddrs == nullptr) if (dataStream.pcmFramesAddrs == nullptr)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to allocate PCM frames read virual addresses!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to allocate PCM frames read virual addresses!");
break; break;
} }
dataStream.pagesAddr = PlatformMemory::virtualAllocUnmapped(bufferingBlockSizeInBytes, 1); dataStream.pagesAddr = PlatformMemory::virtualAllocUnmapped(bufferingBlockSizeInBytes, 1);
if (dataStream.pcmFramesAddrs == nullptr) if (dataStream.pcmFramesAddrs == nullptr)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to allocate PCM frames write virual addresses!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to allocate PCM frames write virual addresses!");
break; break;
} }
/* Now map the virtual addresses */ /* Now map the virtual addresses */
@@ -1572,7 +1576,7 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
true true
)) ))
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeAudioModule::LOG_CATEGORY, "Failed to map section to PCM read frames at {}(offset {})!", mappedFrameAddrNum, ICbeAudioModule::LOG_CATEGORY, "Failed to map section to PCM read frames at {}(offset {})!", mappedFrameAddrNum,
bufferingBlockSizeInBytes * mappedFrameAddrNum bufferingBlockSizeInBytes * mappedFrameAddrNum
); );
@@ -1585,7 +1589,7 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
} }
if (!PlatformMemory::mapPhysicalMemorySection(dataStream.pageBackingFile, 0, dataStream.pagesAddr, bufferingBlockSizeInBytes, false)) if (!PlatformMemory::mapPhysicalMemorySection(dataStream.pageBackingFile, 0, dataStream.pagesAddr, bufferingBlockSizeInBytes, false))
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to map section at page idx to PCM write frames!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to map section at page idx to PCM write frames!");
break; break;
} }
@@ -1595,7 +1599,7 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
ma_result result = ma_decoder_init_vfs(&dataStream.maVfs, dataStream.filePath, &decodeCfg, &dataStream.decoder); ma_result result = ma_decoder_init_vfs(&dataStream.maVfs, dataStream.filePath, &decodeCfg, &dataStream.decoder);
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create stream decoder!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create stream decoder!");
break; break;
} }
@@ -1706,7 +1710,7 @@ bool MiniAudioBackendContext::createAudioPlayerInstancesImpl(
if (!isValidUserEngine(engIdxStart)) if (!isValidUserEngine(engIdxStart))
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeAudioModule::LOG_CATEGORY, "Engine not initialized. At least one engine associated with a device is required to play sound!" ICbeAudioModule::LOG_CATEGORY, "Engine not initialized. At least one engine associated with a device is required to play sound!"
); );
return false; return false;
@@ -1799,7 +1803,7 @@ bool MiniAudioBackendContext::destroyAudioPlayerInstancesImpl(ArrayView<cbe::aud
if (!isValidUserEngine(firstEngIdx)) if (!isValidUserEngine(firstEngIdx))
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Engine not initialized. At least one engine associated with a device is required!"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Engine not initialized. At least one engine associated with a device is required!");
return false; return false;
} }
const SizeT engCountToCheck = static_cast<SizeT>(CBE_AUDIO_MAX_LOCAL_USER - firstEngIdx); const SizeT engCountToCheck = static_cast<SizeT>(CBE_AUDIO_MAX_LOCAL_USER - firstEngIdx);
@@ -2068,7 +2072,7 @@ bool MiniAudioBackendContext::initSoundGroup(MaEngineSoundGroup &engSoundGrp, Ma
ma_result result = ma_sound_group_init_ex(&eng.engine, &soundGrpCfg, &engSoundGrp.soundGrp); ma_result result = ma_sound_group_init_ex(&eng.engine, &soundGrpCfg, &engSoundGrp.soundGrp);
if (result != MA_SUCCESS) if (result != MA_SUCCESS)
{ {
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_sound_group for engine"); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "Failed to create ma_sound_group for engine");
return false; return false;
} }
@@ -3194,7 +3198,7 @@ void MiniAudioBackendContext::destroyLocalDevice(const LocalDevice &dev) const
return; return;
} }
LOG_WARN_C( CBE_LOG_WARN_C(
isValidUserEngine(dev.audioDevData->userEngIdx), ICbeAudioModule::LOG_CATEGORY, isValidUserEngine(dev.audioDevData->userEngIdx), ICbeAudioModule::LOG_CATEGORY,
"All user engines({}) using device {} must be cleared before destroying device!", dev.audioDevData->userEngIdx, "All user engines({}) using device {} must be cleared before destroying device!", dev.audioDevData->userEngIdx,
devicesInfo[dev.devIdx].devInfo.name devicesInfo[dev.devIdx].devInfo.name
@@ -3336,17 +3340,17 @@ void MiniAudioBackendContext::miniaudioLog(void *pUserData, ma_uint32 level, con
switch (level) switch (level)
{ {
case MA_LOG_LEVEL_DEBUG: case MA_LOG_LEVEL_DEBUG:
LOG_DEBUG(ICbeAudioModule::LOG_CATEGORY, "{}", msg); CBE_LOG_DEBUG(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break; break;
case MA_LOG_LEVEL_INFO: case MA_LOG_LEVEL_INFO:
LOG(ICbeAudioModule::LOG_CATEGORY, "{}", msg); CBE_LOG(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break; break;
case MA_LOG_LEVEL_WARNING: case MA_LOG_LEVEL_WARNING:
LOG_WARN(ICbeAudioModule::LOG_CATEGORY, "{}", msg); CBE_LOG_WARN(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break; break;
case MA_LOG_LEVEL_ERROR: case MA_LOG_LEVEL_ERROR:
default: default:
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "{}", msg); CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break; break;
} }
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date November 2024 * \date November 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -160,7 +160,7 @@ private:
{ {
ma_device device; ma_device device;
/* Connects the device to engine index this device received input from */ /* Connects the device to engine index this device received input from */
CBESpinLock dataLock; cbe::SpinLock dataLock;
/* User engine always maps 1:1 to Device */ /* User engine always maps 1:1 to Device */
cbe::audio::EngineId userEngIdx = cbe::audio::INVALID_ENGINE_ID; cbe::audio::EngineId userEngIdx = cbe::audio::INVALID_ENGINE_ID;
}; };

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date November 2024 * \date November 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -106,7 +106,7 @@ struct StreamingTask
Task task = { nullptr }; Task task = { nullptr };
/* Required to be sure that correct data is present in the streamed in address */ /* Required to be sure that correct data is present in the streamed in address */
uint64 startFrameIdx = 0; uint64 startFrameIdx = 0;
CBESpinLock lock; cbe::SpinLock lock;
bool bInProgress; bool bInProgress;
}; };
struct MaAudioPlayerAudioSrcRef struct MaAudioPlayerAudioSrcRef

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date April 2025 * \date April 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -85,7 +85,7 @@ private:
/* If empty, verts are unique per index */ /* If empty, verts are unique per index */
std::vector<uint32> idxs; std::vector<uint32> idxs;
CBESpinLock meshHndsLock; cbe::SpinLock meshHndsLock;
std::unordered_map<cbe::physics::World, cbe::physics::MeshBatchHnd> worldToMeshHnd; std::unordered_map<cbe::physics::World, cbe::physics::MeshBatchHnd> worldToMeshHnd;
}; };
@@ -133,7 +133,7 @@ private:
BatchAllocator triangleBatchesAlloc; BatchAllocator triangleBatchesAlloc;
/* No reference will be taken here. */ /* No reference will be taken here. */
JoltMeshBatch *allBatches = nullptr; JoltMeshBatch *allBatches = nullptr;
CBESpinLock batchesLock; cbe::SpinLock batchesLock;
cbe::physics::IDebugDraw *debugDrawInterface = nullptr; cbe::physics::IDebugDraw *debugDrawInterface = nullptr;
}; };

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date April 2025 * \date April 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -139,12 +139,12 @@ private:
static_assert(sizeof(AllocHnd) == sizeof(JobPacketPoolAllocator::AllocHandle), "Invalid Allocation handle size for JobPacket!"); static_assert(sizeof(AllocHnd) == sizeof(JobPacketPoolAllocator::AllocHandle), "Invalid Allocation handle size for JobPacket!");
static_assert(sizeof(AllocHnd) == sizeof(JobPacketPoolAllocator::AllocHandle), "Invalid Allocation handle size for Barrier!"); static_assert(sizeof(AllocHnd) == sizeof(JobPacketPoolAllocator::AllocHandle), "Invalid Allocation handle size for Barrier!");
CBESpinLock jobsAllocatorLock; cbe::SpinLock jobsAllocatorLock;
JobPacketPoolAllocator jobsAllocator; JobPacketPoolAllocator jobsAllocator;
RingBufferLockFree<AllocHnd> freeJobAllocs; RingBufferLockFree<AllocHnd> freeJobAllocs;
BarrierPoolAllocator barrierAllocator; BarrierPoolAllocator barrierAllocator;
CBESpinLock barrierAllocatorLock; cbe::SpinLock barrierAllocatorLock;
private: private:
static JoltCopatTask queueOneJob(copat::JobSystem *jobSystem, JobPacket *job); static JoltCopatTask queueOneJob(copat::JobSystem *jobSystem, JobPacket *job);

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date April 2025 * \date April 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -66,7 +66,7 @@ static void joltLog(const AChar *fmt, ...)
va_end(list); va_end(list);
LOG(ICbePhysicsModule::LOG_CATEGORY, "{}", UTF8_TO_TCHAR(fmted)); CBE_LOG(ICbePhysicsModule::LOG_CATEGORY, "{}", UTF8_TO_TCHAR(fmted));
} }
#if (defined JPH_EXTERNAL_PROFILE) & PROFILING_ENABLED #if (defined JPH_EXTERNAL_PROFILE) & PROFILING_ENABLED
@@ -276,7 +276,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::B
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating box shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating box shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -376,7 +376,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating capsule shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating capsule shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -386,7 +386,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr()); JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr());
if (newRotatedShape.GetPtr() == nullptr) if (newRotatedShape.GetPtr() == nullptr)
{ {
LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating capsule orientation shape"); CBE_LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating capsule orientation shape");
return false; return false;
} }
@@ -441,7 +441,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating convex hull shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating convex hull shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -495,7 +495,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating cylinder shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating cylinder shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -505,7 +505,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr()); JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr());
if (newRotatedShape.GetPtr() == nullptr) if (newRotatedShape.GetPtr() == nullptr)
{ {
LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating cylinder orientation shape"); CBE_LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating cylinder orientation shape");
return false; return false;
} }
@@ -557,7 +557,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::S
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating sphere shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating sphere shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -611,7 +611,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered capsule shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered capsule shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -621,7 +621,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr()); JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr());
if (newRotatedShape.GetPtr() == nullptr) if (newRotatedShape.GetPtr() == nullptr)
{ {
LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered capsule orientation shape"); CBE_LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered capsule orientation shape");
return false; return false;
} }
@@ -675,7 +675,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered cylinder shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered cylinder shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -685,7 +685,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr()); JPH::Ref<JPH::Shape> newRotatedShape = createDefaultOrientedShape(result.Get().GetPtr());
if (newRotatedShape.GetPtr() == nullptr) if (newRotatedShape.GetPtr() == nullptr)
{ {
LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered cylinder orientation shape"); CBE_LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered cylinder orientation shape");
return false; return false;
} }
@@ -740,7 +740,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating triangle shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating triangle shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -790,7 +790,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
const JoltShapeData *subShape = getShape(subShapeSetting.shape, subShapeSetting.shapeType); const JoltShapeData *subShape = getShape(subShapeSetting.shape, subShapeSetting.shapeType);
if (subShape == nullptr) if (subShape == nullptr)
{ {
LOG_WARN(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating static compound shape!"); CBE_LOG_WARN(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating static compound shape!");
continue; continue;
} }
@@ -802,7 +802,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating static compound shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating static compound shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -852,7 +852,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::M
const JoltShapeData *subShape = getShape(subShapeSetting.shape, subShapeSetting.shapeType); const JoltShapeData *subShape = getShape(subShapeSetting.shape, subShapeSetting.shapeType);
if (subShape == nullptr) [[unlikely]] if (subShape == nullptr) [[unlikely]]
{ {
LOG_WARN(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating mutable compound shape!"); CBE_LOG_WARN(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating mutable compound shape!");
continue; continue;
} }
@@ -864,7 +864,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::M
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating mutable compound shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating mutable compound shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -909,7 +909,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JoltShapeData *subShape = getShape(settings.decoratedSettings.innerShape, settings.decoratedSettings.innerShapeType); const JoltShapeData *subShape = getShape(settings.decoratedSettings.innerShape, settings.decoratedSettings.innerShapeType);
if (subShape == nullptr) [[unlikely]] if (subShape == nullptr) [[unlikely]]
{ {
LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating transformed shape!"); CBE_LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating transformed shape!");
return false; return false;
} }
@@ -922,7 +922,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating transformed shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating transformed shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -968,7 +968,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::O
const JoltShapeData *subShape = getShape(settings.decoratedSettings.innerShape, settings.decoratedSettings.innerShapeType); const JoltShapeData *subShape = getShape(settings.decoratedSettings.innerShape, settings.decoratedSettings.innerShapeType);
if (subShape == nullptr) [[unlikely]] if (subShape == nullptr) [[unlikely]]
{ {
LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating offset center of mass shape!"); CBE_LOG_ERROR(ICbePhysicsModule::LOG_CATEGORY, "Invalid sub shape when creating offset center of mass shape!");
return false; return false;
} }
@@ -980,7 +980,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::O
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating offset center of mass shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating offset center of mass shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -1028,7 +1028,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::E
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating empty shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating empty shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -1138,7 +1138,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::H
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating height field shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating height field shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -1154,7 +1154,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::H
const JPH::ShapeSettings::ShapeResult rotationResult = joltRotationSettings.Create(); const JPH::ShapeSettings::ShapeResult rotationResult = joltRotationSettings.Create();
if (!rotationResult.IsValid()) if (!rotationResult.IsValid())
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating height field orientation shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating height field orientation shape with error: {}",
rotationResult.HasError() ? UTF8_TO_TCHAR(rotationResult.GetError().c_str()) : StringView(TCHAR("None")) rotationResult.HasError() ? UTF8_TO_TCHAR(rotationResult.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -1232,7 +1232,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::M
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating mesh shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating mesh shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -1285,7 +1285,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::P
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create(); const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]] if (!result.IsValid()) [[unlikely]]
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating empty shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating empty shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );
@@ -1438,7 +1438,7 @@ JPH::Ref<JPH::Shape> JoltBackend::createDefaultOrientedShape(const JPH::Shape *i
return result.Get(); return result.Get();
} }
LOG_ERROR( CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating default oriented shape wrapping a shape with error: {}", ICbePhysicsModule::LOG_CATEGORY, "Failed creating default oriented shape wrapping a shape with error: {}",
result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None")) result.HasError() ? UTF8_TO_TCHAR(result.GetError().c_str()) : StringView(TCHAR("None"))
); );

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date April 2025 * \date April 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -233,7 +233,7 @@ bool JoltPhysicsWorld::createBodies(ArrayRange<cbe::physics::Body> outBodies, Ar
} }
else else
{ {
LOG_WARN(ICbePhysicsModule::LOG_CATEGORY, "Failed to create scaled shape for body."); CBE_LOG_WARN(ICbePhysicsModule::LOG_CATEGORY, "Failed to create scaled shape for body.");
} }
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date April 2025 * \date April 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -39,7 +39,7 @@ public:
for (uint32 broadphaseLayerIdx = 0; broadphaseLayerIdx < broadphaseLayersCount; ++broadphaseLayerIdx) for (uint32 broadphaseLayerIdx = 0; broadphaseLayerIdx < broadphaseLayersCount; ++broadphaseLayerIdx)
{ {
const cbe::physics::BroadPhaseLayerInfo &info = broadphaseLayers[broadphaseLayerIdx]; const cbe::physics::BroadPhaseLayerInfo &info = broadphaseLayers[broadphaseLayerIdx];
LOG_WARN_C( CBE_LOG_WARN_C(
info.name.length() >= cbe::physics::MAX_LAYER_NAME_LEN, ICbePhysicsModule::LOG_CATEGORY, info.name.length() >= cbe::physics::MAX_LAYER_NAME_LEN, ICbePhysicsModule::LOG_CATEGORY,
"Broadphase layer name {}[{}] is exceeding the allowed limit of {}", info.name, info.name.length(), "Broadphase layer name {}[{}] is exceeding the allowed limit of {}", info.name, info.name.length(),
cbe::physics::MAX_LAYER_NAME_LEN - 1 cbe::physics::MAX_LAYER_NAME_LEN - 1
@@ -76,7 +76,7 @@ public:
for (uint32 broadphaseLayerIdx = 0; broadphaseLayerIdx < broadphaseLayers.size(); ++broadphaseLayerIdx) for (uint32 broadphaseLayerIdx = 0; broadphaseLayerIdx < broadphaseLayers.size(); ++broadphaseLayerIdx)
{ {
const cbe::physics::BroadPhaseLayerInfo &info = broadphaseLayers[broadphaseLayerIdx]; const cbe::physics::BroadPhaseLayerInfo &info = broadphaseLayers[broadphaseLayerIdx];
LOG_WARN_C( CBE_LOG_WARN_C(
PlatformFunctions::countOnes(info.collidesWith) == 0, ICbePhysicsModule::LOG_CATEGORY, PlatformFunctions::countOnes(info.collidesWith) == 0, ICbePhysicsModule::LOG_CATEGORY,
"Broadphase layer name {} does not collide with any ObjectLayer", info.name "Broadphase layer name {} does not collide with any ObjectLayer", info.name
); );

View File

@@ -4,7 +4,7 @@
* \author Subity * \author Subity
* \date May 2025 * \date May 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -48,7 +48,9 @@ void FixedTicker::tick(float deltaTime)
/* If delta time goes above certain threshold just drop the iterations */ /* If delta time goes above certain threshold just drop the iterations */
if (deltaTime > FRAME_TIME_THRESHOLD) if (deltaTime > FRAME_TIME_THRESHOLD)
{ {
LOG_VERBOSE(ICbePhysicsModule::LOG_CATEGORY, "Fixed ticker iterations dropped {}->{}", numOfIteration, DROP_TO_ITERATIONS_NUM); CBE_LOG_VERBOSE(
ICbePhysicsModule::LOG_CATEGORY, "Fixed ticker iterations dropped {}->{}", numOfIteration, DROP_TO_ITERATIONS_NUM
);
numOfIteration = DROP_TO_ITERATIONS_NUM; numOfIteration = DROP_TO_ITERATIONS_NUM;
} }

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -197,10 +197,24 @@ String ICbeRendererModule::generateUniformStructCodes(const gal::GPUStructLayout
{ {
retType = compilerInst->getShaderTypeName(gal::EShaderDataFormat::Float4); retType = compilerInst->getShaderTypeName(gal::EShaderDataFormat::Float4);
} }
else if (!field.metaData.bStructType) else if (field.metaData.bStructType)
{
auto structLayoutItr = std::find_if(
rendererMod->getGpuStructs().cbegin(), rendererMod->getGpuStructs().cend(),
[field](const gal::GPUStructLayout &layout)
{
return TCharStr::isEqual(layout.name, field.metaData.structTypeName);
}
);
debugAssert(structLayoutItr != rendererMod->getGpuStructs().cend());
CBE_LOG_DEBUG_C(
structLayoutItr->textureFieldsCount == 0, ICbeRendererModule::LOG_CATEGORY,
"Inner struct with textures are not supported with material param accessor macros."
);
}
else
{ {
retType = compilerInst->getShaderTypeName(field.metaData.dataType); retType = compilerInst->getShaderTypeName(field.metaData.dataType);
auto typeInfo = gal::EShaderDataFormat::typeToInfo(field.metaData.dataType);
} }
FormatArgsMap argsMap{ FormatArgsMap argsMap{
{ TCHAR(MAT_TMPL_ACCESSOR_IS_ARRAY), (field.elemCount > 1) }, { TCHAR(MAT_TMPL_ACCESSOR_IS_ARRAY), (field.elemCount > 1) },

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2024 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -58,7 +58,7 @@ CommandPools::CommandPools(gal::Context *galContext, uint32 threadCount, const T
} }
if (!galCtx->createCommandPools(uniqPools, poolCis)) if (!galCtx->createCommandPools(uniqPools, poolCis))
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed creating all command pools for {}", name); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed creating all command pools for {}", name);
} }
for (uint8 uniqQIdx = 0, qIdx = 0; qIdx < gal::EQueue::MaxQs; qIdx++) for (uint8 uniqQIdx = 0, qIdx = 0; qIdx < gal::EQueue::MaxQs; qIdx++)
{ {
@@ -110,14 +110,13 @@ CommandPools::allocateAllThreads(const TChar *baseName, gal::EQueue::Type queue,
if (!galCtx->allocateCommandBuffers(retVals, cmdBufferAis)) if (!galCtx->allocateCommandBuffers(retVals, cmdBufferAis))
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed allocating all command buffers for {}", baseName); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed allocating all command buffers for {}", baseName);
retVals.clear(); retVals.clear();
} }
return retVals; return retVals;
} }
gal::GAL_RESOURCE_HND(CommandBuffer gal::GAL_RESOURCE_HND(CommandBuffer) CommandPools::allocate(const TChar *cmdName, uint32 threadIdx, gal::EQueue::Type queue, bool bSecondary /*= false*/) const
) CommandPools::allocate(const TChar *cmdName, uint32 threadIdx, gal::EQueue::Type queue, bool bSecondary /*= false*/) const
{ {
gal::GAL_RESOURCE_HND(CommandBuffer) retVal; gal::GAL_RESOURCE_HND(CommandBuffer) retVal;
@@ -128,7 +127,7 @@ gal::GAL_RESOURCE_HND(CommandBuffer
gal::CommandBufferAllocInfo{ .name = cmdName, .pool = pools[poolOffset + threadIdx], .count = 1, .bSecondary = bSecondary } gal::CommandBufferAllocInfo{ .name = cmdName, .pool = pools[poolOffset + threadIdx], .count = 1, .bSecondary = bSecondary }
)) ))
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed allocating command buffer {}", cmdName); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed allocating command buffer {}", cmdName);
} }
return retVal; return retVal;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -96,7 +96,7 @@ void RendererContext::initialize(RenderContextInitInfo initInfo) noexcept
.genericMem = { .allocPtr = galGenericAlloc, .reallocPtr = galGenericRealloc, .freePtr = galGenericFree } }, .genericMem = { .allocPtr = galGenericAlloc, .reallocPtr = galGenericRealloc, .freePtr = galGenericFree } },
.bEnableValidation = cmdLine.hasArg(GAL_VALIDATION_ENABLE_NAME), .bEnableValidation = cmdLine.hasArg(GAL_VALIDATION_ENABLE_NAME),
.bExtendedValidations = true, .bExtendedValidations = true,
.bDebugGpuVaBinding = true, .bDebugGpuVaBinding = false, ///< Too verbose right now
.bOffscreenOnly = false, .bOffscreenOnly = false,
.driverApi = initInfo.driverApi, .driverApi = initInfo.driverApi,
.enableFs = initInfo.enableFs, .enableFs = initInfo.enableFs,
@@ -115,13 +115,13 @@ void RendererContext::initialize(RenderContextInitInfo initInfo) noexcept
void RendererContext::prepareRelease() noexcept void RendererContext::prepareRelease() noexcept
{ {
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Preparing RendererContext release!"); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Preparing RendererContext release!");
/* Wait clear all deletes */ /* Wait clear all deletes */
resourceDeleter.clear(); resourceDeleter.clear();
} }
void RendererContext::release() noexcept void RendererContext::release() noexcept
{ {
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Releasing RendererContext!"); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Releasing RendererContext!");
copat::waitOnAwaitable(std::move(lastCompileTask)); copat::waitOnAwaitable(std::move(lastCompileTask));
@@ -326,7 +326,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
if (bFailure) if (bFailure)
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to compile and reflect shaders"); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to compile and reflect shaders");
co_return ECompileState::Failure; co_return ECompileState::Failure;
} }
@@ -358,7 +358,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
std::erase_if(generalDirs, erasePred); std::erase_if(generalDirs, erasePred);
if (basicDirs.size() + editorDirs.size() + generalDirs.size() == 0) if (basicDirs.size() + editorDirs.size() + generalDirs.size() == 0)
{ {
LOG_WARN(ICbeRendererModule::LOG_CATEGORY, "No shader directory found. Skipping shader compile from source!"); CBE_LOG_WARN(ICbeRendererModule::LOG_CATEGORY, "No shader directory found. Skipping shader compile from source!");
bLoadFromDirs = false; bLoadFromDirs = false;
} }
@@ -371,7 +371,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
const SCompShaderBlob basicSrcs = co_await basicSrcsAwaitable; const SCompShaderBlob basicSrcs = co_await basicSrcsAwaitable;
const SCompShaderBlob editorSrcs = co_await editorSrcsAwaitable; const SCompShaderBlob editorSrcs = co_await editorSrcsAwaitable;
const SCompShaderBlob generalSrcs = co_await generalSrcsAwaitable; const SCompShaderBlob generalSrcs = co_await generalSrcsAwaitable;
LOG(ICbeRendererModule::LOG_CATEGORY, "All shader sources loaded in {}s", s.currentLap()); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "All shader sources loaded in {}s", s.currentLap());
s.lap(); s.lap();
/* Now wait and load the new blobs */ /* Now wait and load the new blobs */
@@ -381,13 +381,13 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
if (cState != ECompileState::Failure) if (cState != ECompileState::Failure)
{ {
basicShadersCompiled.test_and_set(std::memory_order::relaxed); basicShadersCompiled.test_and_set(std::memory_order::relaxed);
LOG(ICbeRendererModule::LOG_CATEGORY, "Fundamental shaders compiled in {}s", s.currentLap()); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "Fundamental shaders compiled in {}s", s.currentLap());
bAnyNewShaders = cState == ECompileState::Success; bAnyNewShaders = cState == ECompileState::Success;
bSerializedShaders = false; bSerializedShaders = false;
} }
else else
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Fundamental shaders compile failed!"); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Fundamental shaders compile failed!");
} }
if (cState == ECompileState::Success && !bSerializedShaders) if (cState == ECompileState::Success && !bSerializedShaders)
@@ -409,13 +409,13 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
{ {
basicShadersCompiled.test_and_set(std::memory_order::relaxed); basicShadersCompiled.test_and_set(std::memory_order::relaxed);
editorShadersCompiled.test_and_set(std::memory_order::relaxed); editorShadersCompiled.test_and_set(std::memory_order::relaxed);
LOG(ICbeRendererModule::LOG_CATEGORY, "Editor shaders compiled in {}s", s.currentLap()); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "Editor shaders compiled in {}s", s.currentLap());
bAnyNewShaders = (cState == ECompileState::Success) || bAnyNewShaders; bAnyNewShaders = (cState == ECompileState::Success) || bAnyNewShaders;
bSerializedShaders = false; bSerializedShaders = false;
} }
else else
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Editor shaders compile failed!"); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Editor shaders compile failed!");
} }
if (cState == ECompileState::Success && !bSerializedShaders) if (cState == ECompileState::Success && !bSerializedShaders)
{ {
@@ -438,13 +438,13 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
basicShadersCompiled.test_and_set(std::memory_order::relaxed); basicShadersCompiled.test_and_set(std::memory_order::relaxed);
editorShadersCompiled.test_and_set(std::memory_order::relaxed); editorShadersCompiled.test_and_set(std::memory_order::relaxed);
generalShadersCompiled.test_and_set(std::memory_order::release); generalShadersCompiled.test_and_set(std::memory_order::release);
LOG(ICbeRendererModule::LOG_CATEGORY, "General shaders compiled in {}s", s.currentLap()); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "General shaders compiled in {}s", s.currentLap());
bAnyNewShaders = (cState == ECompileState::Success) || bAnyNewShaders; bAnyNewShaders = (cState == ECompileState::Success) || bAnyNewShaders;
bSerializedShaders = false; bSerializedShaders = false;
} }
else else
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "General shaders compile failed!"); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "General shaders compile failed!");
} }
if (cState == ECompileState::Success && !bSerializedShaders) if (cState == ECompileState::Success && !bSerializedShaders)
@@ -474,7 +474,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
bSerializedShaders = false; bSerializedShaders = false;
} }
s.stop(); s.stop();
LOG(ICbeRendererModule::LOG_CATEGORY, "All shaders compiled in {}s", s.duration()); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "All shaders compiled in {}s", s.duration());
/* If shaders are updated or compiled, store and cache then immediately */ /* If shaders are updated or compiled, store and cache then immediately */
if (bAnyNewShaders) if (bAnyNewShaders)
@@ -528,7 +528,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync()
bool bFailure = !co_await compiler->compileShadersAsync(newCompiledResults, compileInfo); bool bFailure = !co_await compiler->compileShadersAsync(newCompiledResults, compileInfo);
if (bFailure) if (bFailure)
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to compile and reflect shaders"); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to compile and reflect shaders");
co_return; co_return;
} }
@@ -543,7 +543,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync()
onShadersUpdated(updates); onShadersUpdated(updates);
s.stop(); s.stop();
LOG(ICbeRendererModule::LOG_CATEGORY, "All shaders compiled in {}s", s.duration()); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "All shaders compiled in {}s", s.duration());
} }
#endif #endif
@@ -574,7 +574,7 @@ void RendererContext::updateCompiledResultsStructLayout()
auto itr = vertName2Layout.find(std::get<NameString>(vertEntry.typeNameOrLayout)); auto itr = vertName2Layout.find(std::get<NameString>(vertEntry.typeNameOrLayout));
if (itr == vertName2Layout.cend()) if (itr == vertName2Layout.cend())
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Vertex layout {} missing for shader {}!", ICbeRendererModule::LOG_CATEGORY, "Vertex layout {} missing for shader {}!",
std::get<NameString>(vertEntry.typeNameOrLayout), r.first std::get<NameString>(vertEntry.typeNameOrLayout), r.first
); );
@@ -609,7 +609,7 @@ void RendererContext::updateCompiledResultsStructLayout()
auto itr = vertName2Layout.find(std::get<NameString>(vertEntry.typeNameOrLayout)); auto itr = vertName2Layout.find(std::get<NameString>(vertEntry.typeNameOrLayout));
if (itr == vertName2Layout.cend()) if (itr == vertName2Layout.cend())
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Vertex layout {} missing for material {}!", ICbeRendererModule::LOG_CATEGORY, "Vertex layout {} missing for material {}!",
std::get<NameString>(vertEntry.typeNameOrLayout), matVariant.second.name std::get<NameString>(vertEntry.typeNameOrLayout), matVariant.second.name
); );
@@ -698,7 +698,7 @@ bool RendererContext::storeCompiledResults(ShadersUpdate &outUpdates, const SCom
recompiledCount += newCompiledRes.materials.size(); recompiledCount += newCompiledRes.materials.size();
if (!bFromCache) if (!bFromCache)
{ {
LOG(ICbeRendererModule::LOG_CATEGORY, "Compiled {} new shaders!", recompiledCount); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "Compiled {} new shaders!", recompiledCount);
} }
/* Update the old compiled results now */ /* Update the old compiled results now */
@@ -1166,7 +1166,7 @@ void RendererContext::createBasicPipelines()
const bool bSuccess = galContext()->createGraphicsPipelines(wgGPipeline, gpCi); const bool bSuccess = galContext()->createGraphicsPipelines(wgGPipeline, gpCi);
if (!bSuccess) if (!bSuccess)
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to create \"{}\" Graphics pipeline!", WG_SHADER_NAME); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to create \"{}\" Graphics pipeline!", WG_SHADER_NAME);
wgGPipeline = {}; wgGPipeline = {};
} }
@@ -1204,7 +1204,7 @@ void RendererContext::createBasicPipelines()
const bool bSuccess = galContext()->createGraphicsPipelines(defaultGBufferPipeline, gpCi); const bool bSuccess = galContext()->createGraphicsPipelines(defaultGBufferPipeline, gpCi);
if (!bSuccess) if (!bSuccess)
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to create \"Default\" Graphics pipeline!"); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to create \"Default\" Graphics pipeline!");
defaultGBufferPipeline = {}; defaultGBufferPipeline = {};
} }
} }
@@ -1222,7 +1222,7 @@ void RendererContext::createBasicPipelines()
const bool bSuccess = galContext()->createComputePipelines(fillBufferWithPcPipeline, fillBufferCompCi); const bool bSuccess = galContext()->createComputePipelines(fillBufferWithPcPipeline, fillBufferCompCi);
if (!bSuccess) if (!bSuccess)
{ {
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to create \"FillBufferFromPC\" Compute pipeline!"); CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed to create \"FillBufferFromPC\" Compute pipeline!");
fillBufferWithPcPipeline = {}; fillBufferWithPcPipeline = {};
} }
} }
@@ -1801,6 +1801,8 @@ CpuBuffersPool::Allocation CpuBuffersPool::getTempBuffer(uint32 minSize) noexcep
} }
framesToFree[bestFitIdx] = static_cast<uint8>(rCtx->getSwapchainImgCount()); framesToFree[bestFitIdx] = static_cast<uint8>(rCtx->getSwapchainImgCount());
debugAssert(rCtx->galContext()->isValid(buffers[bestFitIdx]));
return Allocation{ .hndl = buffers[bestFitIdx], .idx = bestFitIdx, .offset = 0, .size = bestFitSize }; return Allocation{ .hndl = buffers[bestFitIdx], .idx = bestFitIdx, .offset = 0, .size = bestFitSize };
} }
@@ -1822,10 +1824,10 @@ void GpGpuBindlessDataBuffers::setup(gal::PipelineHndlVariants newPipeline, uint
#endif #endif
if (pipeline == newPipeline && descTableIdx == tblIdx) if (pipeline == newPipeline && descTableIdx == tblIdx)
{ {
LOG(ICbeRendererModule::LOG_CATEGORY, "Cannot reset same pipeline and table pairs!"); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "Cannot reset same pipeline and table pairs!");
return; return;
} }
LOG(ICbeRendererModule::LOG_CATEGORY, "Setting up bindless data buffers using new pipeline for {}!", baseName); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "Setting up bindless data buffers using new pipeline for {}!", baseName);
resetDesc(false); resetDesc(false);
pipeline = newPipeline; pipeline = newPipeline;
@@ -2069,10 +2071,10 @@ void GpGpuBindlessTextures::setup(
#endif #endif
if (pipeline == newPipeline && descTableIdx == tblIdx && descBindingIdx == bindIdx) if (pipeline == newPipeline && descTableIdx == tblIdx && descBindingIdx == bindIdx)
{ {
LOG(ICbeRendererModule::LOG_CATEGORY, "Cannot reset same pipeline and table pairs!"); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "Cannot reset same pipeline and table pairs!");
return; return;
} }
LOG(ICbeRendererModule::LOG_CATEGORY, "Setting up bindless textures using new pipeline for {}!", baseName); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "Setting up bindless textures using new pipeline for {}!", baseName);
defTexSlotClear.resize(rCtx.getSwapchainImgCount()); defTexSlotClear.resize(rCtx.getSwapchainImgCount());
@@ -2342,6 +2344,10 @@ std::vector<uint64> GpGpuBindlessTextures::addImages(ArrayView<gal::GAL_RESOURCE
std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::GAL_RESOURCE_HND(ImageView)> imgViews) std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::GAL_RESOURCE_HND(ImageView)> imgViews)
{ {
debugAssert(!imgViews.empty()); debugAssert(!imgViews.empty());
fatalAssertf(
imgViews.size() < descsCountPerTable, "Cannot support {} textures in same descriptor table. Max {} is allowed in this HW",
imgViews.size(), descsCountPerTable
);
std::vector<uint64> retVals; std::vector<uint64> retVals;
retVals.resize(imgViews.size()); retVals.resize(imgViews.size());
@@ -2382,7 +2388,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
bool bNoneReachedEnd = true; bool bNoneReachedEnd = true;
while (bNoneReachedEnd) while (bNoneReachedEnd)
{ {
uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]); const uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]);
bool bAllMatched = true; bool bAllMatched = true;
/* Necessary to increment the image entry index until it reaches the max table index */ /* Necessary to increment the image entry index until it reaches the max table index */
@@ -2429,7 +2435,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
uint64 descsCountLeftToCheck = allocator.size(); uint64 descsCountLeftToCheck = allocator.size();
for (; tableIdx < descsTables.size(); ++tableIdx) for (; tableIdx < descsTables.size(); ++tableIdx)
{ {
const SizeT count = Math::min(descsCountLeftToCheck, descsCountLeftToCheck); const SizeT count = Math::min(descsCountLeftToCheck, descsCountPerTable);
const SizeT freeCount = allocator.countZeroesInRange(tableToEntryIdx(tableIdx), count); const SizeT freeCount = allocator.countZeroesInRange(tableToEntryIdx(tableIdx), count);
descsCountLeftToCheck -= count; descsCountLeftToCheck -= count;
@@ -2439,18 +2445,74 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
} }
} }
/* Reset so that we only allocate new descriptor entry if view does not exist in current selected tableIdx. */
viewsExists.resetRange(0, imgViews.size());
if (tableIdx >= descsTables.size()) if (tableIdx >= descsTables.size())
{ {
/* Resize and allocate from the end table index */ /* Resize and allocate from the end table index */
resizeDescs(allocator.size() + imgViews.size()); resizeDescs(allocator.size() + imgViews.size());
tableIdx = static_cast<uint32>(descsTables.size() - 1); tableIdx = static_cast<uint32>(descsTables.size() - 1);
} }
else
{
/* Find if img view entry is found in selected table and reuse it.
* For new but repeatedly used textures the slot reuse happens in next step. */
for (SizeT i = 0; i < imgViews.size(); ++i)
{
auto foundEntriesItr = std::find_if(
perImgFoundEntries[i].cbegin(), perImgFoundEntries[i].cend(),
[tableIdx, this](uint64 descEntryIdx)
{
return entryIdxToTable(descEntryIdx) == tableIdx;
}
);
if (foundEntriesItr == perImgFoundEntries[i].cend())
{
continue;
}
viewsExists[i] = true;
retVals[i] = *foundEntriesItr;
++descEntries[retVals[i]].refCount;
}
}
/* Allocate from descriptors in this table's range */ /* Allocate from descriptors in this table's range */
const SizeT descEntryStartIndex = tableToEntryIdx(tableIdx); const SizeT descEntryStartIndex = tableToEntryIdx(tableIdx);
const SizeT descEntryEndIndex = Math::min(descEntryStartIndex + descsCountPerTable, allocator.size()); const SizeT descEntryEndIndex = Math::min(descEntryStartIndex + descsCountPerTable, allocator.size());
for (SizeT i = 0; i < imgViews.size(); ++i) for (SizeT i = 0; i < imgViews.size(); ++i)
{ {
if (viewsExists[i])
{
continue;
}
const ResKey resKey{ gal::EResourceType::ImageView, GAL_RES_TO_UNTYPE_HNDL(imgViews[i]) };
auto resToEntryItr = imgResToEntryIdx.find(resKey);
/* If the same texture is reused in same request? Try to reuse slot here. */
if (resToEntryItr != imgResToEntryIdx.end())
{
/* Find if texture is already inserted in this table. */
uint64 entryIdx = resToEntryItr->second;
while (isValidTexAt(entryIdx))
{
if (entryIdxToTable(entryIdx) == tableIdx)
{
break;
}
entryIdx = descEntries[entryIdx].nextEntryIdx;
}
if (isValidTexAt(entryIdx))
{
/* Found an entry try to reuse and mark view as exists to skip writing descriptors again */
viewsExists[i] = true;
retVals[i] = entryIdx;
++descEntries[retVals[i]].refCount;
continue;
}
}
SizeT descIdx = 0; SizeT descIdx = 0;
const bool bAllocated = allocator.findFirstUnsetInRange(descIdx, descEntryStartIndex, descEntryEndIndex - descEntryStartIndex); const bool bAllocated = allocator.findFirstUnsetInRange(descIdx, descEntryStartIndex, descEntryEndIndex - descEntryStartIndex);
debugAssert(bAllocated); debugAssert(bAllocated);
@@ -2462,8 +2524,6 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
.bOwningView = false, .bOwningView = false,
}; };
const ResKey resKey{ gal::EResourceType::ImageView, GAL_RES_TO_UNTYPE_HNDL(imgViews[i]) };
auto resToEntryItr = imgResToEntryIdx.find(resKey);
if (resToEntryItr == imgResToEntryIdx.end()) if (resToEntryItr == imgResToEntryIdx.end())
{ {
/* First entry */ /* First entry */
@@ -2475,6 +2535,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
TextureData &thisEntry = descEntries[descIdx]; TextureData &thisEntry = descEntries[descIdx];
TextureData &prevEntry = descEntries[resToEntryItr->second]; TextureData &prevEntry = descEntries[resToEntryItr->second];
thisEntry.bFirstEntry = false;
thisEntry.prevEntryIdx = resToEntryItr->second; thisEntry.prevEntryIdx = resToEntryItr->second;
if (isValidTexAt(prevEntry.nextEntryIdx)) if (isValidTexAt(prevEntry.nextEntryIdx))
{ {
@@ -2488,7 +2549,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
retVals[i] = descIdx; retVals[i] = descIdx;
} }
/* Write only if pipeline is available */ /* Write only if pipeline is available. */
if (rCtx.galContext()->isValid(pipeline)) if (rCtx.galContext()->isValid(pipeline))
{ {
std::vector<gal::UpdateImageDescriptors> writeDescs; std::vector<gal::UpdateImageDescriptors> writeDescs;
@@ -2497,6 +2558,12 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
writeDescInfo.resize(imgViews.size()); writeDescInfo.resize(imgViews.size());
for (SizeT i = 0; i < imgViews.size(); ++i) for (SizeT i = 0; i < imgViews.size(); ++i)
{ {
/* If view already exists no need to update descriptor again */
if (viewsExists[i])
{
continue;
}
writeDescInfo[i] = { writeDescInfo[i] = {
.hndl = descEntries[retVals[i]].viewHndl, .hndl = descEntries[retVals[i]].viewHndl,
.layout = gal::EImageLayout::ShaderReadOnly, .layout = gal::EImageLayout::ShaderReadOnly,
@@ -2516,6 +2583,10 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_RESOURCE_HND(Image)> imgs) std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_RESOURCE_HND(Image)> imgs)
{ {
debugAssert(!imgs.empty()); debugAssert(!imgs.empty());
fatalAssertf(
imgs.size() < descsCountPerTable, "Cannot support {} textures in same descriptor table. Max {} is allowed in this HW", imgs.size(),
descsCountPerTable
);
std::vector<uint64> retVals; std::vector<uint64> retVals;
retVals.resize(imgs.size()); retVals.resize(imgs.size());
@@ -2556,7 +2627,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
bool bNoneReachedEnd = true; bool bNoneReachedEnd = true;
while (bNoneReachedEnd) while (bNoneReachedEnd)
{ {
uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]); const uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]);
bool bAllMatched = true; bool bAllMatched = true;
/* Necessary to increment the image entry index until it reaches the max table index */ /* Necessary to increment the image entry index until it reaches the max table index */
@@ -2603,7 +2674,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
uint64 descsCountLeftToCheck = allocator.size(); uint64 descsCountLeftToCheck = allocator.size();
for (; tableIdx < descsTables.size(); ++tableIdx) for (; tableIdx < descsTables.size(); ++tableIdx)
{ {
const SizeT count = Math::min(descsCountLeftToCheck, descsCountLeftToCheck); const SizeT count = Math::min(descsCountLeftToCheck, descsCountPerTable);
const SizeT freeCount = allocator.countZeroesInRange(tableToEntryIdx(tableIdx), count); const SizeT freeCount = allocator.countZeroesInRange(tableToEntryIdx(tableIdx), count);
descsCountLeftToCheck -= count; descsCountLeftToCheck -= count;
@@ -2613,18 +2684,75 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
} }
} }
/* Reset so that we only allocate new descriptor entry if img does not exist in current selected tableIdx. */
viewsExists.resetRange(0, imgs.size());
if (tableIdx >= descsTables.size()) if (tableIdx >= descsTables.size())
{ {
/* Resize and allocate from the end table index */ /* Resize and allocate from the end table index */
resizeDescs(allocator.size() + imgs.size()); resizeDescs(allocator.size() + imgs.size());
tableIdx = static_cast<uint32>(descsTables.size() - 1); tableIdx = static_cast<uint32>(descsTables.size() - 1);
} }
else
{
/* Find if img entry is found in selected table and reuse it.
* For new but repeatedly used textures the slot reuse happens in next step. */
for (SizeT i = 0; i < imgs.size(); ++i)
{
auto foundEntriesItr = std::find_if(
perImgFoundEntries[i].cbegin(), perImgFoundEntries[i].cend(),
[tableIdx, this](uint64 descEntryIdx)
{
return entryIdxToTable(descEntryIdx) == tableIdx;
}
);
if (foundEntriesItr == perImgFoundEntries[i].cend())
{
continue;
}
viewsExists[i] = true;
retVals[i] = *foundEntriesItr;
++descEntries[retVals[i]].refCount;
}
}
/* Allocate from descriptors in this table's range */ /* Allocate from descriptors in this table's range */
const SizeT descEntryStartIndex = tableToEntryIdx(tableIdx); const SizeT descEntryStartIndex = tableToEntryIdx(tableIdx);
const SizeT descEntryEndIndex = Math::min(descEntryStartIndex + descsCountPerTable, allocator.size()); const SizeT descEntryEndIndex = Math::min(descEntryStartIndex + descsCountPerTable, allocator.size());
for (SizeT i = 0; i < imgs.size(); ++i) for (SizeT i = 0; i < imgs.size(); ++i)
{ {
if (viewsExists[i])
{
continue;
}
const ResKey resKey{ gal::EResourceType::Image, GAL_RES_TO_UNTYPE_HNDL(imgs[i]) };
auto resToEntryItr = imgResToEntryIdx.find(resKey);
/* If the same texture is reused in same request? Try to reuse slot here. */
if (resToEntryItr != imgResToEntryIdx.end())
{
/* Find if texture is already inserted in this table. */
uint64 entryIdx = resToEntryItr->second;
while (isValidTexAt(entryIdx))
{
if (entryIdxToTable(entryIdx) == tableIdx)
{
break;
}
entryIdx = descEntries[entryIdx].nextEntryIdx;
}
if (isValidTexAt(entryIdx))
{
/* Found an entry try to reuse and mark view as exists to skip writing descriptors again */
viewsExists[i] = true;
retVals[i] = entryIdx;
++descEntries[retVals[i]].refCount;
continue;
}
}
SizeT descIdx = 0; SizeT descIdx = 0;
const bool bAllocated = allocator.findFirstUnsetInRange(descIdx, descEntryStartIndex, descEntryEndIndex - descEntryStartIndex); const bool bAllocated = allocator.findFirstUnsetInRange(descIdx, descEntryStartIndex, descEntryEndIndex - descEntryStartIndex);
debugAssert(bAllocated); debugAssert(bAllocated);
@@ -2651,8 +2779,6 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
.bOwningView = true, .bOwningView = true,
}; };
const ResKey resKey{ gal::EResourceType::Image, GAL_RES_TO_UNTYPE_HNDL(imgs[i]) };
auto resToEntryItr = imgResToEntryIdx.find(resKey);
if (resToEntryItr == imgResToEntryIdx.end()) if (resToEntryItr == imgResToEntryIdx.end())
{ {
/* First entry */ /* First entry */
@@ -2686,6 +2812,12 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
writeDescInfo.resize(imgs.size()); writeDescInfo.resize(imgs.size());
for (SizeT i = 0; i < imgs.size(); ++i) for (SizeT i = 0; i < imgs.size(); ++i)
{ {
/* If image already exists no need to update descriptor again */
if (viewsExists[i])
{
continue;
}
writeDescInfo[i] = { writeDescInfo[i] = {
.hndl = descEntries[retVals[i]].viewHndl, .hndl = descEntries[retVals[i]].viewHndl,
.layout = gal::EImageLayout::ShaderReadOnly, .layout = gal::EImageLayout::ShaderReadOnly,

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -113,7 +113,6 @@ public:
void updateRenderThread() noexcept; void updateRenderThread() noexcept;
void clear() noexcept; void clear() noexcept;
/* Below functions are preferred to be called from render thread */ /* Below functions are preferred to be called from render thread */
CBERENDERER_EXPORT void addToDelete(ResourceVariant res, EDeferredDelStrategy deleteStrategy, TickRep durOrFrameCount = 1); CBERENDERER_EXPORT void addToDelete(ResourceVariant res, EDeferredDelStrategy deleteStrategy, TickRep durOrFrameCount = 1);
void addPipelineToDelete(gal::PipelineHndlVariants res, EDeferredDelStrategy deleteStrategy, TickRep durOrFrameCount = 1) void addPipelineToDelete(gal::PipelineHndlVariants res, EDeferredDelStrategy deleteStrategy, TickRep durOrFrameCount = 1)
@@ -524,7 +523,7 @@ private:
SCompShaderBlob shaderSrcBlob; SCompShaderBlob shaderSrcBlob;
/* Necessary as we provide queries for compiled shaders */ /* Necessary as we provide queries for compiled shaders */
CBESpinLock compiledShadersLock; cbe::SpinLock compiledShadersLock;
SCompilationResults compiledResults; SCompilationResults compiledResults;
/* Shaders */ /* Shaders */
std::unordered_map<NameString, RasterShaderResults> rasterShaders; std::unordered_map<NameString, RasterShaderResults> rasterShaders;
@@ -534,11 +533,11 @@ private:
std::array<std::unordered_map<NameString, ShardPermutationMap>, gal::EFeatureSet::MaxFS * MAX_PASS_VARIANTS> materialShaders; std::array<std::unordered_map<NameString, ShardPermutationMap>, gal::EFeatureSet::MaxFS * MAX_PASS_VARIANTS> materialShaders;
CompileTask lastCompileTask{ nullptr }; CompileTask lastCompileTask{ nullptr };
CBESpinLock shaderCompileEventsLock; cbe::SpinLock shaderCompileEventsLock;
ShadersUpdateEvent shadersCompiledEvent; ShadersUpdateEvent shadersCompiledEvent;
std::unordered_map<NameString, NamedShaderUpdateEvent> namedShadersCompiledEvent; std::unordered_map<NameString, NamedShaderUpdateEvent> namedShadersCompiledEvent;
/* Doing separate deregistration queue to allow deregistration from callback. */ /* Doing separate deregistration queue to allow deregistration from callback. */
CBESpinLock shaderCompileEventDeregLock; cbe::SpinLock shaderCompileEventDeregLock;
std::vector<DelegateHandle> shadersCompiledEventDeregQueue; std::vector<DelegateHandle> shadersCompiledEventDeregQueue;
std::vector<std::pair<NameString, DelegateHandle>> namedShadersCompiledEventDeregQueue; std::vector<std::pair<NameString, DelegateHandle>> namedShadersCompiledEventDeregQueue;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date September 2024 * \date September 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2024 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -67,5 +67,6 @@ CBE_MATERIAL_STRUCT_BEGIN(CbeMatStruct1, false)
CBE_MATERIAL_STRUCT_END(CbeMatStruct1) CBE_MATERIAL_STRUCT_END(CbeMatStruct1)
REGISTER_MATERIAL_PARAM_DESC(CbeMatStruct1); REGISTER_MATERIAL_PARAM_DESC(CbeMatStruct1);
REGISTER_GPU_BUFFER_LAYOUT(CbeMatSInnerStruct);
#endif #endif

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -53,6 +53,7 @@ constexpr gal::ESamplerFiltering::Type WG_IMG_FILTER = gal::ESamplerFiltering::N
constexpr static const TChar *COMPUTE_WARP_SIZE_DEF_NAME = TCHAR("G_COMPUTE_WARP_SIZE"); constexpr static const TChar *COMPUTE_WARP_SIZE_DEF_NAME = TCHAR("G_COMPUTE_WARP_SIZE");
constexpr uint32 COMPUTE_WARP_SIZE = 16; constexpr uint32 COMPUTE_WARP_SIZE = 16;
/* This is the max loop unroll count in shader */
constexpr static const TChar *MAX_BATCH_COUNT_DEF_NAME = TCHAR("G_MAX_BATCH_COUNT"); constexpr static const TChar *MAX_BATCH_COUNT_DEF_NAME = TCHAR("G_MAX_BATCH_COUNT");
constexpr uint32 MAX_BATCH_COUNT = 8; constexpr uint32 MAX_BATCH_COUNT = 8;

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date April 2025 * \date April 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -214,7 +214,7 @@ private:
}; };
using SMList = SparseVector<SMStat, BitArraySparsityPolicy, false>; using SMList = SparseVector<SMStat, BitArraySparsityPolicy, false>;
CBESpinLock meshesLock; cbe::SpinLock meshesLock;
SMList meshes; SMList meshes;
AddMeshBatch *addMeshList = nullptr; AddMeshBatch *addMeshList = nullptr;
RemoveMeshData *removeMeshList = nullptr; RemoveMeshData *removeMeshList = nullptr;
@@ -223,14 +223,14 @@ private:
AllocationInUseCounter addMeshAllocTracker; AllocationInUseCounter addMeshAllocTracker;
AllocationInUseCounter rmMeshAllocTracker; AllocationInUseCounter rmMeshAllocTracker;
CBESpinLock drawLayerLock; cbe::SpinLock drawLayerLock;
std::array<DrawLayer, DrawLayerMaxCount> drawLayers; std::array<DrawLayer, DrawLayerMaxCount> drawLayers;
Vector2 orthoSize; Vector2 orthoSize;
/* Draws in the screen space using ortho projection for meshes. */ /* Draws in the screen space using ortho projection for meshes. */
std::array<DrawLayer, DrawLayerMaxCount> drawOrthoLayers; std::array<DrawLayer, DrawLayerMaxCount> drawOrthoLayers;
std::array<DrawLayerAllocTrackers, DrawLayerMaxCount> drawAllocTrackers; std::array<DrawLayerAllocTrackers, DrawLayerMaxCount> drawAllocTrackers;
CBESpinLock selectionProxyLock; cbe::SpinLock selectionProxyLock;
/* Sometimes selection proxy might be valid. /* Sometimes selection proxy might be valid.
* That is because end will not actually clear the selection proxy to reuse the proxy ID * That is because end will not actually clear the selection proxy to reuse the proxy ID
* if same proxy gets used immediately. */ * if same proxy gets used immediately. */
@@ -238,7 +238,7 @@ private:
/* Only used by the renderer to determine upper limit of proxies count to allocate. */ /* Only used by the renderer to determine upper limit of proxies count to allocate. */
uint32 maxProxiesCount = 0; uint32 maxProxiesCount = 0;
CBESpinLock allocatorLock; cbe::SpinLock allocatorLock;
ArenaAllocator<CBEMemory::CBEAllocator> allocator = { DEFAULT_PAGE_SIZE }; ArenaAllocator<CBEMemory::CBEAllocator> allocator = { DEFAULT_PAGE_SIZE };
public: public:

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date July 2024 * \date July 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -68,12 +68,12 @@ void WorldDataTransferManager::resetRenderData()
tempAllocator.reset(); tempAllocator.reset();
} }
void WorldDataTransferManager::addLargeTransfer(LargeTransfer transfer) void WorldDataTransferManager::addLargeTransfer(LargeTransfer transfer, MAYBE_UNUSED std::source_location srcLoc)
{ {
CBE_ASSERT_INSIDE_RENDERTHREAD(); CBE_ASSERT_INSIDE_RENDERTHREAD();
debugAssert(transfer.continuation); debugAssert(transfer.continuation);
#if DEBUG_BUILD #if DEBUG_VALIDATIONS_ENABLED
for (const GpuBufferTransfer &gpuTransfer : transfer.bufferGpu2Gpu) for (const GpuBufferTransfer &gpuTransfer : transfer.bufferGpu2Gpu)
{ {
debugAssert(rCtx.galContext()->isValid(gpuTransfer.srcHndl)); debugAssert(rCtx.galContext()->isValid(gpuTransfer.srcHndl));
@@ -94,6 +94,9 @@ void WorldDataTransferManager::addLargeTransfer(LargeTransfer transfer)
LargeTransfer *thisTransfer = getLargeTransferAllocator().allocateAligned<LargeTransfer>(); LargeTransfer *thisTransfer = getLargeTransferAllocator().allocateAligned<LargeTransfer>();
new (thisTransfer) LargeTransfer(transfer); new (thisTransfer) LargeTransfer(transfer);
thisTransfer->next = nullptr; thisTransfer->next = nullptr;
#if DEBUG_BUILD
thisTransfer->from = srcLoc.function_name();
#endif
if (largeTransfersHead == nullptr) if (largeTransfersHead == nullptr)
{ {
@@ -111,11 +114,11 @@ void WorldDataTransferManager::addLargeTransfer(LargeTransfer transfer)
} }
} }
void WorldDataTransferManager::addSmallTransfer(SmallTransfer transfer) void WorldDataTransferManager::addSmallTransfer(SmallTransfer transfer, MAYBE_UNUSED std::source_location srcLoc)
{ {
CBE_ASSERT_INSIDE_RENDERTHREAD(); CBE_ASSERT_INSIDE_RENDERTHREAD();
#if DEBUG_BUILD #if DEBUG_VALIDATIONS_ENABLED
SizeT calcTotalTransferSize = 0; SizeT calcTotalTransferSize = 0;
for (const CpuBufferTransfer &cpuTransfer : transfer.bufferCpu2Gpu) for (const CpuBufferTransfer &cpuTransfer : transfer.bufferCpu2Gpu)
{ {
@@ -130,6 +133,9 @@ void WorldDataTransferManager::addSmallTransfer(SmallTransfer transfer)
SmallTransfer *thisTransfer = getSmallTransferAllocator().allocateAligned<SmallTransfer>(); SmallTransfer *thisTransfer = getSmallTransferAllocator().allocateAligned<SmallTransfer>();
new (thisTransfer) SmallTransfer(transfer); new (thisTransfer) SmallTransfer(transfer);
thisTransfer->next = nullptr; thisTransfer->next = nullptr;
#if DEBUG_BUILD
thisTransfer->from = srcLoc.function_name();
#endif
if (smallTransfersHead == nullptr) if (smallTransfersHead == nullptr)
{ {
@@ -747,6 +753,14 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
SizeT bytesOffset = bytesTransferred; SizeT bytesOffset = bytesTransferred;
while (lBufferGpuTransfersPending != nullptr) while (lBufferGpuTransfersPending != nullptr)
{ {
/* Early out if nothing to transfer */
if (lBufferGpuTransfersPending->bufferGpu2Gpu.empty())
{
debugAssert(transferIndexOffset == 0 && regionIndexOffset == 0 && bytesTransferred == 0);
lBufferGpuTransfersPending = LinkedListHelpers::next(lBufferGpuTransfersPending);
continue;
}
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenBufferGpuTransfer")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenBufferGpuTransfer"));
SizeT tIdx = 0; SizeT tIdx = 0;
@@ -829,6 +843,14 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
uint32 layerOffset = static_cast<uint32>(bytesTransferred); uint32 layerOffset = static_cast<uint32>(bytesTransferred);
while (lImageGpuTransfersPending != nullptr) while (lImageGpuTransfersPending != nullptr)
{ {
/* Early out if nothing to transfer */
if (lImageGpuTransfersPending->imageGpu2Gpu.empty())
{
debugAssert(transferIndexOffset == 0 && regionIndexOffset == 0 && bytesTransferred == 0);
lImageGpuTransfersPending = LinkedListHelpers::next(lImageGpuTransfersPending);
continue;
}
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenImageGpuTransfer")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenImageGpuTransfer"));
SizeT tIdx = 0; SizeT tIdx = 0;
@@ -980,7 +1002,7 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
rIdx += regionIndexStart; rIdx += regionIndexStart;
regionIndexStart = 0; regionIndexStart = 0;
} }
debugAssert(lImageGpuTransfersPending->imageGpu2Gpu.empty() || (tIdx > 0 && lastLayerOffset > 0)); debugAssert((tIdx > 0 && lastLayerOffset > 0));
imageGpuCopiesIdx += tIdx; imageGpuCopiesIdx += tIdx;
/* Add to transfer index which is used in case terminated in middle */ /* Add to transfer index which is used in case terminated in middle */
tIdx += transferStart; tIdx += transferStart;
@@ -1024,6 +1046,14 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
SizeT bytesOffset = bytesTransferred; SizeT bytesOffset = bytesTransferred;
while (lBufferCpuTransfersPending != nullptr) while (lBufferCpuTransfersPending != nullptr)
{ {
/* Early out if nothing to transfer */
if (lBufferCpuTransfersPending->bufferCpu2Gpu.empty())
{
debugAssert(transferIndexOffset == 0 && bytesTransferred == 0);
lBufferCpuTransfersPending = LinkedListHelpers::next(lBufferCpuTransfersPending);
continue;
}
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenBufferCpuTransfer")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenBufferCpuTransfer"));
SizeT tIdx = 0; SizeT tIdx = 0;
@@ -1127,6 +1157,19 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
uint32 layerOffset = static_cast<uint32>(bytesTransferred); uint32 layerOffset = static_cast<uint32>(bytesTransferred);
while (lImageCpuTransfersPending != nullptr) while (lImageCpuTransfersPending != nullptr)
{ {
/* Early out if nothing to transfer */
if (lImageCpuTransfersPending->imageCpu2Gpu.empty())
{
debugAssert(transferIndexOffset == 0 && regionIndexOffset == 0 && bytesTransferred == 0);
/* Since this is the last transfer must setup the continuation */
outTransferCmds.continuations.emplace_back(lImageCpuTransfersPending->continuation);
lImageCpuTransfersPending->continuation = nullptr;
lImageCpuTransfersPending = LinkedListHelpers::next(lImageCpuTransfersPending);
continue;
}
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenImageCpuTransfer")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("GenImageCpuTransfer"));
SizeT tIdx = 0; SizeT tIdx = 0;
@@ -1928,7 +1971,7 @@ WorldRenderer::DeferredAddTask<std::vector<renderer::MeshId>> WorldRenderer::add
allSms.allMeshInsts.entries.totalCount(), allSms.allMeshInsts.entries.size() + sms.size(), STATIC_MESHES_BASE_COUNT, allSms.allMeshInsts.entries.totalCount(), allSms.allMeshInsts.entries.size() + sms.size(), STATIC_MESHES_BASE_COUNT,
STATIC_MESHES_RESIZE_FACTOR STATIC_MESHES_RESIZE_FACTOR
); );
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max static meshes count {} to {}", allSms.allMeshInsts.entries.totalCount(), ICbeRendererModule::LOG_CATEGORY, "Resizing max static meshes count {} to {}", allSms.allMeshInsts.entries.totalCount(),
newSmsCount newSmsCount
); );
@@ -1975,7 +2018,7 @@ WorldRenderer::DeferredAddTask<std::vector<renderer::MeshId>> WorldRenderer::add
), ),
iAlignment iAlignment
); );
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh indices count {} to {}", allSms.idxsAllocator.capacity(), newCount ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh indices count {} to {}", allSms.idxsAllocator.capacity(), newCount
); );
@@ -1997,7 +2040,7 @@ WorldRenderer::DeferredAddTask<std::vector<renderer::MeshId>> WorldRenderer::add
), ),
vAlignment vAlignment
); );
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh vertex count {} to {}", allSms.verts.allocator.capacity(), ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh vertex count {} to {}", allSms.verts.allocator.capacity(),
newCount newCount
); );
@@ -2285,7 +2328,7 @@ WorldRenderer::DeferredAddTask<std::vector<renderer::MeshId>> WorldRenderer::add
allSms.verts.usableCount = allSms.verts.allocator.capacity(); allSms.verts.usableCount = allSms.verts.allocator.capacity();
} }
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} static meshes", sms.size()); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} static meshes", sms.size());
co_return retIds; co_return retIds;
} }
@@ -2306,7 +2349,7 @@ void WorldRenderer::removeStaticMeshes(ArrayView<renderer::MeshId> sms)
if (const uint32 batchCount = std::get<"batchCount">(smTable); batchCount > constants::SM_BATCHES_INLINE_LIMIT) if (const uint32 batchCount = std::get<"batchCount">(smTable); batchCount > constants::SM_BATCHES_INLINE_LIMIT)
{ {
sceneGpData.free( sceneGpData.free(
std::get<"batchBufferIndex">(smTable), std::get<"batchCount">(smTable), std::get<"batchBufferIndex">(smTable), std::get<"batchByteOffset">(smTable),
static_cast<uint32>(sizeof(GalStaticMeshBatchEntry) * batchCount) static_cast<uint32>(sizeof(GalStaticMeshBatchEntry) * batchCount)
); );
} }
@@ -2365,7 +2408,7 @@ WorldRenderer::addStaticMeshComps(ArrayView<renderer::StaticMeshRenderableDesc>
), ),
constants::GPU_MIN_BITS_PER_ELEMENT constants::GPU_MIN_BITS_PER_ELEMENT
); );
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh components count {} to {}", allSmComps.entries.totalCount(), ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh components count {} to {}", allSmComps.entries.totalCount(),
newSmCompsCount newSmCompsCount
); );
@@ -2529,7 +2572,7 @@ WorldRenderer::addStaticMeshComps(ArrayView<renderer::StaticMeshRenderableDesc>
addMaterialInstRefs(renderer::Mesh_Static, referencedMats); addMaterialInstRefs(renderer::Mesh_Static, referencedMats);
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} static mesh components", smComps.size()); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} static mesh components", smComps.size());
co_return retVals; co_return retVals;
} }
@@ -2648,7 +2691,7 @@ WorldRenderer::addTextures2dRaw(ArrayView<renderer::TextureDescRaw2d> texDescs)
const SizeT newCount = RendererContext::resizeBuffer( const SizeT newCount = RendererContext::resizeBuffer(
allTextures.totalCount(), allTextures.size() + texDescs.size(), TEXTURES_BASE_COUNT, TEXTURES_RESIZE_FACTOR allTextures.totalCount(), allTextures.size() + texDescs.size(), TEXTURES_BASE_COUNT, TEXTURES_RESIZE_FACTOR
); );
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Resizing max textures count {} to {}", allTextures.totalCount(), newCount); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Resizing max textures count {} to {}", allTextures.totalCount(), newCount);
allTextures.resize(newCount); allTextures.resize(newCount);
} }
@@ -2819,7 +2862,7 @@ std::vector<renderer::MaterialId> WorldRenderer::addMaterials(ArrayView<NameStri
/* Resize allMats */ /* Resize allMats */
const SizeT newCount const SizeT newCount
= RendererContext::resizeBuffer(allMats.totalCount(), mats.size() + allMats.size(), MATS_BASE_COUNT, MATS_RESIZE_FACTOR); = RendererContext::resizeBuffer(allMats.totalCount(), mats.size() + allMats.size(), MATS_BASE_COUNT, MATS_RESIZE_FACTOR);
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Resizing max materials count {} to {}", allMats.totalCount(), newCount); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Resizing max materials count {} to {}", allMats.totalCount(), newCount);
allMats.resize(newCount); allMats.resize(newCount);
} }
@@ -2831,7 +2874,7 @@ std::vector<renderer::MaterialId> WorldRenderer::addMaterials(ArrayView<NameStri
}))); })));
recreateShardMaterial(matId); recreateShardMaterial(matId);
} }
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} materials to world!", mats.size()); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} materials to world!", mats.size());
return retVals; return retVals;
} }
@@ -2860,7 +2903,7 @@ void WorldRenderer::removeMaterials(ArrayView<renderer::MaterialId> mats)
for (renderer::EMeshType meshT = renderer::Mesh_Begin; meshT < renderer::Mesh_End; ++meshT) for (renderer::EMeshType meshT = renderer::Mesh_Begin; meshT < renderer::Mesh_End; ++meshT)
{ {
LOG_WARN_C( CBE_LOG_WARN_C(
allMats[matId].maxReservedMeshRefs[meshT] != 0, ICbeRendererModule::LOG_CATEGORY, allMats[matId].maxReservedMeshRefs[meshT] != 0, ICbeRendererModule::LOG_CATEGORY,
"Material {} cleared before all mesh(Type {}) derefs", allMats[matId].materialName, meshT "Material {} cleared before all mesh(Type {}) derefs", allMats[matId].materialName, meshT
); );
@@ -3084,7 +3127,7 @@ WorldRenderer::addMaterialInstances(ArrayRange<renderer::MaterialDataDesc> matIn
newMatInstsCount = RendererContext::resizeBuffer( newMatInstsCount = RendererContext::resizeBuffer(
oldMatInstsCount, matInsts.size() + oldMatInstsCount, MAT_INSTS_BASE_COUNT, MAT_INSTS_RESIZE_FACTOR oldMatInstsCount, matInsts.size() + oldMatInstsCount, MAT_INSTS_BASE_COUNT, MAT_INSTS_RESIZE_FACTOR
); );
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Resizing material instances count {} to {}", oldMatInstsCount, newMatInstsCount); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Resizing material instances count {} to {}", oldMatInstsCount, newMatInstsCount);
const bool bCreated = rCtx.galContext()->createBuffers( const bool bCreated = rCtx.galContext()->createBuffers(
thisMatInstsBuffer, thisMatInstsBuffer,
@@ -3270,7 +3313,7 @@ WorldRenderer::addMaterialInstances(ArrayRange<renderer::MaterialDataDesc> matIn
std::sort(material.shardInsts.begin(), material.shardInsts.end()); std::sort(material.shardInsts.begin(), material.shardInsts.end());
} }
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} material instances", matInsts.size()); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} material instances", matInsts.size());
co_return retVals; co_return retVals;
} }
@@ -3397,7 +3440,7 @@ void WorldRenderer::addMaterialInstRefs(renderer::EMeshType meshType, ArrayView<
} }
const uint64 oldTotalRefsCount = meshRefsTotalCount[meshType]; const uint64 oldTotalRefsCount = meshRefsTotalCount[meshType];
meshRefsTotalCount[meshType] += newTotalRefsAdded; meshRefsTotalCount[meshType] += newTotalRefsAdded;
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Added {} material instances reference. Total resized refs count {}(+{})", matInsts.size(), ICbeRendererModule::LOG_CATEGORY, "Added {} material instances reference. Total resized refs count {}(+{})", matInsts.size(),
meshRefsTotalCount[meshType], newTotalRefsAdded meshRefsTotalCount[meshType], newTotalRefsAdded
); );
@@ -3513,7 +3556,7 @@ void WorldRenderer::removeMaterialInstances(ArrayView<renderer::MaterialDataId>
* Then mark the material for offset recalculation. */ * Then mark the material for offset recalculation. */
for (renderer::EMeshType meshType = renderer::Mesh_Begin; meshType < renderer::Mesh_End; ++meshType) for (renderer::EMeshType meshType = renderer::Mesh_Begin; meshType < renderer::Mesh_End; ++meshType)
{ {
LOG_WARN_C( CBE_LOG_WARN_C(
worldMatInst.meshRefs[meshType] != 0, ICbeRendererModule::LOG_CATEGORY, worldMatInst.meshRefs[meshType] != 0, ICbeRendererModule::LOG_CATEGORY,
"Material Instance {} cleared before all mesh(Type {}) derefs", matInstId, meshType "Material Instance {} cleared before all mesh(Type {}) derefs", matInstId, meshType
); );
@@ -4343,7 +4386,7 @@ bool WorldRenderer::isRendererReady() const
void WorldRenderer::onNewShaderCompiled(const ShadersUpdate &updates) void WorldRenderer::onNewShaderCompiled(const ShadersUpdate &updates)
{ {
LOG(ICbeRendererModule::LOG_CATEGORY, "New/Update shaders compiled!"); CBE_LOG(ICbeRendererModule::LOG_CATEGORY, "New/Update shaders compiled!");
CBE_ENQUEUE_RENDER_COMMAND(ShadersCompiled) CBE_ENQUEUE_RENDER_COMMAND(ShadersCompiled)
( (
[updates, this] [updates, this]
@@ -4412,11 +4455,13 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
{ {
rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync); rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync);
} }
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::frustum_cull::IM_DD_SHADER_NAME); CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::frustum_cull::IM_DD_SHADER_NAME
);
} }
else else
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Compute pipeline({}) create failed!", ICbeRendererModule::LOG_CATEGORY, "Compute pipeline({}) create failed!",
shader_consts::frustum_cull::IM_DD_SHADER_NAME shader_consts::frustum_cull::IM_DD_SHADER_NAME
); );
@@ -4465,14 +4510,14 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
{ {
rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync); rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync);
} }
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}",
shader_consts::frustum_cull::IM_DD_GEN_DRAWLIST_SHADER_NAME shader_consts::frustum_cull::IM_DD_GEN_DRAWLIST_SHADER_NAME
); );
} }
else else
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Compute pipeline({}) create failed!", ICbeRendererModule::LOG_CATEGORY, "Compute pipeline({}) create failed!",
shader_consts::frustum_cull::IM_DD_GEN_DRAWLIST_SHADER_NAME shader_consts::frustum_cull::IM_DD_GEN_DRAWLIST_SHADER_NAME
); );
@@ -4568,7 +4613,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
} }
if (bPipelineCreated) if (bPipelineCreated)
{ {
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {} for Triangle Draws", ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {} for Triangle Draws",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME, shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4576,7 +4621,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
} }
else else
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) for Triangle Draws create failed!", ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) for Triangle Draws create failed!",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME, shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4616,7 +4661,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
} }
if (bPipelineCreated) if (bPipelineCreated)
{ {
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {} for Line Draws", ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {} for Line Draws",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME, shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4624,7 +4669,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
} }
else else
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) for Line Draws create failed!", ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) for Line Draws create failed!",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME, shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4712,7 +4757,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
} }
if (bPipelineCreated) if (bPipelineCreated)
{ {
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {}", ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {}",
shader_consts::direct_draw::DRAW_INSTANCES_SHADER_NAME, shader_consts::direct_draw::DRAW_INSTANCES_SHADER_NAME,
shader_consts::direct_draw::DRAW_INSTANCES_SEL_PROXY_SHADER_NAME shader_consts::direct_draw::DRAW_INSTANCES_SEL_PROXY_SHADER_NAME
@@ -4720,7 +4765,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
} }
else else
{ {
LOG_ERROR( CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) create failed!", ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) create failed!",
shader_consts::direct_draw::DRAW_INSTANCES_SHADER_NAME, shader_consts::direct_draw::DRAW_INSTANCES_SHADER_NAME,
shader_consts::direct_draw::DRAW_INSTANCES_SEL_PROXY_SHADER_NAME shader_consts::direct_draw::DRAW_INSTANCES_SEL_PROXY_SHADER_NAME
@@ -5317,7 +5362,7 @@ void WorldRenderer::recreateSmFrustumCull(WorldRenderer *self, ShaderPipeline *)
rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync); rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync);
} }
self->sceneGpData.setup(p.hndl, shader_consts::GP_SCENE_DATA_ENTRY.first); self->sceneGpData.setup(p.hndl, shader_consts::GP_SCENE_DATA_ENTRY.first);
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::frustum_cull::SM_SHADER_NAME); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::frustum_cull::SM_SHADER_NAME);
} }
/* There is possibility for table to be outdated before pipeline is even created */ /* There is possibility for table to be outdated before pipeline is even created */
if (!rCtx.galContext()->isValid(p.hndl)) if (!rCtx.galContext()->isValid(p.hndl))
@@ -5471,7 +5516,7 @@ void WorldRenderer::recreateLitModelResolve(WorldRenderer *self, ShaderPipeline
{ {
rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync); rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync);
} }
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::model_lit::COLOR_RESOLVE_SHADER_NAME); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::model_lit::COLOR_RESOLVE_SHADER_NAME);
} }
/* There is possibility for table to be outdated before pipeline is even created */ /* There is possibility for table to be outdated before pipeline is even created */
@@ -5551,7 +5596,7 @@ void WorldRenderer::recreateDepthResolve(WorldRenderer *self, ShaderPipeline *)
{ {
rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync); rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync);
} }
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::debugging::DEPTH_RESOLVE_SHADER_NAME); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}", shader_consts::debugging::DEPTH_RESOLVE_SHADER_NAME);
} }
/* There is possibility for table to be outdated before pipeline is even created */ /* There is possibility for table to be outdated before pipeline is even created */
@@ -7619,7 +7664,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
allImDdMeshes.allMeshInsts.entries.totalCount(), allImDdMeshes.allMeshInsts.entries.size() + addedMeshCount, IM_MESHES_BASE_COUNT, allImDdMeshes.allMeshInsts.entries.totalCount(), allImDdMeshes.allMeshInsts.entries.size() + addedMeshCount, IM_MESHES_BASE_COUNT,
IM_MESHES_RESIZE_FACTOR IM_MESHES_RESIZE_FACTOR
); );
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate draw meshes count {} to {}", ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate draw meshes count {} to {}",
allImDdMeshes.allMeshInsts.entries.totalCount(), newImDdMeshCount allImDdMeshes.allMeshInsts.entries.totalCount(), newImDdMeshCount
); );
@@ -7680,7 +7725,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
), ),
iAlignment iAlignment
); );
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate debug draw mesh indices count {} to {}", ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate debug draw mesh indices count {} to {}",
allImDdMeshes.idxsAllocator.capacity(), newCount allImDdMeshes.idxsAllocator.capacity(), newCount
); );
@@ -7703,7 +7748,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
), ),
vAlignment vAlignment
); );
LOG_VERBOSE( CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate debug draw mesh vertex count {} to {}", ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate debug draw mesh vertex count {} to {}",
allImDdMeshes.verts.allocator.capacity(), newCount allImDdMeshes.verts.allocator.capacity(), newCount
); );
@@ -8088,7 +8133,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
/* Complete the add in draw context */ /* Complete the add in draw context */
imDdDrawContext.INTERNAL_completeAddMeshes(meshHndToMeshId); imDdDrawContext.INTERNAL_completeAddMeshes(meshHndToMeshId);
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} immediate debug draw meshes", addedMeshCount); CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Added {} immediate debug draw meshes", addedMeshCount);
} }
if (bImDdMeshResized) if (bImDdMeshResized)

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date July 2024 * \date July 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -126,6 +126,11 @@ public:
std::coroutine_handle<void> continuation; std::coroutine_handle<void> continuation;
/* To help with debugging */
#if DEBUG_BUILD
const char *from;
#endif
LargeTransfer *next = nullptr; LargeTransfer *next = nullptr;
}; };
struct SmallTransfer struct SmallTransfer
@@ -133,6 +138,11 @@ public:
ArrayView<CpuBufferTransfer> bufferCpu2Gpu; ArrayView<CpuBufferTransfer> bufferCpu2Gpu;
SizeT totalCpu2GpuSize = 0; SizeT totalCpu2GpuSize = 0;
/* To help with debugging */
#if DEBUG_BUILD
const char *from;
#endif
SmallTransfer *next = nullptr; SmallTransfer *next = nullptr;
}; };
@@ -182,8 +192,8 @@ public:
return frameData[fDatIdx].smallTransferComplete; return frameData[fDatIdx].smallTransferComplete;
} }
void addLargeTransfer(LargeTransfer transfer); void addLargeTransfer(LargeTransfer transfer, std::source_location srcLoc = std::source_location::current());
void addSmallTransfer(SmallTransfer transfer); void addSmallTransfer(SmallTransfer transfer, std::source_location srcLoc = std::source_location::current());
/* Setting up all frame commands pools after every update as CommandPools gets copied on resize */ /* Setting up all frame commands pools after every update as CommandPools gets copied on resize */
void setupNewFrameData(ArrayView<CommandPools *> frameCmdsPools, SizeT fDatIdx); void setupNewFrameData(ArrayView<CommandPools *> frameCmdsPools, SizeT fDatIdx);
@@ -320,6 +330,8 @@ private:
} }
}; };
// TODO(Jeslas) : Default initialize GAL Resources = {} if ever it gets dynamically recreated, or garbage might point to valid one.
class CBERENDERER_EXPORT WorldRenderer class CBERENDERER_EXPORT WorldRenderer
{ {
public: public:
@@ -525,11 +537,11 @@ private:
* Gets resized on components list resize and material instance references gets added. * Gets resized on components list resize and material instance references gets added.
* Offsets gets calculated from material instance references count. * Offsets gets calculated from material instance references count.
* Gets filled from the culling shader. */ * Gets filled from the culling shader. */
gal::GAL_RESOURCE_HND(Buffer) drawListAndCompInstList; gal::GAL_RESOURCE_HND(Buffer) drawListAndCompInstList = {};
/* One counter and offset for each material instance/data. /* One counter and offset for each material instance/data.
* Gets resized on material instances getting added before. * Gets resized on material instances getting added before.
* Offsets gets filled when material instance references gets added. */ * Offsets gets filled when material instance references gets added. */
gal::GAL_RESOURCE_HND(Buffer) perMatInstCounterAndOffset; gal::GAL_RESOURCE_HND(Buffer) perMatInstCounterAndOffset = {};
static SizeT drawListBufferByteSize(SizeT matRefsCount, uint32 bufferOffsetAlignment) static SizeT drawListBufferByteSize(SizeT matRefsCount, uint32 bufferOffsetAlignment)
{ {
@@ -581,8 +593,8 @@ private:
#pragma region Textures #pragma region Textures
struct WorldTexture struct WorldTexture
{ {
gal::GAL_RESOURCE_HND(Image) img; gal::GAL_RESOURCE_HND(Image) img = {};
gal::GAL_RESOURCE_HND(ImageView) imgView; gal::GAL_RESOURCE_HND(ImageView) imgView = {};
bool bOwnImg:1 = false; bool bOwnImg:1 = false;
bool bOwnImgView:1 = false; bool bOwnImgView:1 = false;
@@ -668,7 +680,7 @@ private:
* 2. Triangles */ * 2. Triangles */
struct ImDdPrimitive struct ImDdPrimitive
{ {
gal::GAL_RESOURCE_HND(Buffer) buffer; gal::GAL_RESOURCE_HND(Buffer) buffer = {};
/* Total count across layers */ /* Total count across layers */
uint64 linesCount = 0; uint64 linesCount = 0;
uint64 trisCount = 0; uint64 trisCount = 0;
@@ -700,7 +712,7 @@ private:
/* First half is Mesh Instances buffer that is filled from CPU and are not ordered. /* First half is Mesh Instances buffer that is filled from CPU and are not ordered.
* Second half is Mesh Instances buffer that gets filled in order based on Instance offsets in Cull shader. * Second half is Mesh Instances buffer that gets filled in order based on Instance offsets in Cull shader.
* No need to memory zero the instances buffer. */ * No need to memory zero the instances buffer. */
gal::GAL_RESOURCE_HND(Buffer) instsBuffer; gal::GAL_RESOURCE_HND(Buffer) instsBuffer = {};
/* Total count Necessary to resize instsBuffer */ /* Total count Necessary to resize instsBuffer */
uint64 maxInstsCount = 0; uint64 maxInstsCount = 0;
/* Below offsets are necessary to setup per layer, per mode culling descriptor sets with ease. */ /* Below offsets are necessary to setup per layer, per mode culling descriptor sets with ease. */
@@ -712,18 +724,18 @@ private:
* Will be prefilled by CPU and offsets points to region in instsBuffer where the visible instance data gets filled in. * Will be prefilled by CPU and offsets points to region in instsBuffer where the visible instance data gets filled in.
* Will only be read by Cull and Draw list fill shaders. * Will only be read by Cull and Draw list fill shaders.
* 1 for each usable ImDdMesh in allImDdMeshes. Gets resized when allImDdMeshes resizes. */ * 1 for each usable ImDdMesh in allImDdMeshes. Gets resized when allImDdMeshes resizes. */
gal::GAL_RESOURCE_HND(Buffer) instsOffsetsBuffer; gal::GAL_RESOURCE_HND(Buffer) instsOffsetsBuffer = {};
/* Will be filled by Cull shader and read by Vertex shader. This counter will be used to skip instances in draw calls. /* Will be filled by Cull shader and read by Vertex shader. This counter will be used to skip instances in draw calls.
* [0 to meshCount) will be the instances that pass the culling counter per mesh. * [0 to meshCount) will be the instances that pass the culling counter per mesh.
* At meshCount it will have draw call counter which gets filled by Fill draw calls shader. * At meshCount it will have draw call counter which gets filled by Fill draw calls shader.
* 1 for each usable ImDdMesh in allImDdMeshes. Gets resized when allImDdMeshes resizes. * 1 for each usable ImDdMesh in allImDdMeshes. Gets resized when allImDdMeshes resizes.
* **NOTE** Must be zero before culling. */ * **NOTE** Must be zero before culling. */
gal::GAL_RESOURCE_HND(Buffer) instsDrawsCountersBuffer; gal::GAL_RESOURCE_HND(Buffer) instsDrawsCountersBuffer = {};
/* Will be prefilled by CPU and offsets points to final draw offsets. Will only be read by command buffer. /* Will be prefilled by CPU and offsets points to final draw offsets. Will only be read by command buffer.
* Filling draw list requires instance offset in instsBuffer(instsOffsets), instsDrawCount(instsDrawsCounters) and mesh table data. * Filling draw list requires instance offset in instsBuffer(instsOffsets), instsDrawCount(instsDrawsCounters) and mesh table data.
* 1 for each usable ImDdMesh in allImDdMeshes. Gets resized when allImDdMeshes resizes. * 1 for each usable ImDdMesh in allImDdMeshes. Gets resized when allImDdMeshes resizes.
* **NOTE** Must be zero before culling. */ * **NOTE** Must be zero before culling. */
gal::GAL_RESOURCE_HND(Buffer) indirectDrawListBuffer; gal::GAL_RESOURCE_HND(Buffer) indirectDrawListBuffer = {};
/* Number of unique meshes drawn per layer and mode */ /* Number of unique meshes drawn per layer and mode */
using DrawsPerDrawMode = std::array<uint32, WorldImDrawContext::DrawModeMaxCount>; using DrawsPerDrawMode = std::array<uint32, WorldImDrawContext::DrawModeMaxCount>;
std::array<DrawsPerDrawMode, WorldImDrawContext::DrawLayerMaxCount> drawsCountPerLayer; std::array<DrawsPerDrawMode, WorldImDrawContext::DrawLayerMaxCount> drawsCountPerLayer;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -176,7 +176,9 @@ void GalWgWindowRenderer::renderDrawCmds(
} }
); );
fdat.geomBytes = totalGeomBytes; fdat.geomBytes = totalGeomBytes;
LOG_ERROR_C(!bGeomBufferRecreated, ICbeRendererModule::LOG_CATEGORY, "Failed to create widget geometry buffer for {}", name); CBE_LOG_ERROR_C(
!bGeomBufferRecreated, ICbeRendererModule::LOG_CATEGORY, "Failed to create widget geometry buffer for {}", name
);
} }
if (!galCtx->isValid(fdat.geomBuffer) || fdat.geomBytes == 0) if (!galCtx->isValid(fdat.geomBuffer) || fdat.geomBytes == 0)
{ {

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date June 2024 * \date June 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -138,7 +138,7 @@ private:
uint32 descTblIdx; uint32 descTblIdx;
}; };
/* resBatches will be accessed by main thread occasionally with WgImGui widget or any widget that uses dynamic textures */ /* resBatches will be accessed by main thread occasionally with WgImGui widget or any widget that uses dynamic textures */
CBESpinLock mainThreadLock; cbe::SpinLock mainThreadLock;
/* Thread shared read and write */ /* Thread shared read and write */
SparseVector<TextureResourceData, BitArraySparsityPolicy> texEntries; SparseVector<TextureResourceData, BitArraySparsityPolicy> texEntries;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin * \author Jeslas Pravin
* \date September 2024 * \date September 2024
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -134,7 +134,7 @@ T getMaterialBuffer(uint baseInstanceIndex)
} }
uint16_t getAnisoSetting() uint16_t getAnisoSetting()
{ {
return 1; return 0;
} }
#if CBE_MAT_SEL_PROXY_PASS #if CBE_MAT_SEL_PROXY_PASS

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date March 2022 * \date March 2022
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -21,8 +21,8 @@ class CoreObjectsModule final : public ICoreObjectsModule
{ {
private: private:
CoreObjectsDB objsDb; CoreObjectsDB objsDb;
CoreObjectGC gc;
CBEPackageManager packMan; CBEPackageManager packMan;
CoreObjectGC gc;
static CoreObjectsDB *objsDbPtr; static CoreObjectsDB *objsDbPtr;

View File

@@ -4,7 +4,7 @@
* \author Jeslas * \author Jeslas
* \date July 2025 * \date July 2025
* \copyright * \copyright
* Copyright (C) Jeslas Pravin, 2022-2025 * Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com * @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root * License can be read in LICENSE file at this repository's root
*/ */
@@ -29,6 +29,7 @@ AssetManager::AssetManager()
, contentDirMountEventHnd(CoreObjectDelegates::onContentDirectoryAdded.bindObject(this, &AssetManager::onContentDirMount)) , contentDirMountEventHnd(CoreObjectDelegates::onContentDirectoryAdded.bindObject(this, &AssetManager::onContentDirMount))
, contentDirUnmountEventHnd(CoreObjectDelegates::onContentDirectoryRemoved.bindObject(this, &AssetManager::onContentDirUnmount)) , contentDirUnmountEventHnd(CoreObjectDelegates::onContentDirectoryRemoved.bindObject(this, &AssetManager::onContentDirUnmount))
#endif #endif
, assetManLock(std::make_unique<std::shared_mutex>())
{} {}
AssetManager::~AssetManager() AssetManager::~AssetManager()
{ {
@@ -59,10 +60,14 @@ AssetManager::~AssetManager()
} }
} }
classToPackages.clear(); classToPackages.clear();
assetManLock.reset();
} }
AssetManager::TreeNodeIdx AssetManager::findAssetUnder(StringView assetName, TreeNodeIdx folderTreeIdx, bool bRecurse) const AssetManager::TreeNodeIdx AssetManager::findAssetUnder(StringView assetName, TreeNodeIdx folderTreeIdx, bool bRecurse) const
{ {
const std::shared_lock readLock{ *assetManLock.get() };
debugAssert(folderTree.isValid(folderTreeIdx)); debugAssert(folderTree.isValid(folderTreeIdx));
std::vector<TreeNodeIdx> folderTreeIdxs; std::vector<TreeNodeIdx> folderTreeIdxs;
folderTree.getChildren(folderTreeIdxs, folderTreeIdx, bRecurse); folderTree.getChildren(folderTreeIdxs, folderTreeIdx, bRecurse);
@@ -89,6 +94,7 @@ void AssetManager::getClassPackages(std::vector<PackageAllocator::AllocHandle> &
children.emplace_back(baseClass); children.emplace_back(baseClass);
IReflectionRuntimeModule::get()->getChildsOf(baseClass, children, bRecurse); IReflectionRuntimeModule::get()->getChildsOf(baseClass, children, bRecurse);
const std::shared_lock readLock{ *assetManLock.get() };
for (CBEClass subclass : children) for (CBEClass subclass : children)
{ {
auto itr = classToPackages.find(subclass); auto itr = classToPackages.find(subclass);
@@ -107,8 +113,10 @@ void AssetManager::getClassPackages(std::vector<PackageAllocator::AllocHandle> &
} }
#if CBE_ENABLE_PACKAGE_REFERENCE_TREE #if CBE_ENABLE_PACKAGE_REFERENCE_TREE
void AssetManager::getPackageReferrers(std::vector<PackageAllocator::AllocHandle> &outPackageHnds, TreeNodeIdx packageIdx) const void AssetManager::getPackageReferrersFromRefTree(std::vector<PackageAllocator::AllocHandle> &outPackageHnds, TreeNodeIdx packageIdx) const
{ {
const std::shared_lock readLock{ *assetManLock.get() };
debugAssert(folderTree.isValid(packageIdx) && folderTree[packageIdx].nodeData.index() == FolderTree_Package); debugAssert(folderTree.isValid(packageIdx) && folderTree[packageIdx].nodeData.index() == FolderTree_Package);
const PackageAllocator::AllocHandle packageHnd = std::get<FolderTree_Package>(folderTree[packageIdx].nodeData); const PackageAllocator::AllocHandle packageHnd = std::get<FolderTree_Package>(folderTree[packageIdx].nodeData);
@@ -120,6 +128,21 @@ void AssetManager::getPackageReferrers(std::vector<PackageAllocator::AllocHandle
referrerLink = LinkedListHelpers::next(referrerLink); referrerLink = LinkedListHelpers::next(referrerLink);
} }
} }
void AssetManager::getPackageDepsFromRefTree(std::vector<PackageAllocator::AllocHandle> &outPackageHnds, TreeNodeIdx packageIdx) const
{
const std::shared_lock readLock{ *assetManLock.get() };
debugAssert(folderTree.isValid(packageIdx) && folderTree[packageIdx].nodeData.index() == FolderTree_Package);
const PackageAllocator::AllocHandle packageHnd = std::get<FolderTree_Package>(folderTree[packageIdx].nodeData);
const AssetPackage *assetPack = packages.getAllocAt(packageHnd);
AssetPackageLinkNode *depLink = assetPack->dependencies;
while (depLink != nullptr)
{
outPackageHnds.emplace_back(depLink->assetAllocHnd);
depLink = LinkedListHelpers::next(depLink);
}
}
#endif #endif
@@ -135,7 +158,7 @@ AssetManager::TreeNodeIdx AssetManager::createFolder(TreeNodeIdx folderIdx, Stri
continue; continue;
} }
const auto &treeNodeData = getFolderInfo(subFolderTreeIdx)->nodeData; const auto &treeNodeData = getFolderInfoNoLock(subFolderTreeIdx)->nodeData;
if (TCharStr::isEqual(std::get<FolderTree_Folder>(treeNodeData).folderName, folderName)) if (TCharStr::isEqual(std::get<FolderTree_Folder>(treeNodeData).folderName, folderName))
{ {
return subFolderTreeIdx; return subFolderTreeIdx;
@@ -149,13 +172,13 @@ AssetManager::TreeNodeIdx AssetManager::createFolder(TreeNodeIdx folderIdx, Stri
pathElems.resize(pathElemIdxs.size() + 2); pathElems.resize(pathElemIdxs.size() + 2);
debugAssert( debugAssert(
!getFolderInfo(pathElemIdxs.front())->contentDir.empty() !getFolderInfoNoLock(pathElemIdxs.front())->contentDir.empty()
&& FileSystemFunctions::dirExists(getFolderInfo(pathElemIdxs.front())->contentDir.getChar()) && FileSystemFunctions::dirExists(getFolderInfoNoLock(pathElemIdxs.front())->contentDir.getChar())
); );
pathElems.front() = getFolderInfo(pathElemIdxs.front())->contentDir; pathElems.front() = getFolderInfoNoLock(pathElemIdxs.front())->contentDir;
for (uint32 i = 0; i < pathElemIdxs.size(); ++i) for (uint32 i = 0; i < pathElemIdxs.size(); ++i)
{ {
pathElems[i + 1] = std::get<FolderTree_Folder>(getFolderInfo(pathElemIdxs[i])->nodeData).folderName; pathElems[i + 1] = std::get<FolderTree_Folder>(getFolderInfoNoLock(pathElemIdxs[i])->nodeData).folderName;
} }
pathElems.back() = folderName; pathElems.back() = folderName;
const String newDirPath = String::join(pathElems.cbegin(), pathElems.cend(), FS_PATH_SEPARATOR); const String newDirPath = String::join(pathElems.cbegin(), pathElems.cend(), FS_PATH_SEPARATOR);
@@ -166,6 +189,7 @@ AssetManager::TreeNodeIdx AssetManager::createFolder(TreeNodeIdx folderIdx, Stri
FileHelper::makeDir(newDirPath); FileHelper::makeDir(newDirPath);
} }
const std::unique_lock writeLock{ *assetManLock.get() };
FolderInfo folderInf{ FolderInfo folderInf{
.folderName = folderName, .folderName = folderName,
.folderId = StringID(folderName), .folderId = StringID(folderName),
@@ -176,10 +200,18 @@ AssetManager::TreeNodeIdx AssetManager::createFolder(TreeNodeIdx folderIdx, Stri
void AssetManager::onPackageDeleted(StringView packagePath) void AssetManager::onPackageDeleted(StringView packagePath)
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager"));
AssetManager::TreeNodeIdx folderTreeIdx;
AssetManager::PackageAllocator::AllocHandle packageHnd;
const AssetPackage *assetPack;
{
const std::shared_lock readLock{ *assetManLock.get() };
auto [folderTreeIdx, packageHnd] = packagePathToPackage(packagePath); std::tie(folderTreeIdx, packageHnd) = packagePathToPackage(packagePath);
const AssetPackage *assetPack = packages.getAllocAt(packageHnd); assetPack = packages.getAllocAt(packageHnd);
}
/* Start writing */
const std::unique_lock writeLock{ *assetManLock.get() };
#if CBE_ENABLE_PACKAGE_REFERENCE_TREE #if CBE_ENABLE_PACKAGE_REFERENCE_TREE
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("ReferenceTree")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("ReferenceTree"));
@@ -217,7 +249,7 @@ void AssetManager::onPackageDeleted(StringView packagePath)
while (folderTree.isValid(parentFolderTreeIdx) && !folderTree.hasChild(parentFolderTreeIdx)) while (folderTree.isValid(parentFolderTreeIdx) && !folderTree.hasChild(parentFolderTreeIdx))
{ {
/* Keep the root always, unless it gets unmounted */ /* Keep the root always, unless it gets unmounted */
if (isRootFolder(parentFolderTreeIdx)) if (isRootFolderNoLock(parentFolderTreeIdx))
{ {
break; break;
} }
@@ -230,34 +262,45 @@ void AssetManager::onPackageDeleted(StringView packagePath)
/* Finally destroy the asset package */ /* Finally destroy the asset package */
destroyAssetPackage(packageHnd); destroyAssetPackage(packageHnd);
} }
void AssetManager::onPackageSaved(Object *pkgObj) void AssetManager::onPackageSaved(MAYBE_UNUSED Object *pkgObj)
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager"));
#if CBE_ENABLE_PACKAGE_REFERENCE_TREE #if CBE_ENABLE_PACKAGE_REFERENCE_TREE
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager"));
Package *package = cast<Package>(pkgObj); Package *package = cast<Package>(pkgObj);
debugAssert(package != nullptr); debugAssert(package != nullptr);
auto [folderTreeIdx, packageHnd] = packagePathToPackage(package->getObjectData().path); AssetManager::TreeNodeIdx folderTreeIdx;
/* If no package found probably new package so ignore. */ AssetManager::PackageAllocator::AllocHandle packageHnd;
if (!folderTree.isValid(folderTreeIdx)) AssetPackage *assetPack;
{
return;
}
/* After inserting the new dependencies, this will only contain dependencies that needs to be removed. */ /* After inserting the new dependencies, this will only contain dependencies that needs to be removed. */
std::unordered_set<NameString> prevDependencies; std::unordered_set<NameString> prevDependencies;
AssetPackage *assetPack = packages.getAllocAt(packageHnd);
AssetPackageLinkNode *oldDep = assetPack->dependencies;
while (oldDep != nullptr)
{ {
prevDependencies.insert(NameString{ packages.getAllocAt(oldDep->assetAllocHnd)->packagePath }); const std::shared_lock readLock{ *assetManLock.get() };
oldDep = LinkedListHelpers::next(oldDep);
std::tie(folderTreeIdx, packageHnd) = packagePathToPackage(package->getObjectData().path);
/* If no package found probably new package so ignore. */
if (!folderTree.isValid(folderTreeIdx))
{
return;
}
assetPack = packages.getAllocAt(packageHnd);
AssetPackageLinkNode *oldDep = assetPack->dependencies;
while (oldDep != nullptr)
{
prevDependencies.insert(NameString{ packages.getAllocAt(oldDep->assetAllocHnd)->packagePath });
oldDep = LinkedListHelpers::next(oldDep);
}
} }
const PackageLoaderInfo *loaderInf = packageManager->getPackageLoader(StringID{ assetPack->packagePath }); const PackageLoaderInfo *loaderInf = packageManager->getPackageLoader(StringID{ assetPack->packagePath });
debugAssert(loaderInf != nullptr && loaderInf->loader != nullptr); debugAssert(loaderInf != nullptr && loaderInf->loader != nullptr);
/* Start writing */
const std::unique_lock writeLock{ *assetManLock.get() };
std::unordered_set<NameString> uniquePkgPath; std::unordered_set<NameString> uniquePkgPath;
for (const PackageDependencyData &dependency : loaderInf->loader->getDependencyObjects()) for (const PackageDependencyData &dependency : loaderInf->loader->getDependencyObjects())
{ {
@@ -313,16 +356,21 @@ void AssetManager::onContentDirMount(StringView contentDir)
} }
/* Erase already inserted folder names */ /* Erase already inserted folder names */
for (const TreeNodeIdx rootIdx : folderTree.getAllRoots())
{ {
debugAssert(!isPackageNode(rootIdx)); const std::shared_lock readLock{ *assetManLock.get() };
debugAssert(!folderTree[rootIdx].contentDir.empty());
const FolderInfo &folderInfo = std::get<FolderTree_Folder>(folderTree[rootIdx].nodeData); for (const TreeNodeIdx rootIdx : folderTree.getAllRoots())
subfolderNames.erase(folderInfo.folderName); {
debugAssert(!isPackageNode(rootIdx));
debugAssert(!folderTree[rootIdx].contentDir.empty());
const FolderInfo &folderInfo = std::get<FolderTree_Folder>(folderTree[rootIdx].nodeData);
subfolderNames.erase(folderInfo.folderName);
}
} }
/* Add new folder names */ /* Add new folder names */
const std::unique_lock writeLock{ *assetManLock.get() };
for (const String &folderName : subfolderNames) for (const String &folderName : subfolderNames)
{ {
FolderInfo folderInf{ .folderName = folderName, .folderId = StringID(folderName) }; FolderInfo folderInf{ .folderName = folderName, .folderId = StringID(folderName) };
@@ -331,6 +379,8 @@ void AssetManager::onContentDirMount(StringView contentDir)
} }
void AssetManager::onContentDirUnmount(StringView contentDir) void AssetManager::onContentDirUnmount(StringView contentDir)
{ {
const std::unique_lock writeLock{ *assetManLock.get() };
for (const TreeNodeIdx rootIdx : folderTree.getAllRoots()) for (const TreeNodeIdx rootIdx : folderTree.getAllRoots())
{ {
debugAssert(!isPackageNode(rootIdx)); debugAssert(!isPackageNode(rootIdx));
@@ -354,6 +404,8 @@ void AssetManager::onContentDirUnmount(StringView contentDir)
void AssetManager::onPackageScanned(PackageLoader *packageLoader, StringView packagePath, StringView packageFilePath, StringView contentDir) void AssetManager::onPackageScanned(PackageLoader *packageLoader, StringView packagePath, StringView packageFilePath, StringView contentDir)
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager"));
const std::unique_lock writeLock{ *assetManLock.get() };
debugAssert(!packageLoader->getContainedObjects().empty()); debugAssert(!packageLoader->getContainedObjects().empty());
auto [treeNodeIdx, packageHnd] = ensureAssetPackageExists(packagePath); auto [treeNodeIdx, packageHnd] = ensureAssetPackageExists(packagePath);
@@ -407,12 +459,13 @@ void AssetManager::onPackageScanned(PackageLoader *packageLoader, StringView pac
alertOncef(bContentDirMatch, "Content directory mismatch. Found \"{}\" Expected \"{}\"", rootInfo.contentDir, contentDir); alertOncef(bContentDirMatch, "Content directory mismatch. Found \"{}\" Expected \"{}\"", rootInfo.contentDir, contentDir);
if (!bContentDirMatch) if (!bContentDirMatch)
{ {
LOG_ERROR( CBE_LOG_ERROR(
CATEGORY, "Package's({}) content directory {} does not match the root folder({}) content directory {}", packagePath, CATEGORY, "Package's({}) content directory {} does not match the root folder({}) content directory {}", packagePath,
contentDir, rootFolder.folderName, rootInfo.contentDir contentDir, rootFolder.folderName, rootInfo.contentDir
); );
LOG(CATEGORY, CBE_LOG(
"If content directory relative path of any asset is colliding, prefix the asset relative path to ensure uniqueness!"); CATEGORY, "If content directory relative path of any asset is colliding, prefix the asset relative path to ensure uniqueness!"
);
} }
} }
@@ -598,7 +651,7 @@ std::pair<AssetManager::TreeNodeIdx, AssetManager::PackageAllocator::AllocHandle
} }
else else
{ {
const TreeNodeIdx parentNodeIdx = pathToFolderIdxInFolderTree(packagePath); const TreeNodeIdx parentNodeIdx = pathToFolderIdxInFolderTreeNoLock(packagePath);
std::vector<TreeNodeIdx> leafNodeIdxs; std::vector<TreeNodeIdx> leafNodeIdxs;
const bool bRecurse = false; const bool bRecurse = false;
folderTree.getChildren(leafNodeIdxs, parentNodeIdx, bRecurse); folderTree.getChildren(leafNodeIdxs, parentNodeIdx, bRecurse);
@@ -623,7 +676,7 @@ std::pair<AssetManager::TreeNodeIdx, AssetManager::PackageAllocator::AllocHandle
return std::make_pair(folderTreeIdx, packageHnd); return std::make_pair(folderTreeIdx, packageHnd);
} }
AssetManager::TreeNodeIdx AssetManager::pathToFolderIdxInFolderTree(StringView packagePath) const AssetManager::TreeNodeIdx AssetManager::pathToFolderIdxInFolderTreeNoLock(StringView packagePath) const
{ {
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("PackagePathToFolder")); CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("PackagePathToFolder"));

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