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

View File

@@ -1,14 +1,13 @@
# Agents.md
## 📁 Repository Overview
The **Cranberry Engine** is a C++ 3D engine that is built using **CMake 3.26+**. The source tree follows a classic *monorepo* layout:
The **Cranberry Engine** is a C++ 3D engine built with CMake3.26+. The source tree follows a classic monorepo layout:
```
Cranberry/ # Root (CMakeLists.txt, global config)
├─ Scripts/ # Helpers and CMake modules
Cranberry/
├─ Scripts/ # Helpers and CMake modules
│ ├─ CMake/ # GlobalConfig, EngineProjectConfig, utilities
│ └─ ... # Other scripts
│ └─ ...
├─ Source/ # Actual engine code
│ ├─ Runtime/ # Core engine modules & runtime executables
│ │ ├─ EngineModules/
@@ -20,10 +19,9 @@ Cranberry/ # Root (CMakeLists.txt, global config)
└─ 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
```bash
# 1. Configure
cmake -S . -B build \
@@ -38,17 +36,16 @@ cmake --build build --config Release --parallel
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
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
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)
@@ -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.
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 | Responsibility | Trigger |
|-------|----------------|---------|
| `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 |
| `ci-agent` | Run CI pipeline (build, tests, clangtidy) | On push to main or PR |
| Agent | Responsibility | Trigger |
|----------------|--------------------------------------------------------------|-------------------------------------------|
| `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 |
| `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.
## 📦 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:
```yaml
@@ -99,5 +94,4 @@ 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 )
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_path ${CMAKE_CURRENT_LIST_DIR} )
set( deps_setup_status_file ${deps_path}/DepsSetupSuccess.txt )

View File

@@ -1,6 +1,6 @@
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
of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,6 +1,6 @@
Open Asset Import Library (assimp)
Copyright (c) 2006-2021, assimp team
Copyright (c) 2006-2026, assimp team
All rights reserved.
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
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.
Redistribution and use in source and binary forms, with or without

View File

@@ -2,17 +2,18 @@
![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.
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.
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.
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
* 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
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.
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
* Install CMake from [CMake]
* Install Visual Studio 2022/2026 from [VisualStudio]
* 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>***
* 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)
* `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`
* `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`
* `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-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
@@ -64,18 +69,20 @@ Many features listed below are supported but tooling still needs to be developed
* Reflection generator for C++
* 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
* Image base lighting
* Point, Spot, Directional lights
* 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.,
* Multiwindow widgets and Input handling
* Supported inputs mouse and keyboard
* World/Actor/Components
* Unity style prefabs
* 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
@@ -116,9 +123,11 @@ Licenses for third party packages used is placed under `Licenses` folder
![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)
[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"
[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"
[Best Practices]: <https://git.jeslaspravin.com/jeslaspravin/Cranberry/raw/branch/main/Docs/BestPractises.md>
[Cranberry Project]: <https://git.jeslaspravin.com/jeslaspravin/Cranberry/projects/2>
[Best Practices]: <https://git.jeslaspravin.com/jeslaspravin/Cranberry/src/branch/main/Docs/BestPractices.md>
[pre-commit webpage]: https://pre-commit.com/
[Github Sponsor]: <https://github.com/sponsors/jeslaspravin>

View File

@@ -4,7 +4,7 @@
# \author Jeslas Pravin
# \date June 2022
# \copyright
# Copyright (C) Jeslas Pravin, 2022-2025
# Copyright (C) Jeslas Pravin, 2022-2026
# @jeslaspravin pravinjeslas@gmail.com
# License can be read in LICENSE file at this repository's root
#
@@ -147,4 +147,42 @@ function( copy_targets_to_thirdparty )
COMMENT "${thirdparty_COMMENT}"
)
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
# \date January 2022
# \copyright
# Copyright (C) Jeslas Pravin, 2022-2025
# Copyright (C) Jeslas Pravin, 2022-2026
# @jeslaspravin pravinjeslas@gmail.com
# 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
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 )
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",
".*\\.natvis",
".*\\.md",
".*\\.yml",
]
# Overrides ignore files pattern

View File

@@ -16,11 +16,35 @@ set( private_includes
${Cranberry_CPP_LIBS_PATH}/tinyobjloader
${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()
target_compile_options( ${target_name} PRIVATE $<$<CXX_COMPILER_ID:MSVC>:/MP> )
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_directories( TARGET_NAME ${target_name}

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date December 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -16,11 +16,14 @@
#include <AssetEditors/IEngineAssetEditor.hpp>
#include <Widgets/WgViewportImGuiLayer.h>
#include <Widgets/WgDetailsImGuiLayer.h>
#include <Widgets/ImGui/CbeImGui.hpp>
#include <IApplicationModule.h>
#include <EditorEngine.hpp>
#include <Widgets/ImGui/WgImGui.h>
#include <CBEPackage.hpp>
#include <EditorHelpers.h>
#include <EditorHelpersTransacted.hpp>
#include <CranberryEngineApp.h>
namespace cbe
{
@@ -40,7 +43,8 @@ public:
void onCloseEditor() final;
MessageResponse sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType) 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 */
private:
@@ -67,9 +71,10 @@ ActorPrefabAssetEditor::ActorPrefabAssetEditor(Object *inAsset, Object *inEditin
.edRoot = this,
.viewportFlags = EViewportFlags::EnableAll | EViewportFlags::NoWorldEdit,
});
detailsLayer = std::make_shared<WgDetailsImGuiLayer>(WgDetailsCreateInfo{
detailsLayer = std::make_shared<WgDetailsImGuiLayer>(cbe::WgDetailsCreateInfo{
.edRoot = this,
.detailWndName = detailsWndName,
.flags = EDetailsFlags::SendCompSelMsgs,
});
WgImGui *wgImGui = gCBEditorEngine->getImGuiWidget();
@@ -114,45 +119,142 @@ void ActorPrefabAssetEditor::onCloseEditor()
cbe::MessageResponse ActorPrefabAssetEditor::sendMessage(MAYBE_UNUSED const MessageData &msgDat, MAYBE_UNUSED ECoreEdMessageType msgType)
{
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)
{
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;
}
break;
}
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();
/* 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 (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.
detailsLayer->onSelectionChanged();
viewportLayer->onSelectionChanged();
cbe::MessageData::SubSelectionData msg{
.objs = newSelObjs,
.proxies = newSelProxies,
};
pushMessage(
cbe::MessageData{
.msg = msg,
},
cbe::CoreEdMessage_SubSelectionChanged
);
}
response.state = MessageResponse::Remove;
break;
}
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;
break;
}
case cbe::CoreEdMessage_SelectionTransformed:
/* 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();
}
if (msgDat.issuerData != std::bit_cast<uint64>(detailsLayer.get()))
if (!bDetailsWgIssuer)
{
detailsLayer->onSelectionTransformed();
}
@@ -170,13 +272,29 @@ cbe::MessageResponse ActorPrefabAssetEditor::sendMessage(MAYBE_UNUSED const Mess
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 */
pushMessage(CoreEdMessage_SelectionTransformed);
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
//////////////////////////////////////////////////////////////////////////
@@ -186,16 +304,87 @@ ActorPrefabAssetFactory::ActorPrefabAssetFactory()
.assetClass = cbe::ActorPrefab::staticType(),
.assetCategory = "Prefabs",
})
, parentPrefabCntx{ .baseClassType = cbe::ActorPrefab::staticType() }
{}
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::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;
}
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)
{
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 */
ActorPrefab *orgPrefab = static_cast<ActorPrefab *>(ci.asset);
ActorPrefab *edPrefab = nullptr;
/* Prefab cannot be transient since it has to be transacted */
if (orgPrefab->getParentPrefab() != nullptr)
{
edPrefab = ActorPrefab::prefabFromActor(EditorHelpers::addActorToWorld(
world, orgPrefab->getParentPrefab(), orgPrefab->getActorTemplate()->getObjectData().name, cbe::ObjFlag_Transient
));
edPrefab = ActorPrefab::prefabFromActor(
EditorHelpers::addActorToWorld(world, orgPrefab->getParentPrefab(), orgPrefab->getActorTemplate()->getObjectData().name, 0)
);
}
else
{
edPrefab = ActorPrefab::prefabFromActor(EditorHelpers::addActorToWorld(
world, orgPrefab->getActorClass(), orgPrefab->getActorTemplate()->getObjectData().name, cbe::ObjFlag_Transient
));
edPrefab = ActorPrefab::prefabFromActor(
EditorHelpers::addActorToWorld(world, orgPrefab->getActorClass(), orgPrefab->getActorTemplate()->getObjectData().name, 0)
);
}
edPrefab->copyFrom(orgPrefab);

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date September 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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
auto editingAssetItr = std::find(editedObjs.cbegin(), editedObjs.cend(), editingAsset);

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date September 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -122,7 +122,7 @@ MessageResponse IEngineAssetEditor::sendMessage(MAYBE_UNUSED const MessageData &
break;
case cbe::CoreEdMessage_ObjsEdited:
{
ArrayView<Object *> editedObjs;
ArrayView<WeakObjectPtr> editedObjs;
if (std::holds_alternative<MessageData::ObjectsList>(msgDat.msg))
{
editedObjs = std::get<MessageData::ObjectsList>(msgDat.msg);
@@ -148,8 +148,15 @@ void IEngineAssetEditor::undo()
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 };
gCBEditorEngine->addNotification(notifyInfo);
@@ -165,8 +172,15 @@ void IEngineAssetEditor::redo()
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 };
gCBEditorEngine->addNotification(notifyInfo);

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date September 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -50,7 +50,10 @@ public:
/* Shortcuts must be handled here. */
virtual void editorShortcuts() {}
/* 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() {}
/* Interface ends */

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -186,8 +186,6 @@ void GenericFieldCustomizer::initSelectionDetails(ClassObjectContext &inOutCntx,
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)
@@ -2487,8 +2485,6 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetbool(Fundam
return editState;
}
constexpr float ASSET_SELECTOR_ICON_SIZE = 40;
constexpr float ASSET_SELECTOR_ICON_FONT_SCALE = 1.5f;
GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointerCntx(ClassObjectContext &cntx)
{
CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());
@@ -2512,7 +2508,7 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
ImGui::BeginGroup();
const bool bSelectionValid = !cntx.objPath.getObjectName().empty();
/* 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";
Color iconBgColor{ ImGui::GetColorU32(ImGuiCol_FrameBg) };
if (bSelectionValid)
@@ -2520,19 +2516,20 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
iconBgColor = cntx.selDetails.classColor;
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. */
const bool bPopupOpenLastFrame = cbe::ImGuiHelpers::isPopupOpen(assetListPopupId);
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);
if (bIconDoubleClicked)
{
if (bSelectionValid)
{
gCBEditorEngine->openAssetEditor(cntx.objPath.getObject());
gCBEditorEngine->openAssetEditor(cntx.objPath);
}
}
else if (bIconClicked)
@@ -2551,157 +2548,16 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
ImGui::SetNextItemWidth(popupItemWidth - (ImGuiHelpers::calcButtonSize(mat_icon::MOP).x * 3));
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::EndGroup();
@@ -2774,6 +2630,167 @@ GenericFieldCustomizer::EEditState GenericFieldCustomizer::drawWidgetClassPointe
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)
{
GenericFieldCustomizer *thisPtr = static_cast<GenericFieldCustomizer *>(args.customizer);

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -12,10 +12,13 @@
#pragma once
#include <ClassDetailsCustomizer.hpp>
#include <bitset>
#include <Widgets/WgEditorConstants.hpp>
#include <Memory/ArenaAllocator.h>
#include <StructDetailsCustomizer.hpp>
#include <bitset>
#include <optional>
namespace cbe
{
class Transaction;
@@ -192,7 +195,7 @@ public:
std::bitset<ELEM_COUNT> multipleValues;
};
/* 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
{
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. */
WeakObjectPtr objPtr;
TChar classInitials[CLASS_INITIALS_COUNT + 1] = {};
AChar classInitials[CLASS_INITIALS_COUNT + 1] = {};
Color classColor;
};
/* Same context type for Raw pointer and ObjectPtr */
@@ -408,6 +411,9 @@ public:
EEditState drawWidgetClassRawPointer(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs);
EEditState drawWidgetClassObjPointer(FieldCustomizationContext &fieldCntx, DrawFieldArgs drawArgs);
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);
static EEditState drawWidgetEnum(EnumContext &cntx);

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -139,7 +139,7 @@ bool TransformComponentCustomizer::drawTransform3dWidget(SelectionClassCustomiza
}
debugAssert(args.ownerEd != nullptr);
args.ownerEd->pushMessage(
args.ownerEd->pushMessageOverwrite(
cbe::MessageData{
.issuerData = std::bit_cast<uint64>(args.drawer),
.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
* \date March 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
#include <AudioImporter.hpp>
#include <Importers/AudioImporter.hpp>
#include <Classes/AudioSource.hpp>
#include <ObjectPathHelpers.h>
#include <EditorHelpers.h>
@@ -31,6 +31,8 @@ bool AudioModuleDecodedImporter::supportsImporting(ImportOption &inOutOptions)
inOutOptions.optionsStruct = &options;
inOutOptions.optionsStructType = AudioImportOptions::staticType();
inOutOptions.prepareProgressCount = 3;
return true;
}
return false;
@@ -54,6 +56,10 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
audio_module_decoded::ImportContext *cntx = new audio_module_decoded::ImportContext();
AssetImporterBase::ImportContext ret{ cntx, ImportContextDtor{ this } };
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Read file"));
}
std::vector<uint8> data;
if (!FileHelper::readBytes(data, inOutOptions.filePath))
{
@@ -80,6 +86,11 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
encoding = cbe::EAudioSrcEncoding::Vorbis;
}
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Decode audio"));
}
ICbeAudioModule *audioModule = ICbeAudioModule::get();
const cbe::audio::DecodeInfo decodeInfo{ .sourceData = data, .encoding = encoding };
cbe::audio::DecodedOutput decodedInfo;
@@ -107,6 +118,11 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
return ret;
}
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Setup Context"));
}
cntx->data = std::move(decodedInfo.pcmFrames.empty() ? data : decodedInfo.pcmFrames);
cntx->createInfo = {
.encodedData = cntx->data,
@@ -123,6 +139,7 @@ AssetImporterBase::ImportContext AudioModuleDecodedImporter::prepareContext(Impo
};
inOutOptions.cntxOptionsStruct = &cntx->cntxOptions;
inOutOptions.cntxOptionsStructType = audio_module_decoded::ContextOptions::staticType();
inOutOptions.importProgressCount = 1;
return ret;
}
@@ -137,6 +154,11 @@ AssetImporterBase::ImportResult AudioModuleDecodedImporter::importAssets(const I
return err;
}
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR("Importing"));
}
/* Now create the package and the asset */
String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR;
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); }
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
* \date March 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -58,8 +58,6 @@ public:
ImportContext prepareContext(ImportOption &inOutOptions) final;
ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final;
std::optional<std::vector<cbe::Object *>> tryImporting(const ImportOption &importOptions) const override;
protected:
void destructImportContext(void *cntx) final;
/* Override ends */

View File

@@ -4,12 +4,12 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 <CBEPackage.hpp>
#include <CBEObjectHelpers.h>
@@ -62,16 +62,16 @@ static void printErrors(uint32 errorCount, EImportErrorCodes errorCode)
switch (errorCode)
{
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;
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;
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;
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;
case ErrorsCount:
default:
@@ -203,11 +203,13 @@ static void calcTangent(
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)
{
/* 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);
const uint64 vertEndIdx = attrib.vertices.size() / 3;
for (uint64 vertIdx = 0; vertIdx != vertEndIdx; ++vertIdx)
{
const uint64 vertXIdx = vertIdx * 3;
@@ -218,7 +220,6 @@ static void transformVertices(tinyobj::attrib_t &attrib, const StaticMeshImportO
attrib.vertices[vertXIdx + 2] = v.z;
}
const uint64 normEndIdx = attrib.normals.size() / 3;
for (uint64 normIdx = 0; normIdx != normEndIdx; ++normIdx)
{
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))
{
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)
{
@@ -245,13 +269,15 @@ static void fillVertexInfo(ImporterSMVertex &vertexData, const tinyobj::attrib_t
Vector3 normal = {};
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], (1.0f - attrib.texcoords[(index.texcoord_index * 2u) + 1u]) };
uvCoord = { attrib.texcoords[(index.texcoord_index * 2u) + 0u], attrib.texcoords[(index.texcoord_index * 2u) + 1u] };
}
if (index.normal_index != -1)
{
normal = { attrib.normals[(index.normal_index * 3u) + 0u], attrib.normals[(index.normal_index * 3u) + 1u],
attrib.normals[(index.normal_index * 3u) + 2u] };
normal = {
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);
@@ -355,9 +381,12 @@ static void load(
{
debugAssert(FACE_MAX_VERTS == 3 && FACE_MAX_VERTS == mesh.mesh.num_face_vertices[faceIdx]);
const 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) + 2u] };
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) + 2u],
};
transformIndices(idxs, outImportData.options);
if (idxs[0].vertex_index == -1 || idxs[1].vertex_index == -1 || idxs[2].vertex_index == -1)
{
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]);
const 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) + 2u] };
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) + 2u],
};
transformIndices(idxs, outImportData.options);
if (idxs[0].vertex_index == -1 || idxs[1].vertex_index == -1 || idxs[2].vertex_index == -1)
{
outImportData.errorsCounter[EImportErrorCodes::InvalidFace]++;
@@ -759,6 +791,7 @@ bool ObjStaticMeshImporter::supportsImporting(ImportOption &inOutOptions)
{
inOutOptions.optionsStruct = &options;
inOutOptions.optionsStructType = StaticMeshImportOptions::staticType();
inOutOptions.prepareProgressCount = 6;
return true;
}
@@ -770,6 +803,11 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
obj_sm::ImportContext *cntx = new obj_sm::ImportContext();
ImportContext ret{ cntx, ImportContextDtor{ this } };
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Load Obj"));
}
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> meshes;
std::vector<tinyobj::material_t> materials;
@@ -782,7 +820,7 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
);
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())
{
@@ -801,8 +839,16 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
return ret;
}
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Transform vertices"));
}
obj_sm::transformVertices(attrib, options);
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Load to intermediate"));
}
obj_sm::IntermediateImportData meshIntermediate;
meshIntermediate.options = options;
CBEMemory::memZero(&meshIntermediate.errorsCounter, sizeof(meshIntermediate.errorsCounter));
@@ -810,12 +856,13 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
{
const bool hasSmoothing = obj_sm::hasSmoothedNormals(mesh);
if (options.bLoadSmoothed && !hasSmoothing)
if (options.bSmoothNormals && !hasSmoothing)
{
obj_sm::smoothAndLoad(meshIntermediate, mesh, attrib, materials);
}
else
{
obj_sm::load(meshIntermediate, mesh, attrib, materials);
}
@@ -824,6 +871,11 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
break;
}
}
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Normalize vertices"));
}
// Normalizing all the vertex normals
for (ImporterSMVertex &vertex : meshIntermediate.vertices)
{
@@ -841,7 +893,7 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
}
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)
{
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 */
std::unordered_map<String, obj_sm::CreateData> createInfoSMs;
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 */
cntx->sms.reserve(createInfoSMs.size());
cntx->contextOpts.importingMeshes.reserve(createInfoSMs.size());
@@ -919,6 +979,9 @@ AssetImporterBase::ImportContext ObjStaticMeshImporter::prepareContext(ImportOpt
}
inOutOptions.cntxOptionsStruct = &cntx->contextOpts;
inOutOptions.cntxOptionsStructType = obj_sm::ContextOptions::staticType();
inOutOptions.importProgressCount = static_cast<uint32>(createInfoSMs.size());
/* +1 scene creation */
inOutOptions.importProgressCount += 1;
return ret;
}
@@ -958,6 +1021,11 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
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;
importedObjs.emplace_back(createStaticMesh(packageName, meshName, std::move(cntxPtr->sms[i].smCi)));
Transform3D actorTf;
@@ -976,6 +1044,11 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
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)));
break;
}
@@ -983,6 +1056,11 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
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;
cbe::Package *worldPackage = cbe::Package::createPackage(packageName, importOptions.importContentPath, false);
debugAssert(worldPackage);
@@ -999,200 +1077,15 @@ AssetImporterBase::ImportResult ObjStaticMeshImporter::importAssets(const Import
importedObjs.insert(importedObjs.begin(), world);
}
else
{
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR(""));
}
}
return ImportSuccess{ .objs = std::move(importedObjs) };
}
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
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -35,7 +35,11 @@ struct StaticMeshImportOptions
bool bImportAllMesh = false;
META_ANNOTATE()
bool bLoadSmoothed = false;
bool bSmoothNormals = false;
/* If the mesh is already left handed */
META_ANNOTATE()
bool bIsLeftHanded = false;
META_ANNOTATE()
float smoothingAngle = 35.0f;
@@ -83,8 +87,6 @@ public:
ImportContext prepareContext(ImportOption &inOutOptions) final;
ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final;
std::optional<std::vector<cbe::Object *>> tryImporting(const ImportOption &importOptions) const override;
protected:
void destructImportContext(void *cntx) final;
/* Override ends */

View File

@@ -4,12 +4,12 @@
* \author Jeslas
* \date June 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 <Logger/Logger.h>
#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)
{
isNormal = true;
LOG_VERBOSE(
CBE_LOG_VERBOSE(
"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",
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")))
{
isNormal = true;
LOG_DEBUG(
CBE_LOG_DEBUG(
"Texture2DImporter", "Texture {} is determined as normal texture based on suffix _N, Please rename texture if not intended",
fileName
);
@@ -111,6 +111,7 @@ bool StbTexture2DImporter::supportsImporting(ImportOption &inOutOptions)
{
inOutOptions.optionsStruct = &options;
inOutOptions.optionsStructType = Texture2DImportOptions::staticType();
inOutOptions.prepareProgressCount = 3;
return true;
}
@@ -139,6 +140,10 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
stb_texture2d::ImportContext *cntx = new stb_texture2d::ImportContext();
AssetImporterBase::ImportContext ret{ cntx, ImportContextDtor{ this } };
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Read file"));
}
std::vector<uint8> data;
if (!FileHelper::readBytes(data, inOutOptions.filePath))
{
@@ -146,6 +151,11 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
return ret;
}
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Import image"));
}
int32 channelsCount = 0;
int32 dimX = 0;
int32 dimY = 0;
@@ -163,6 +173,11 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
return ret;
}
if (inOutOptions.progressCb.isBound())
{
inOutOptions.progressCb(1, TCHAR("Setup Context"));
}
cntx->channelsCount = channelsCount;
cntx->dim = { static_cast<uint16>(dimX), static_cast<uint16>(dimY) };
cntx->data = texelData;
@@ -172,6 +187,7 @@ AssetImporterBase::ImportContext StbTexture2DImporter::prepareContext(ImportOpti
inOutOptions.cntxOptionsStruct = &cntx->contextOpts;
inOutOptions.cntxOptionsStructType = stb_texture2d::ContextOptions::staticType();
inOutOptions.importProgressCount = 2;
return ret;
}
@@ -186,6 +202,11 @@ AssetImporterBase::ImportResult StbTexture2DImporter::importAssets(const ImportO
return err;
}
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR("Copy data"));
}
cbe::Texture2DCreateInfo createInfo;
createInfo.dim = Math::clamp(Short2(cntxPtr->contextOpts.dimensionX, cntxPtr->contextOpts.dimensionY), Short2(1), cntxPtr->dim);
createInfo.numOfChannels = stb_texture2d::CHANNEL_NUM;
@@ -212,6 +233,11 @@ AssetImporterBase::ImportResult StbTexture2DImporter::importAssets(const ImportO
stb::deallocStbBuffer(cntxPtr->data);
if (importOptions.progressCb.isBound())
{
importOptions.progressCb(1, TCHAR("Create texture"));
}
/* Now create the package and the asset */
String packageRelPath = importOptions.relativeDirPath + ObjectPathHelper::OBJECT_OBJECT_SEPARATOR;
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); }
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
* \date June 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -57,8 +57,6 @@ public:
ImportContext prepareContext(ImportOption &inOutOptions) final;
ImportResult importAssets(const ImportOption &importOptions, const ImportContext &cntx) const final;
std::optional<std::vector<cbe::Object *>> tryImporting(const ImportOption &importOptions) const final;
protected:
void destructImportContext(void *cntx) final;
/* 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
* \date December 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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);
cbe::TransformComponent *tfComponent = static_cast<cbe::TransformComponent *>(cntx.getObject());
if (tfComponent == nullptr)
{
return resp;
}
input.imDd.beginSelectionProxy(&cntx);
Transform3D tf = static_cast<cbe::TransformComponent *>(cntx.getObject())->getWorldTransform();
Transform3D tf = tfComponent->getWorldTransform();
tf.setScale(tf.getScale() * TF_SPHERE_SCALE);
input.imDd.drawMeshes(
cbe::WorldImDrawContext::MeshDrawInfo{

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2024
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -51,7 +51,7 @@ private:
struct WgConsolePacket
{
ReferenceCountPtr<WgConsoleStringBuffer> strBuffer;
Logger::LogMsgPacket logPacket;
cbe::Logger::LogMsgPacket logPacket;
};
class WgConsoleImGuiLayer : public IImGuiLayer
@@ -73,10 +73,10 @@ private:
SizeT packetsHead = 0;
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;
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);
};

View File

@@ -4,29 +4,31 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
#include <Widgets/WgContentsImGuiLayer.h>
#include <CbeApplication.h>
#include <CBEPackage.hpp>
#include <CoreObjectGC.h>
#include <Wg/EngineDragPayloads.hpp>
#include <Serialization/ArrayArchiveStream.h>
#include <Serialization/PackageLoader.h>
#include <Serialization/PackageSaver.h>
#include <Widgets/EditorWidgetsHelper.hpp>
#include <Widgets/WgEditorConstants.hpp>
#include <Widgets/WgEditorImGuiLayer.h>
#include <Widgets/ImGui/CbeImGui.hpp>
#include <Types/Platform/LFS/File/FileHelper.h>
#include <Types/Platform/LFS/PathFunctions.h>
#include <Types/Platform/LFS/Paths.h>
#include <Types/Platform/LFS/PlatformLFS.h>
#include <CranberryEngineApp.h>
#include <IApplicationModule.h>
#include <CBEAssetManager.hpp>
#include <Widgets/EditorWidgetsHelper.hpp>
#include <ObjectPathHelpers.h>
#include <Property/PropertyHelper.h>
#include <Widgets/WgEditorConstants.hpp>
#include <Classes/ActorPrefab.hpp>
#include <Classes/World.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 Vector2 HMODAL_POPUP_SIZE = { 500, 200 };
constexpr Vector2 VMODAL_POPUP_SIZE = { 300, 500 };
constexpr const AChar *IMPORT_MODAL_POPUP = "Import Assets";
constexpr const AChar *CREATE_MODAL_POPUP = "New Asset";
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)
{
cbe::Object *asset = cbe::getOrLoad(assetPack->rootObjectPath, assetPack->rootObjectClass);
cbe::gCBEditorEngine->openAssetEditor(asset);
cbe::gCBEditorEngine->openAssetEditor(assetPack->rootObjectPath, assetPack->rootObjectClass);
}
/**
* Approach each delete, copy, move actions from transaction point.
@@ -96,7 +95,7 @@ public:
{
/* Remove the backup directory */
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();
}
}
@@ -124,7 +123,7 @@ public:
const bool bSucceeded = FileHelper::copyFile(
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
);
}
@@ -174,7 +173,7 @@ public:
{
/* Remove the backup directory */
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();
}
}
@@ -210,7 +209,7 @@ public:
FileHelper::makeDir(destDir);
}
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 */
@@ -271,7 +270,7 @@ public:
{
/* Remove the backup directory */
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();
}
}
@@ -353,7 +352,7 @@ private:
FileHelper::makeDir(destDir);
}
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 */
@@ -377,8 +376,7 @@ void WgContentsImGuiLayer::drawImGui()
{
if (assetManager == nullptr)
{
CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());
assetManager = application->getAssetManager();
assetManager = cbe::gCBEditorEngine->getAssetManager();
}
if (assetManager == nullptr)
{
@@ -389,6 +387,9 @@ void WgContentsImGuiLayer::drawImGui()
const ImGuiWindowFlags wndFlags = ImGuiWindowFlags_NoFocusOnAppearing; ///< To not mess with docking parent focus
const cbe::ImGuiScopedWindow window{ cbe::wg_consts::WND_CONTENTS_ID, wndFlags };
ImGui::PopStyleVar();
if (window)
{
if (ImGui::BeginChild(
@@ -428,8 +429,6 @@ void WgContentsImGuiLayer::drawImGui()
drawCwdContents();
}
ImGui::PopStyleVar();
drawImportAssetsModal();
drawDeleteAssetsModal();
drawCreateAssetModal();
@@ -1033,16 +1032,20 @@ void WgContentsImGuiLayer::drawAssetContextMenus(const CwdContent &content)
drawMultiSelectContextMenus();
return;
}
#if DEBUG_VALIDATIONS_ENABLED
{
void *selItr = nullptr;
ImGuiID id;
const bool bHasSel = selections.GetNextSelectedItem(&selItr, &id);
const uint32 cwdContentIdx = imGuiIdToCwdContentIdx(id);
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"))
{
@@ -1080,7 +1083,6 @@ void WgContentsImGuiLayer::drawFolderContextMenus(TreeNodeIdx dirFolderTreeIdx)
return;
}
#if DEBUG_VALIDATIONS_ENABLED
if (selections.Size == 1)
{
void *selItr = nullptr;
@@ -1088,9 +1090,13 @@ void WgContentsImGuiLayer::drawFolderContextMenus(TreeNodeIdx dirFolderTreeIdx)
const bool bHasSel = selections.GetNextSelectedItem(&selItr, &id);
const uint32 cwdContentIdx = imGuiIdToCwdContentIdx(id);
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 bool bCreateAssetOpenLastFrame = cbe::ImGuiHelpers::isPopupOpen(cbe::ImGuiHelpers::getPopupId(createAssetLabel));
@@ -1313,13 +1319,18 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
ImportAssetData &impData = *importData;
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;
/* To allow matching content size every time it appears */
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))
{
bool bImportNextPath = false;
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. */
@@ -1357,7 +1368,7 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
/* Still null? Skip to next path */
if (impData.importer == nullptr)
{
bImportNextPath = true;
impData.bImportNext = true;
}
else
{
@@ -1377,6 +1388,8 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
);
}
ImGui::BeginDisabled(impData.bProcessing);
ImGui::SetNextItemShortcut(ImGuiKey_Escape);
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 (impData.cntx)
{
const AssetImporterBase::ImportResult result = impData.importer->importAssets(impData.importOption, impData.cntx);
if (std::holds_alternative<AssetImporterBase::ImportSuccess>(result))
{
const AssetImporterBase::ImportSuccess &success = std::get<AssetImporterBase::ImportSuccess>(result);
for (cbe::Object *obj : success.objs)
debugAssert(impData.importOption.importProgressCount > 0);
/* +1 for completion in main thread */
const cbe::ProgressTrackerHnd progTracker = cbe::gCBEditorEngine->addProgressTracker(cbe::ProgressTrackerInfo{
.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::save(obj);
cbe::gCBEditorEngine->progressProgressTracker(progTracker, stepsCount, desc);
}
);
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. */
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)
if (std::holds_alternative<AssetImporterBase::ImportSuccess>(result))
{
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
@@ -1424,17 +1514,53 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
if (ImGui::Button("Prepare"))
{
debugAssert(impData.importer != nullptr);
impData.cntx = impData.importer->prepareContext(impData.importOption);
if (impData.importOption.cntxOptionsStruct != nullptr)
debugAssert(impData.importOption.prepareProgressCount > 0);
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>(
impData.importOption.cntxOptionsStructType, impData.importOption.cntxOptionsStruct
);
}
impData.bPrepared = true;
co_await copat::SwitchJobSystemThreadAwaiter{ js, copat::EJobThreadType::WorkerThreads };
/* Prepare the context */
impData.cntx = impData.importer->prepareContext(impData.importOption);
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();
if (cbe::EditorWidgetsHelper::beginPropertiesTable("", 150.f))
{
@@ -1451,7 +1577,7 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
ImGui::EndTable();
}
if (bImportNextPath)
if (impData.bImportNext)
{
impData.pathIdx++;
/* Reset the data cache for a file import */
@@ -1461,6 +1587,8 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
impData.cntxOptionsDrawer.reset();
impData.cntx.reset();
impData.bPrepared = false;
impData.bProcessing = false;
impData.bImportNext = false;
if (impData.pathIdx >= impData.fromPaths.size())
{
/* Close the pop up now. */
@@ -1475,7 +1603,8 @@ void WgContentsImGuiLayer::drawImportAssetsModal()
ImGui::EndPopup();
}
if (!bPopupOpen)
/* If processing, probably the progress bar made this popup to close. */
if (!bPopupOpen && !impData.bProcessing)
{
importData.reset();
}
@@ -1771,9 +1900,19 @@ void WgContentsImGuiLayer::issueMoveAssets(MoveAssetsInfo &&moveInf)
movedDummyTransaction.apply(dummyCollector);
for (uint32 i = 0; i < moveInf.movingAssets.size(); ++i)
{
cbe::Object *orgObj = orgRootObjs[i];
cbe::Object *movedObj = cbe::getOrLoad(movedDummyTransaction.pkgs[i].rootObjectPath, 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;
@@ -1800,7 +1939,7 @@ void WgContentsImGuiLayer::issueMoveAssets(MoveAssetsInfo &&moveInf)
{
thisTransaction->captureObjectBaseline(referrer);
std::vector<cbe::Object *> children;
objectsDb.getChildren(children, referrer->getDbIdx());
objectsDb.getSubobjects(children, referrer->getDbIdx());
for (cbe::Object *child : children)
{
thisTransaction->captureObjectBaseline(child);
@@ -1863,7 +2002,7 @@ void WgContentsImGuiLayer::drawRenameModal()
/* To allow matching content size every time if appears */
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))
{
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 */
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))
{
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<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, treeNodeIdx);
assetManager->getPackageReferrersFromRefTree(referrers, treeNodeIdx);
for (const cbe::AssetManager::PackageAllocator::AllocHandle &hnd : referrers)
{
referrerContents.emplace_back(fillPackageContent(assetManager->getAssetPackage(hnd)->folderTreeIdx));
@@ -2281,6 +2420,7 @@ void WgContentsImGuiLayer::drawDeleteAssetsModal()
return;
}
const CoreObjectsDB &objectsDb = ICoreObjectsModule::get()->objectsDB();
DeleteAssetData &deleteAssetsData = std::get<DeleteAssetData>(assetEditData);
bool bIssueDelete = false;
@@ -2288,7 +2428,7 @@ void WgContentsImGuiLayer::drawDeleteAssetsModal()
/* To allow matching content size every time if appears */
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))
{
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 *replacement = replacementCntx.objPath.isValid() ? replacementCntx.objPath.getObject() : nullptr;
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 */
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 */
thisTransaction->addCustomAction<cbe::SaveAssetsAction>(nullptr, referrers, cbe::SaveAssetsAction::AtRevert);
const CoreObjectsDB &objectsDb = ICoreObjectsModule::get()->objectsDB();
for (cbe::Object *referrer : referrers)
{
thisTransaction->captureObjectBaseline(referrer);
std::vector<cbe::Object *> children;
objectsDb.getChildren(children, referrer->getDbIdx());
objectsDb.getSubobjects(children, referrer->getDbIdx());
for (cbe::Object *child : children)
{
thisTransaction->captureObjectBaseline(child);
@@ -2608,7 +2761,7 @@ void WgContentsImGuiLayer::cutAssets(bool bCutFolder)
std::vector<CwdContent> &referrerContents = moveAssetData.movingAssetsReferrers.emplace_back();
std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, treeNodeIdx);
assetManager->getPackageReferrersFromRefTree(referrers, treeNodeIdx);
for (const cbe::AssetManager::PackageAllocator::AllocHandle &hnd : referrers)
{
referrerContents.emplace_back(fillPackageContent(assetManager->getAssetPackage(hnd)->folderTreeIdx));
@@ -2893,7 +3046,7 @@ void WgContentsImGuiLayer::openRenameAssetOrFolderModal(TreeNodeIdx folderTreeId
);
std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, folderTreeIdx);
assetManager->getPackageReferrersFromRefTree(referrers, folderTreeIdx);
std::vector<CwdContent> &outReferrers = renameData.referrers.emplace_back();
outReferrers.reserve(referrers.size());
@@ -2922,7 +3075,7 @@ void WgContentsImGuiLayer::openRenameAssetOrFolderModal(TreeNodeIdx folderTreeId
renameData.folderAssets.emplace_back(fillPackageContent(childIdx));
std::vector<cbe::AssetManager::PackageAllocator::AllocHandle> referrers;
assetManager->getPackageReferrers(referrers, childIdx);
assetManager->getPackageReferrersFromRefTree(referrers, childIdx);
std::vector<CwdContent> &outReferrers = renameData.referrers.emplace_back();
outReferrers.reserve(referrers.size());
for (cbe::AssetManager::PackageAllocator::AllocHandle pkgAllocHnd : referrers)

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 */
uint32 pathIdx = 0;
/* 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:

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -22,16 +22,29 @@
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
{
const cbe::ICoreAssetEditorRef &edRoot;
std::string detailWndName;
EDetailsFlags flags = EDetailsFlags::None;
};
} // namespace cbe
class WgDetailsImGuiLayer : public IImGuiLayer
{
public:
WgDetailsImGuiLayer(WgDetailsCreateInfo ci);
WgDetailsImGuiLayer(cbe::WgDetailsCreateInfo ci);
~WgDetailsImGuiLayer();
/* IImGuiLayer overrides */
@@ -39,10 +52,17 @@ public:
/* Overrides ends */
void setWorldViewport(cbe::World *world, WorldViewport *viewport);
void resetDetails()
{
/* redo this when sub-selection gets added */
onSelectionChanged();
}
void onSelectionChanged();
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 */
void refreshDetailsDrawers(ArrayView<cbe::Object *> objsModified);
void refreshDetailsDrawers(ArrayView<cbe::WeakObjectPtr> objsModified);
private:
static_assert(!IsTCharWide::value, "Wide character support is not optimized here! It will work but performs bad");
@@ -73,7 +93,7 @@ private:
enum ESelectionVariantIndex
{
Selection_Single,
Selection_Multi
Selection_Multi,
};
using TransformCompNodeData = std::variant<TransformPerCompNodeData, std::vector<TransformPerCompNodeData>>;
using LogicCompData = std::variant<LogicPerCompData, std::vector<LogicPerCompData>>;
@@ -81,6 +101,48 @@ private:
using TfCompTreeTypeList = TL::CreateFrom_t<TransformCompNodeData, TransformCompCommonNodeData>;
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:
bool isMultiSelection() const { return selectedActors.size() > 1; }
void buildTransformCompTree();
@@ -98,6 +160,16 @@ private:
void drawTfNodeDetails(FlatTfCompTree::NodeIdx nodeIdx);
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:
constexpr static const AChar *MULTIPLE_SELECTION_TXT = "Multiple";
constexpr static const float COMP_LIST_FRAME_SIZE = 0.2f;
@@ -124,6 +196,8 @@ private:
FlatTfCompTree::NodeIdx selectedTfNodeIdx = FlatTfCompTree::InvalidIdx;
String filterName;
ComponentManipulationData compEditData;
std::string detailsWndName;
cbe::EDetailsFlags detailsFlags;
};

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -13,6 +13,8 @@
#include <Widgets/WgEditorConstants.hpp>
#include <Widgets/WgViewportImGuiLayer.h>
#include <Widgets/WgDetailsImGuiLayer.h>
#include <ICoreObjectsModule.h>
#include <CoreObjectGC.h>
#include <CbeApplication.h>
#include <EditorHelpers.h>
#include <IApplicationModule.h>
@@ -29,6 +31,9 @@
#include <AssetEditors/IEngineAssetEditor.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)
: ownerRoot(ci.rootEd.get())
, wnd(ci.windowWidget)
@@ -41,9 +46,9 @@ WgEditorImGuiLayer::WgEditorImGuiLayer(const WgEditorCreateInfo &ci)
void WgEditorImGuiLayer::drawImGui()
{
if (bShowDemo)
if (demoData.bShowImGuiDemo)
{
ImGui::ShowDemoWindow(&bShowDemo);
ImGui::ShowDemoWindow(&demoData.bShowImGuiDemo);
}
if (bShowStyleEditor)
{
@@ -51,6 +56,23 @@ void WgEditorImGuiLayer::drawImGui()
ImGui::ShowStyleEditor();
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 */
ImGuiStyle &style = ImGui::GetStyle();
@@ -124,24 +146,138 @@ void WgEditorImGuiLayer::removeMenuExtender(const TChar *menuName, DelegateHandl
}
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)
{
/* Remove the oldest */
NotifyDrawInfo *tailNotification = LinkedListHelpers::tail(notifyHead);
LinkedListHelpers::remove(&notifyHead, tailNotification, nullptr);
tailNotification->~NotifyDrawInfo();
notifyAlloc.free(tailNotification);
currNotifyCount--;
NotifyDrawInfo *rmNotification = LinkedListHelpers::tail(notifyHead);
/* Find earliest non sticky notification */
rmNotification = LinkedListHelpers::rfind(
notifyHead, rmNotification,
[](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) },
.endAt = Time::fromSeconds(info.durrInSeconds) + Time::timeNow(),
.cb = std::move(info.callback),
.bSticky = true,
};
LinkedListHelpers::pushHead(&notifyHead, notification);
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)
@@ -211,7 +347,239 @@ cbe::ETryExit WgEditorImGuiLayer::tryExitEditor()
}
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()
@@ -242,12 +610,11 @@ void WgEditorImGuiLayer::drawEditorWindows()
ImGui::SetNextWindowDockID(ED_DOCK_SPACE);
cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld();
const cbe::World *editorWorld = gCBEEngine->worldManager()->getEditorWorld();
bool bWorldOpen = cbe::isValidFast(mainWorld);
const std::string windowTitle
= std::format("{}{}", bWorldOpen ? TCHAR_TO_UTF8(mainWorld->getObjectData().name) : "None", cbe::wg_consts::ED_MAIN_WINDOW);
ImGuiWindowFlags edWindowFlags = edWndBaseFlags | ImGuiWindowFlags_MenuBar;
if (cbe::isValidFast(editorWorld) && cbe::isPackageDirty(editorWorld))
if (cbe::gCBEditorEngine->getMainEd()->isAssetDirty())
{
edWindowFlags |= ImGuiWindowFlags_UnsavedDocument;
}
@@ -303,6 +670,8 @@ void WgEditorImGuiLayer::drawEditorWindows()
ImGui::PopStyleColor(1);
addMenubar();
drawProgressTrackers();
drawNotifications();
/* 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. */
if (ImGui::GetWindowDockID() == ED_DOCK_SPACE)
{
drawProgressTrackers();
drawNotifications();
}
@@ -504,8 +874,27 @@ void WgEditorImGuiLayer::addMenubar()
if (ImGui::BeginMenu("Developer"))
{
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("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();
}
@@ -626,8 +1015,13 @@ void WgEditorImGuiLayer::undo()
}
if (!editedObjs.empty())
{
std::vector<cbe::WeakObjectPtr> weakObjPtrs{ editedObjs.size() };
for (SizeT i = 0; i < editedObjs.size(); ++i)
{
weakObjPtrs[i] = editedObjs[i];
}
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())
{
std::vector<cbe::WeakObjectPtr> weakObjPtrs{ editedObjs.size() };
for (SizeT i = 0; i < editedObjs.size(); ++i)
{
weakObjPtrs[i] = editedObjs[i];
}
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();
ImGuiViewport *viewport = ImGui::GetMainViewport();
ImGuiViewport *viewport = ImGui::GetWindowViewport() != nullptr ? ImGui::GetWindowViewport() : ImGui::GetMainViewport();
const Vector2 notificationSpacing{ ImGui::GetFontSize(), ImGui::GetTextLineHeight() };
@@ -699,8 +1097,20 @@ void WgEditorImGuiLayer::drawNotifications()
uint32 idx = 0;
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::SetNextWindowSize(notifyWindowSize, ImGuiCond_Always);
ImGui::SetNextWindowSizeConstraints(notifyWindowSize, Vector2{ std::numeric_limits<float>::max() });
ImGui::SetNextWindowBgAlpha(0.8f);
ImGui::Begin(
@@ -708,6 +1118,8 @@ void WgEditorImGuiLayer::drawNotifications()
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove
);
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);
if (cbe::ImGuiHelpers::buttonNoBg("x"))
{
@@ -718,27 +1130,209 @@ void WgEditorImGuiLayer::drawNotifications()
{
notification->cb.invoke();
}
notification->wndHeight = ImGui::GetWindowSize().y;
ImGui::End();
notification = LinkedListHelpers::next(notification);
idx++;
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)
{
remNotification(notificationToRemove);
}
/* Skip to first non sticky notification from back. */
NotifyDrawInfo *tailNotification = LinkedListHelpers::tail(notifyHead);
tailNotification = LinkedListHelpers::rfind(
notifyHead, tailNotification,
[](const NotifyDrawInfo &notification)
{
return !notification.bSticky;
}
);
if (tailNotification != nullptr && tailNotification->endAt <= Time::timeNow())
{
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
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -38,6 +38,19 @@ struct NotificationInfo
/* Optional */
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
class WgEditorImGuiLayer : public IImGuiLayer
@@ -60,6 +73,34 @@ public:
void removeMenuExtender(const TChar *menuName, DelegateHandle handle);
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);
/* Returns true if removed, false means remove cannot be done now.
@@ -68,18 +109,6 @@ public:
void tickAssetEditors(float deltaTime);
cbe::ETryExit tryExitEditor();
private:
void drawEditorWindows();
void addMenubar();
void aboutWindow();
void jobSystemJobsStats();
void handleShortcuts();
void drawNotifications();
void undo();
void redo();
private:
std::unordered_map<std::string, ImGuiDrawInterfaceCallback> menuExtenders;
@@ -99,6 +128,10 @@ private:
WindowingManager *wndManager;
Color wndFrameColor;
//////////////////////////////////////////////////////////////////////////
// Notifications
//////////////////////////////////////////////////////////////////////////
/**
* Notification gets drawn only in scene window.
* This is because that is the only window that can never be undocked and moved.
@@ -109,21 +142,87 @@ private:
std::string name;
TickRep endAt;
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 *prev = nullptr;
NotifyDrawInfo *previous = nullptr;
};
constexpr static const uint32 NOTIFICATION_LINES_COUNT = 2;
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. */
PoolAllocator<NotifyDrawInfo, 16> notifyAlloc;
uint32 maxNotifies = 16;
PoolAllocator<NotifyDrawInfo, NOTIFICATION_POOL_COUNT> notifyAlloc;
uint32 maxNotifies = NOTIFICATION_POOL_COUNT;
uint32 currNotifyCount = 0;
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 bShowImGuiMetricWnd = false;
bool bShowDebugLog = false;
bool bShowImGuiIdStack = false;
bool bShowAbout = 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
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -114,6 +114,9 @@ void WgViewportImGuiLayer::drawImGui()
ImGui::PopStyleVar(2);
if (window)
{
/* Pump any current frame messages. This ensure any queued messages gets processed before core logic. */
ownerRoot->pumpMessages();
cbe::TransactionsLedger &ledger = getTransactionsLedger();
ImGuiWindow *currWindow = ImGui::GetCurrentWindow();
@@ -234,7 +237,7 @@ void WgViewportImGuiLayer::drawImGui()
/* Clear selection if Esc is pressed */
if (ImGui::IsKeyReleased(ImGuiKey::ImGuiKey_Escape))
{
LOG("LogTemp", "Cleared selections!");
CBE_LOG("LogTemp", "Cleared selections!");
worldViewport.clearSelections();
ownerRoot->pushMessage(cbe::CoreEdMessage_SelectionChanged);
}
@@ -396,7 +399,7 @@ void WgViewportImGuiLayer::drawViewportToolBar()
bPlaying = true;
}
ImGui::BeginDisabled(!ownerRoot->isAssetDirt() || bPlaying);
ImGui::BeginDisabled(!ownerRoot->isAssetDirty() || bPlaying);
if (bDrawingMainEd)
{
if (ImGui::Button(cbe::mat_icon::SAVE))
@@ -1413,6 +1416,12 @@ bool WorldViewportTools::drawTfGizmo(bool bCanHandleInputs, Short2 frameSize, Wo
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 */
TfGizmoProxy *tfSelProxy = nullptr;
if (hoverSelProxy && (hoverSelProxy->isType<TfGizmoScaleOrMove>() || hoverSelProxy->isType<TfGizmoRotate>()))
@@ -1957,6 +1966,15 @@ bool WorldViewportTools::drawTfGizmo(bool bCanHandleInputs, Short2 frameSize, Wo
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 (bScale)
@@ -1996,8 +2014,6 @@ void WorldViewportTools::TfGizmoScaleOrMove::setupReference()
{
if (bScale)
{
viewportTools->gizmoRefValue = viewportTools->gizmoTf.getScale();
for (SizeT selIdx = 0; selIdx < viewportTools->selectedActors.size(); ++selIdx)
{
cbe::Actor *selActor = viewportTools->selectedActors[selIdx];
@@ -2007,8 +2023,6 @@ void WorldViewportTools::TfGizmoScaleOrMove::setupReference()
}
else
{
viewportTools->gizmoRefValue = viewportTools->gizmoTf.getTranslation();
for (SizeT selIdx = 0; selIdx < viewportTools->selectedActors.size(); ++selIdx)
{
cbe::Actor *selActor = viewportTools->selectedActors[selIdx];
@@ -2312,38 +2326,103 @@ void WorldViewportTools::TfGizmoScaleOrMove::transformSelection(
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)
{
debugAssert(actor->getRootComponent() != nullptr);
const Vector3 worldScale = actor->getRootComponent()->getWorldScale();
viewportTools->gizmoTf.setTranslation(viewportTools->gizmoTf.getTranslation() + worldOffset);
currMouse3dPt = viewportTools->gizmoTf.getTranslation();
}
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)
if (!viewportTools->selTfComps.empty() || !viewportTools->selTfProxies.empty())
{
if (bScale)
{
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
{
viewportTools->gizmoTf.setTranslation(viewportTools->gizmoTf.getTranslation() + worldOffset);
currMouse3dPt = viewportTools->gizmoTf.getTranslation();
for (cbe::Actor *actor : viewportTools->selectedActors)
if (bScale)
{
debugAssert(actor->getRootComponent() != nullptr);
const Vector3 worldLoc = actor->getRootComponent()->getWorldLocation();
actor->getRootComponent()->setWorldLocation(worldLoc + worldOffset);
for (cbe::Actor *actor : viewportTools->selectedActors)
{
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
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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
DragDrop = 0x4, ///< Allow dragging and dropping into viewport
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,
};
MAKE_ENUM_BIT_OPS(EViewportFlags)
@@ -354,6 +354,11 @@ public:
const Camera &getCamera() const { return defaultCamera; }
WorldViewport &getWorldViewport() { return worldViewport; }
void resetViewport()
{
/* Right now doing selection changed is equivalent to reset viewport drawing */
onSelectionChanged();
}
void onSelectionChanged();
void
onSubselectionChanged(ArrayView<cbe::WeakObjectPtr> selectedObjs, const std::unordered_set<cbe::WorldSelectionProxyRef> &selectedProxies);

View File

@@ -4,14 +4,18 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
#include <EditorEngine.hpp>
#include <Types/Platform/Threading/CoPaT/CoroutineUtilities.h>
#include <Modules/ModuleManager.h>
#include <CBEAssetManager.hpp>
#include <IEditorCore.h>
#include <ICBEEditor.h>
#include <CranberryEngineApp.h>
#include <EditorHelpers.h>
#include <GalWgWindowRenderer.h>
#include <Types/Platform/LFS/PlatformLFS.h>
@@ -19,7 +23,6 @@
#include <Types/Platform/LFS/PathFunctions.h>
#include <Types/Platform/LFS/Paths.h>
#include <IApplicationModule.h>
#include <CbeApplication.h>
#include <Widgets/ImGui/WgImGui.h>
#include <Widgets/WgWindow.h>
#include <Widgets/WgEditorImGuiLayer.h>
@@ -31,6 +34,7 @@
#include <Widgets/NullWidget.h>
#include <Classes/WorldsManager.hpp>
#include <WorldViewport.h>
#include <CoreObjectGC.h>
void tempTest();
void tempTickTest(const ApplicationTimeData &timeData, const Camera &camera);
@@ -58,7 +62,7 @@ EngineMainEd::EngineMainEd()
.edRoot = this,
.viewportFlags = EViewportFlags::EnableAll,
});
detailsLayer = std::make_shared<WgDetailsImGuiLayer>(WgDetailsCreateInfo{
detailsLayer = std::make_shared<WgDetailsImGuiLayer>(cbe::WgDetailsCreateInfo{
.edRoot = this,
.detailWndName = cbe::wg_consts::WND_DETAILS_ID,
});
@@ -92,6 +96,9 @@ EngineMainEd::EngineMainEd()
{
if (bIsMain)
{
bIsPlayInEd = false;
bEditingAssetDirty = false;
viewportLayer->resetWorld(nullptr);
worldLayer->setWorldViewport(nullptr, nullptr);
detailsLayer->setWorldViewport(nullptr, nullptr);
@@ -106,7 +113,7 @@ void EngineMainEd::saveAsset()
cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld();
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. */
mainWorld->copyFrom(editWorld);
@@ -131,27 +138,49 @@ MessageResponse EngineMainEd::sendMessage(const MessageData &msgDat, ECoreEdMess
}
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;
}
case cbe::CoreEdMessage_SelectionChanged:
{
const std::vector<WeakObjPtr<Object>> &newSelObjs = viewportLayer->getWorldViewport().getSelectedObjects();
const std::unordered_set<WorldSelectionProxyRef> &newSelProxies = viewportLayer->getWorldViewport().getSelectedProxies();
worldLayer->onSelectionChanged();
detailsLayer->onSelectionChanged();
viewportLayer->onSelectionChanged();
mainWorldSelObjs = newSelObjs;
mainWorldSelProxies.clear();
mainWorldSelProxies.reserve(newSelProxies.size());
mainWorldSelProxies.insert(mainWorldSelProxies.begin(), newSelProxies.cbegin(), newSelProxies.cend());
response.state = MessageResponse::Remove;
break;
}
@@ -177,9 +206,6 @@ void EngineMainEd::drawEditor() {}
void EngineMainEd::collectReferencesInternal(MAYBE_UNUSED std::vector<cbe::Object *> &outObjects) const {}
void EngineMainEd::onCloseEditor()
{
mainWorldSelProxies.clear();
mainWorldSelObjs.clear();
gCBEEngine->worldManager()->onWorldInitEvent().unbind(worldInitHandle);
gCBEEngine->worldManager()->onWorldUnloadEvent().unbind(worldUnloadHandle);
@@ -211,6 +237,11 @@ void EngineMainEd::onEdTick(MAYBE_UNUSED float deltaTime)
bEditingAssetDirty = cbe::isPackageDirty(world);
bIsPlayInEd = EditorHelpers::isPlayInEd(*gCBEditorEngine->worldManager());
}
else
{
bEditingAssetDirty = false;
bIsPlayInEd = false;
}
}
//////////////////////////////////////////////////////////////////////////
@@ -235,7 +266,10 @@ void EditorEngine::engineStart()
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("EditorStartup"));
const String engContentDir = Paths::engineContentDirectory();
CbeApplication *application = IApplicationModule::get()->getApplication();
CranberryEngineApp *application = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());
assetManager = application->getAssetManager();
/* Initialize font data */
WgImGui::WgImGuiFont fonts[2];
std::vector<uint8> fontData[2];
@@ -300,6 +334,10 @@ void EditorEngine::engineStart()
}
CoreObjectDelegates::broadcastContentDirectoryAdded(engContentDir);
modLoadCbHndl = ModuleManager::get()->onModuleLoad.bindObject(this, &EditorEngine::onModuleLoadCb);
modUnloadCbHndl = ModuleManager::get()->onModuleUnload.bindObject(this, &EditorEngine::onModuleUnloadCb);
refreshTypeInfo();
tempTest();
}
@@ -318,6 +356,9 @@ void EditorEngine::engineExit()
{
tempExitTest();
ModuleManager::get()->onModuleLoad.unbind(modLoadCbHndl);
ModuleManager::get()->onModuleUnload.unbind(modUnloadCbHndl);
mainEd->closeEditor();
/* GC is the last reference holder */
debugAssert(mainEd->refCount() <= 2);
@@ -328,23 +369,91 @@ void EditorEngine::engineExit()
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)
{
debugAssert(obj);
/* 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))
{
cbe::World *mainWorld = gCBEEngine->worldManager()->getMainWorld();
WorldsManager *worldMan = gCBEEngine->worldManager();
cbe::World *mainWorld = worldMan->getMainWorld();
if (newWorld != mainWorld)
{
if (mainWorld != nullptr)
{
gCBEEngine->worldManager()->unloadWorld(mainWorld);
worldMan->unloadWorld(mainWorld);
}
const bool bAsMainWorld = true;
gCBEEngine->worldManager()->initWorld(newWorld, bAsMainWorld);
worldMan->initWorld(newWorld, bAsMainWorld);
ICoreObjectsModule::get()->getGC().waitGc();
}
return nullptr;
@@ -371,6 +480,19 @@ ICoreAssetEditorRef EditorEngine::openAssetEditor(Object *obj)
mainEd->editorLayer->addAssetEditor(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)
{
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); }
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
@@ -443,20 +627,11 @@ COMPILER_PRAGMA(COMPILER_DISABLE_WARNING(WARN_UNREACHABLE_CODE WARN_UNUSED_LOCAL
#include "CbeRendererTypes.h"
#include "ICbeRendererModule.h"
#include "GCReferenceCollector.h"
#include "CoreObjectGC.h"
std::vector<cbe::Actor *> worldCubes;
cbe::ActorPrefab *cubeActorPrefab = nullptr;
constexpr uint32 MATS_COUNT = 17;
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;
// TODO(ASAP) : Keep this until the actual materials are ready.
CBE_MATERIAL_STRUCT_BEGIN(SampleMaterialBuffer, false)
CBE_MATERIAL_STRUCT_FIELD_TEXTURE(color, Linear)
CBE_MATERIAL_STRUCT_FIELD_TEXTURE(normal, Nearest)
@@ -473,122 +648,29 @@ cbe::physics::Body pHeightBody;
std::vector<cbe::physics::Body> pWorldBodies;
bool bUseCubeScene = false;
bool bUseMaterials = 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()
{
String dir = Paths::engineContentDirectory();
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
String importContentTo = PathFunctions::combinePath(Paths::engineRuntimeRoot(), TCHAR("Content"));
String importContentTo = Paths::engineContentDirectory();
// Create cube first
cbe::StaticMesh *cubeMesh = cbe::getOrLoad<cbe::StaticMesh>(TCHAR("Meshes/Cube:Cube"));
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::EditorGeomHelpers::createCube(createInfo, 1.0f);
cubeMesh = EditorHelpers::createStaticMesh(TCHAR("Meshes/Cube"), importContentTo, TCHAR("Cube"), std::move(createInfo));
cbe::save(cubeMesh);
#endif
}
// Create or get cube actor prefab
debugAssert(cubeMesh);
const TChar *cubeActorPath = TCHAR("Prefabs/CubeActor:CubeActor");
cubeActorPrefab = cbe::getOrLoad<cbe::ActorPrefab>(cubeActorPath);
if (cubeActorPrefab == nullptr)
@@ -621,65 +703,6 @@ void tempTestScenes()
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)
{
const TChar *scenePath = TCHAR("Scenes/TestCubes:TestCubes");
@@ -772,71 +795,6 @@ void tempTestScenes()
{
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
}
@@ -850,7 +808,6 @@ void tempTest()
else
{
bUseCubeScene = false;
bUseMaterials = false;
}
}
@@ -894,15 +851,6 @@ void tempTickTest(const ApplicationTimeData &timeData, const Camera &camera)
));
worldCubes.emplace_back(smActorPrefab->getActorTemplate());
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);
@@ -922,45 +870,10 @@ void tempTickTest(const ApplicationTimeData &timeData, const Camera &camera)
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()
{
tempRefCollector.reset();
if (bUseCubeScene)
{
CranberryEngineApp *app = static_cast<CranberryEngineApp *>(IApplicationModule::get()->getApplication());

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -17,9 +17,10 @@
#include <Classes/EngineBase.hpp>
#include <Classes/Actor.hpp>
#include <ObjectPtrs.h>
#include <CbeWorldRenderer/WorldSelectionProxy.hpp>
#include <Transaction/TransactionsLedger.hpp>
#include <ICoreAssetEditor.hpp>
#include <EditorTypes.h>
#include <Widgets/EditorWidgetsHelper.hpp>
#include "EditorEngine.gen.hpp"
@@ -43,7 +44,9 @@ class Texture2D;
class StaticMesh;
struct NotificationInfo;
struct ProgressTrackerInfo;
class EditorEngine;
class AssetManager;
/**
* @brief Inheriting ICoreAssetEditor in order to support the message queue in unified manner.
@@ -88,10 +91,6 @@ private:
DelegateHandle worldInitHandle;
DelegateHandle worldUnloadHandle;
/* For tracking selections only. */
std::vector<WeakObjPtr<Object>> mainWorldSelObjs;
std::vector<WorldSelectionProxyRef> mainWorldSelProxies;
TransactionsLedger undoRedoLedger;
bool bIsPlayInEd:1 = false;
@@ -109,7 +108,7 @@ public:
cbe::ETryExit engineTryExit() override;
void engineExit() override;
/* EngineBase overrides */
void destroy() override;
void onDestructed() override;
/* Override ends */
/* Do not hold reference to pointer */
@@ -117,22 +116,75 @@ public:
EngineMainEd *getMainEd() const { return mainEd.get(); }
const String &transactionFolder() const { return transactionDir; }
TransactionsLedger &getTransactionLedger() const { return *mainEd->getTransactionLedger(); }
AssetManager *getAssetManager() const { return assetManager; }
/* Asset editor helpers */
ICoreAssetEditorRef openAssetEditor(Object *obj);
ICoreAssetEditorRef openAssetEditor(StringView objPath, CBEClass objClass);
ICoreAssetEditorRef openAssetEditor(const ObjectPath &objPath);
void closeAssetEditor(const ICoreAssetEditorRef &editor);
ICoreAssetEditorRef getAssetEditor(Object *obj);
void refreshAssetEditor(Object *obj);
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:
SharedPtr<WgImGui> wgImgui;
ReferenceCountPtr<EngineMainEd> mainEd;
AssetManager *assetManager;
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);
CBEEDITOR_EXPORT extern EditorEngine *gCBEditorEngine;

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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[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.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 + 0);
outCreateInfo.indices.emplace_back(startVertIdx + 3);
outCreateInfo.indices.emplace_back(startVertIdx + 2);
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)
@@ -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)
{
longSegCount = Math::max(longSegCount, 4);

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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;
}
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(
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::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
)
{
@@ -209,24 +233,36 @@ cbe::Object *EditorHelpers::createAsset(
const StringView objName = ObjectPathHelper::getObjectName(packageRelPath);
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{
.assetName = objName,
.clazz = clazz,
.outer = package,
.flags = cbe::ObjFlag_PackageLoaded,
};
if (createCb.isBound())
obj = createCb(assetCi);
}
if (obj == nullptr && !assetFactories.empty())
{
for (cbe::ICoreAssetFactory *factory : assetFactories)
{
obj = createCb(assetCi);
}
else
{
obj = assetFactory->createAsset(assetCi);
if (!factory->canCreateAsset())
{
continue;
}
obj = factory->createAsset(assetCi);
if (obj != nullptr)
{
break;
}
}
}
else
if (obj == nullptr)
{
obj = cbe::create(clazz, objName, package, cbe::ObjFlag_PackageLoaded);
}
@@ -278,12 +314,12 @@ void EditorHelpers::saveEditingAsset(cbe::ICoreAssetEditor *assetEditor, cbe::IC
ArrayArchiveStream archiveStream;
EPackageLoadSaveResult result
= 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 {}",
editingAsset->getObjectData().path
);
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
);
@@ -342,22 +378,6 @@ cbe::Texture2D *EditorHelpers::createTexture2D(
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 *
EditorHelpers::createStaticMesh(const String &packageRelPath, const String &packagePath, StringView name, cbe::SMCreateInfo &&createInfo)
{
@@ -385,12 +405,12 @@ cbe::Actor *EditorHelpers::addStaticMeshesToWorld(
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;
}
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;
}
@@ -425,13 +445,14 @@ cbe::Actor *EditorHelpers::addStaticMeshToWorld(cbe::StaticMesh *sm, const Trans
cbe::Object *modifyingComp
= modifyPrefabCompField(PropertyHelper::findField(smComp->getType(), GET_MEMBER_ID_CHECKED(cbe::StaticMeshComponent, mesh)), smComp);
debugAssert(modifyingComp == smComp);
/* Setting mesh also modifies batchMaterials */
modifyingComp = modifyPrefabCompField(
PropertyHelper::findField(smComp->getType(), GET_MEMBER_ID_CHECKED(cbe::StaticMeshComponent, batchMaterials)), smComp
);
debugAssert(modifyingComp == smComp);
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());
modifyingComp = modifyPrefabCompField(
@@ -502,8 +523,12 @@ cbe::Actor *EditorHelpers::addActorToWorld(cbe::World *world, cbe::ActorPrefab *
cbe::markPackageDirty(world);
return prefab->getActorTemplate();
}
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)
{
@@ -550,6 +575,8 @@ void EditorHelpers::removeComponentFromPrefab(cbe::ActorPrefab *prefab, cbe::Obj
cbe::markPackageDirty(prefab);
}
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)
@@ -558,6 +585,45 @@ cbe::Object *EditorHelpers::modifyComponentInPrefab(cbe::ActorPrefab *prefab, cb
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)
{
debugAssert(comp && prop);
@@ -640,6 +706,11 @@ std::vector<cbe::Object *> EditorHelpers::getActorCompSubObjects(cbe::Actor *act
for (cbe::Object *child : children)
{
if (!cbe::isValidFast(child))
{
continue;
}
if (child->getType() != cbe::ObjectTemplate::staticType())
{
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)
{
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 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 bAddToCompList = cbe::EWorldState::isPreparedState(world->getState()) && bAddToActorPending;
const bool bBroadcast = !bWorldPrepared || (bWorldPrepared && bAddToActorPending);
const bool bAddToCompList = bWorldPrepared && bAddToActorPending;
if (!bBroadcast)
{
return;
@@ -846,27 +918,40 @@ void EditorHelpers::componentAddedToWorld(cbe::World *world, cbe::Actor *actor,
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)
{
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()))
{
world->broadcastLogicCompAdded(component);
if (bAddToCompList)
{
actor->logicComps.insert(static_cast<cbe::LogicComponent *>(component));
}
world->broadcastLogicCompAdded(component);
}
else if (PropertyHelper::isChildOf<cbe::TransformLeafComponent>(component->getType()))
{
world->broadcastLeafCompAdded(component);
if (bAddToCompList)
{
actor->leafComps.insert(static_cast<cbe::TransformLeafComponent *>(component));
}
world->broadcastLeafCompAdded(component);
}
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)
{
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 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 bRemoveFromCompList = cbe::EWorldState::isPreparedState(world->getState()) && bRemoveFromActorPending;
const bool bBroadcast = !bWorldPrepared || (bWorldPrepared && 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)
{
return;
}
/* Just to be safe and not have any hanging references */
if (cbe::TransformComponent *tfComponent = cbe::cast<cbe::TransformComponent>(component))
{
auto compWorldTfItr = world->compToTf.find(tfComponent);
cbe::World::TFHierarchyIdx compTfIdx;
if (compWorldTfItr != world->compToTf.end())
if (bWorldPrepared)
{
compTfIdx = compWorldTfItr->second;
#if DEBUG_VALIDATIONS_ENABLED
std::vector<cbe::World::TFHierarchyIdx> directAttachments;
world->txHierarchy.getChildren(directAttachments, compTfIdx, false);
/* So if ever below assert fails. There is desync in logic on how world components are attached and tree updates */
debugAssert(directAttachments.empty());
#endif // DEBUG_VALIDATIONS_ENABLED
auto compWorldTfItr = world->compToTf.find(tfComponent);
debugAssert(compWorldTfItr != world->compToTf.end());
cbe::World::TFHierarchyIdx compTfIdx = compWorldTfItr->second;
/* Do not try to reattach since root do not have in actor parent to reattach to.
* 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->compToTf.erase(compWorldTfItr);

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -38,6 +38,7 @@ class StaticMesh;
class World;
class Actor;
class TransformComponent;
class TransformLeafComponent;
class MaterialInstance;
class WorldsManager;
@@ -67,6 +68,8 @@ public:
/* Name must be a full path to package */
static void makePackageUnique(String &inOutPackagePath) { makePackageUnique(inOutPackagePath, {}); }
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.
* 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. */
@@ -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 *>> 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(
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
);
static cbe::Object *createEditingAsset(cbe::Object *asset);
@@ -91,8 +95,6 @@ public:
static cbe::Texture2D *
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 *
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
@@ -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, cbe::ActorPrefab *inPrefab, const String &name, EObjectFlags flags);
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.
* 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 void removeComponentFromPrefab(cbe::ActorPrefab *prefab, cbe::Object *comp);
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. */
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 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 componentRemovedFromWorld(cbe::World *world, cbe::Actor *actor, cbe::Object *component, bool bRemoveFromActorPending);

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date December 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 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()
{
@@ -351,6 +353,510 @@ void GenericSaveAssetAction::endTransaction()
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
cbe::ActorPrefab *
@@ -496,4 +1002,201 @@ void EditorHelpers::removeActorsFromWorld(cbe::World *world, ArrayView<cbe::Acto
}
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
* \date December 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -13,9 +13,12 @@
#include <CBEEditorExports.h>
#include <Transaction/Transaction.hpp>
#include <ICoreAssetEditor.hpp>
class CoreObjectsDB;
namespace cbe
{
class ActorPrefab;
/**
* @brief Asset save action can use this to transact editor saving asset to disk.
@@ -51,4 +54,90 @@ private:
cbe::Object *obj;
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

View File

@@ -4,15 +4,39 @@
* \author Jeslas
* \date August 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2024
* 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 "Types/Delegates/Delegate.h"
#include <Types/Delegates/Delegate.h>
#include <CBEObjectTypes.h>
#include <Types/Colors.h>
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
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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))
{
ImGui::PushID(modelObjData.sid.getID());
/* Draw all class level customizations first */
for (const CustomizerContext &cntx : classCustomizers)
{
@@ -122,6 +124,8 @@ void ObjectsDetailsDrawer::drawWidgets(const DrawWidgetArgs &args) const
.bFilterPassed = bFilterPassed,
});
}
ImGui::PopID();
}
}
@@ -295,7 +299,7 @@ bool StructDetailsDrawer::drawWidgets(const DrawWidgetArgs &args) const
String filter = args.filter;
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))
{

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -15,6 +15,7 @@
#include <CBEAssetManager.hpp>
#include <Property/Property.h>
#include <Classes/ActorPrefab.hpp>
#include <EditorTypes.h>
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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -12,6 +12,7 @@
#pragma once
#include <CBEEditorExports.h>
#include <CBEObjectTypes.h>
#include <Widgets/WgEditorConstants.hpp>
#include <String/String.h>
#include <Widgets/ImGui/CbeImGui.hpp>
@@ -24,6 +25,7 @@ class Transform3D;
namespace cbe
{
struct EdClassCacheList;
class Object;
class AssetManager;
enum class ECloseConfirmationState : uint8
@@ -87,6 +89,8 @@ public:
/* 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 CBEClass drawClassMenuList(EdClassCacheList &classList, float popupItemWidth, bool bShowFilter);
private:
EditorWidgetsHelper() = default;
};

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -39,6 +39,8 @@
#define CBE_MAT_ICON_COPY "\xEE\x85\x8D"
/* \ue14f */
#define CBE_MAT_ICON_PASTE "\xEE\x85\x8F"
/* \ue5c9 */
#define CBE_MAT_ICON_CANCEL "\xEE\x97\x89"
/* \ue2c7 */
#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 *COPY = CBE_MAT_ICON_COPY;
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_OPEN = CBE_MAT_ICON_FOLDER_OPEN;
@@ -150,6 +153,17 @@ constexpr const uint32 DEFAULT_LAYER_DEPTH = 1;
constexpr ImGuiTreeNodeFlags PROP_TREE_NODE_FLAGS
= ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_SpanAllColumns | ImGuiTreeNodeFlags_LabelSpanAllColumns | ImGuiTreeNodeFlags_Framed;
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 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 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_SECONDARY = ColorConst::WHEAT;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -14,7 +14,7 @@
#include "Base/BaseCommon.hlsli"
config
config
{
model=Lit
materialInput="SampleMaterialBuffer"
@@ -27,12 +27,14 @@ node positionOffset
node diffuseColor
{
return CBE_MAT_SAMPLE_TEXTURE(color, CBE_TEXTURE_COORD());
return
CBE_MAT_SAMPLE_TEXTURE(color, CBE_TEXTURE_COORD());
}
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/MaterialInstanceAssetEditor.cpp
Private/AssetEditors/MaterialInstanceAssetEditor.hpp
Private/AudioImporter.cpp
Private/AudioImporter.hpp
Private/CBEEditorModule.cpp
Private/DetailCustomizers/GenericFieldCustomizer.cpp
Private/DetailCustomizers/GenericFieldCustomizer.hpp
@@ -16,10 +14,16 @@ set(cpp_target_sources
Private/DetailCustomizers/MaterialInstanceCustomizer.hpp
Private/DetailCustomizers/TransformComponentCustomizer.cpp
Private/DetailCustomizers/TransformComponentCustomizer.hpp
Private/ObjStaticMeshImporter.cpp
Private/StaticMeshImporter.hpp
Private/Texture2DImporter.cpp
Private/Texture2DImporter.hpp
Private/Importers/AssimpImporter.cpp
Private/Importers/AssimpImporter.hpp
Private/Importers/AudioImporter.cpp
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.hpp
Private/ViewportDrawers/TransformComponentViewportDrawer.cpp

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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());
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;
}
@@ -243,7 +243,7 @@ void EditorCoreModule::registerAssetFactory(cbe::ICoreAssetFactory *editorFactor
auto itr = assetFactories.find(editorFactory->getAssetClass());
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;
}

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date September 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -70,14 +70,81 @@ void ICoreAssetEditor::closeEditor()
{
factory->onCloseAssetEditor(this);
}
editingAsset = nullptr;
asset = nullptr;
}
MAKE_ENUM_ARITHMETIC_OPS(ECoreEdMessageType)
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. */
while (bAnyMsgs)
{
/* Immediately clear the any messages flags to allow processing messages sent by another message. */
bAnyMsgs = false;
bool bAnySticky = false;
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. */
debugAssert(messages[msgType].lastHandleState == MessageResponse::None);
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,
msgsCopy[msgType].lastHandleState, editObjName
);
@@ -125,49 +192,12 @@ void ICoreAssetEditor::edTick(float deltaTime)
/* If anything became sticky */
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)
{
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
{

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date September 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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.
* @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.
* Caller must handle the false case.
@@ -154,7 +154,7 @@ enum ECoreEdMessageType : uint8
CoreEdMessage_SelectionTransformed, ///< When selection gets moved interactively.
CoreEdMessage_Custom, ///< Custom messages from this and beyond.
CoreEdMessage_MaxCount = std::numeric_limits<uint8>::max(),
CoreEdMessage_Begin = CoreEdMessage_RefreshEd,
CoreEdMessage_Begin = CoreEdMessage_ObjsEdited,
CoreEdMessage_End = CoreEdMessage_MaxCount,
};
struct MessageResponse
@@ -169,9 +169,15 @@ struct MessageResponse
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
{
using ObjectsList = std::vector<cbe::Object *>;
using ObjectsList = std::vector<WeakObjectPtr>;
struct SubSelectionData
{
std::vector<WeakObjectPtr> objs;
@@ -179,7 +185,7 @@ struct MessageData
};
/* EmptyType is if the message has no data associated to it like refresh.
* 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. */
uint64 issuerData = 0;
/* 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 *getEditingAsset() const { return editingAsset; }
bool isAssetDirt() const { return bEditingAssetDirty; }
bool isAssetDirty() const { return bEditingAssetDirty; }
ICoreAssetFactory *getOwningFactory() const { return factory; }
/* Called when asset editor is about to be closed. */
void closeEditor();
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(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 */
void clearReferences(ArrayView<cbe::Object *> deletedObjects) final;
@@ -220,7 +240,7 @@ public:
/* Interfaces */
public:
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)
{
return MessageResponse{ .state = MessageResponse::NotHandled };

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date December 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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)
public:
virtual Transform3D getRelativeTransform() const { return getTransform(); }
/* Absolute in world space */
virtual Transform3D getTransform() const = 0;
virtual void setTransform(const Transform3D &worldTf) = 0;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -58,7 +58,7 @@ uint32 ApplicationModule::getThreadingConstraints()
IApplicationModule *IApplicationModule::get()
{
static WeakModulePtr appModule = ModuleManager::get()->getOrLoadModule(TCHAR("Application"));
const static WeakModulePtr appModule = ModuleManager::get()->getOrLoadModule(TCHAR("Application"));
if (appModule.expired())
{
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. */
if (!ProgramCmdLine::get().parse(app->getCmdLine()))
{
LOG_ERROR("Application", "Invalid command line");
CBE_LOG_ERROR("Application", "Invalid command line");
ProgramCmdLine::get().printCommandLine();
}
ProgramCmdLine::get().setProgramDescription(TCHAR("Cranberry application - ") + appCI.applicationName);
if (ProgramCmdLine::get().hasArg(CMDLINE_PROFILESTARTUP))
{
LOG("Application", "Waiting for profiler...");
CBE_LOG("Application", "Waiting for profiler...");
StopWatch waitTime;
CBEProfiler::waitForConnection();
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. */
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"));
Logger::flushStream();
LOG("Application", "{} application start", appCI.applicationName);
cbe::Logger::flushStream();
CBE_LOG("Application", "{} application start", appCI.applicationName);
app->startApp();
}
/* Post Init */
/* Post-Init */
{
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("4: Post App"));
ModuleManager::get()->progressLoadingStage(ModLoadingStage_PostEngineStart);
Logger::flushStream();
cbe::Logger::flushStream();
ModuleManager::get()->progressLoadingStage(ModLoadingStage_Last);
Logger::flushStream();
cbe::Logger::flushStream();
}
/* Pre app tick initialization */
/* Pre-app tick initialization */
app->timeData.tickStart();
/* Run the app and wait for it to quit */
app->getJobSystem()->joinMain();
LOG("Application", "{} application exit", appCI.applicationName);
app->exitApp();
/* Exit app */
{
CBE_LOG("Application", "{} application exit", appCI.applicationName);
app->exitApp();
}
Logger::flushStream();
cbe::Logger::flushStream();
/* Finish all jobs and shutdown */
app->getJobSystem()->shutdown();
delete app->jobSystem;
/* Has to be done after shutdown to complete log flush */
Logger::stopLoggingTime();
cbe::Logger::stopLoggingTime();
static_cast<ApplicationModule *>(this)->app = nullptr;
delete inputSystem;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date January 2022
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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_PROFILER_MESSAGE_LC("Hello Profiler! Cranberry Here!", ColorConst::GREEN);
Logger::initialize();
cbe::Logger::initialize();
ModuleManager::get()->setAutoLoadEnable(true);
if (bHelpRun)
{
/* 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. */
ModuleManager::get()->progressLoadingStage(ModLoadingStage_Manual);
@@ -50,7 +50,7 @@ int32 IApplicationModule::startApplication(const AppCreateInfo &appCI)
}
else
{
LOG_DEBUG("CommandLine", "Command [{}]", appCI.cmdLine);
CBE_LOG_DEBUG("CommandLine", "Command [{}]", appCI.cmdLine);
/* App Core */
{
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("Load Core"));
@@ -78,9 +78,9 @@ int32 IApplicationModule::startApplication(const AppCreateInfo &appCI)
ModuleManager::get()->unloadAll(false);
UnexpectedErrorHandler::getHandler()->unregisterFilter();
Logger::flushStream();
cbe::Logger::flushStream();
Logger::shutdown();
cbe::Logger::shutdown();
return 0;
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -51,7 +51,7 @@ void ApplicationTimeData::tickStart()
initEndTick = Time::timeNow();
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()
@@ -184,7 +184,7 @@ NODISCARD bool CbeApplication::appTick()
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("Tick"));
onTick();
Logger::flushStream();
cbe::Logger::flushStream();
timeData.progressFrame();
}
@@ -198,7 +198,7 @@ void CbeApplication::exitApp()
clearWidgets();
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)

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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.maxBound.x * dpiScaling), int32(windowBound.maxBound.y * dpiScaling) },
};
const WindowCreateInfo mainWndCi{
WindowCreateInfo mainWndCi{
.initialName = applicationName.getChar(),
.region = windowBound,
.bMaximized = EDITOR_BUILD == 0,
@@ -230,6 +230,11 @@ void CbeApplication::createMainWnd() noexcept
.bDrawTitlebar = 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);
inputSystem->registerWindow(windowManager->getWindowInfo(windowManager->getMainWindow()));
}
@@ -416,11 +421,11 @@ void CbeApplication::windowExternalDragEnter(CbeWindowHandle wndHndl, Short2 dra
auto itr = windowWidgets.find(wndHndl);
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;
}
/* 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{
.dragStart = dragStart,
@@ -476,7 +481,7 @@ void CbeApplication::windowExternalDragLeave()
{
return;
}
LOG(LOG_CATEGORY, "External drag left");
CBE_LOG(LOG_CATEGORY, "External drag left");
wndIntxnStates.dragsInProgress.erase(itr);
debugAssertf(
@@ -688,7 +693,7 @@ void CbeApplication::tickWindowWidgets() noexcept
if (overDragResp.state != EInputHandleState::NotHandled && overDragResp.bDragNewPayload)
{
debugAssert(!bEndDrag);
LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!");
CBE_LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!");
itr->payload = overDragResp.payload;
}
}
@@ -735,7 +740,7 @@ void CbeApplication::tickWindowWidgets() noexcept
if (enterDragResp.state != EInputHandleState::NotHandled && enterDragResp.bDragNewPayload)
{
debugAssert(!bEndDrag);
LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!");
CBE_LOG_VERBOSE(LOG_CATEGORY, "Drag payload is updated!");
itr->payload = enterDragResp.payload;
}
}
@@ -800,7 +805,7 @@ void CbeApplication::tickWindowWidgets() noexcept
if (enterDragResp.bDragNewPayload)
{
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;
}
wndIntxnStates.dragsInProgress.emplace_back(DragInProgress{

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date October 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -15,6 +15,9 @@
namespace cbe
{
/* Yanked from ImGui source */
static const float DRAGDROP_HOLD_TO_OPEN_TIMER = 0.70f;
bool ImGuiHelpers::isAnyMouseClicked()
{
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 };
}
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(
const char *label, String *str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback /*= nullptr*/, void *userData /*= nullptr*/
)

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date October 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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);
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);
@@ -68,6 +71,13 @@ public:
debugAssert(!payloadsPair.first || payloadsPair.first == payloadRef);
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>
static void drawPackedRectangles(

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -205,7 +205,7 @@ void WgImGui::construct(const WgArguments &args)
}
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.OversampleV = 3;
fontConfig.RasterizerMultiply = 2;
@@ -403,7 +403,7 @@ void WgImGui::drawWidget(ShortRect clipBound, WidgetGeomId thisId, const WidgetG
const ImDrawCmd &drawCmd = uiCmdList->CmdBuffer[cmdIdx];
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);
continue;
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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)
{
LOG_WARN(
CBE_LOG_WARN(
"WindowsKeyboardDevice", "Possible multibyte key that is not handled properly : {}, Flags : {}", rawInput.data.keyboard.MakeCode,
rawInput.data.keyboard.Flags
);

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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]))))
{
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;
@@ -59,7 +59,7 @@ void WindowsInputSystem::registerWindow(const WindowInfo &wndInfo) const
keyboardDevice.hwndTarget = (HWND)wndInfo.hndl;
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;
@@ -69,7 +69,7 @@ void WindowsInputSystem::registerWindow(const WindowInfo &wndInfo) const
mouseDevice.hwndTarget = (HWND)wndInfo.hndl;
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));
if (currentBlocksNum == -1)
{
LOG_ERROR(LOG_CATEGORY, "Retrieving input buffer size failed");
CBE_LOG_ERROR(LOG_CATEGORY, "Retrieving input buffer size failed");
return;
}
/* Must be aligned by sizeof pointer.
@@ -114,7 +114,7 @@ void WindowsInputSystem::updateInputStates()
if (currentBlocksNum == -1)
{
LOG_ERROR(LOG_CATEGORY, "Reading buffered raw input failed");
CBE_LOG_ERROR(LOG_CATEGORY, "Reading buffered raw input failed");
return;
}
}
@@ -129,7 +129,7 @@ void WindowsInputSystem::updateInputStates()
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));
}
using QWORD = uint64;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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);
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;
}
@@ -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));
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;
}
@@ -109,17 +109,17 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->GetOptions(&dwOptions);
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;
}
dwOptions |= optionFlags;
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)
{
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)
@@ -154,9 +154,9 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
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);
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 */
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))))
{
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);
defaultPathItem->Release();
@@ -183,12 +183,12 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->Show(hwndOwner);
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;
}
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;
}
@@ -204,14 +204,14 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
hr = pfd->GetResult(&pResult);
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;
}
PWSTR pFilePath = nullptr;
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;
}
@@ -265,7 +265,7 @@ static void fileDialog(HWND hwndOwner, std::variant<cbe::OpenFileDialogInfo, cbe
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. */
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);
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;
}
dword count = 0;
pResults->GetCount(&count);
LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Selected {} items", count);
CBE_LOG_VERBOSE(WindowsWindowingManager::LOG_CATEGORY, "Selected {} items", count);
selected.reserve(count);
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. */
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);
if (!bSetDpiAwarness)
{
LOG_WARN(LOG_CATEGORY, "DPI awareness setup failed");
CBE_LOG_WARN(LOG_CATEGORY, "DPI awareness setup failed");
}
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 */
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);
printDisplayDevices();
}
@@ -457,7 +457,7 @@ void WindowsWindowingManager::printDisplayDevices()
for (int i = 0; (!!::EnumDisplayDevices(NULL, i, &dispDevice, 0)); i++)
{
// 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.DeviceID),
UTF8_TO_TCHAR(dispDevice.DeviceString),
@@ -524,7 +524,7 @@ void WindowsWindowingManager::dpiChanged(CbeWindowHandle hndl, uint16 newDpi, IR
WindowInfo &wndInfo = windows[hndl];
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;
::SetWindowPos(
@@ -609,7 +609,7 @@ void WindowsWindowingManager::refreshMonitors(bool bLogMonitors)
}
}
LOG_C(
CBE_LOG_C(
bLogMonitors, LOG_CATEGORY,
"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 :{}",
@@ -666,7 +666,7 @@ CbeWindowHandle WindowsWindowingManager::platformCreateWindow(CbeWindowHandle th
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;
}
/* Since dpi awareness questions the monitor list's legitimacy */
@@ -679,7 +679,7 @@ CbeWindowHandle WindowsWindowingManager::platformCreateWindow(CbeWindowHandle th
if (windowCi.bEnableDragDrop)
{
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;
@@ -851,8 +851,10 @@ BOOL WindowsCallbackProcs::monitorEnum(HMONITOR monitor, HDC devCntx, LPRECT mon
::MONITORINFOEX info;
info.cbSize = sizeof(MONITORINFOEX);
LOG(WindowsWindowingManager::LOG_CATEGORY, "Monitor callback received! Monitor region [{}, {}] to [{}, {}]", monitorRegion->left,
monitorRegion->top, monitorRegion->right, monitorRegion->bottom);
CBE_LOG(
WindowsWindowingManager::LOG_CATEGORY, "Monitor callback received! Monitor region [{}, {}] to [{}, {}]", monitorRegion->left,
monitorRegion->top, monitorRegion->right, monitorRegion->bottom
);
if (::GetMonitorInfo(monitor, &info))
{
MonitorInfo &mInfo = monitors.emplace_back();
@@ -916,24 +918,24 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
const uint64 userData = cbeHndl;
::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;
}
case WM_DESTROY:
{
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
{
LOG(WindowingManager::LOG_CATEGORY, "Destroying window");
CBE_LOG(WindowingManager::LOG_CATEGORY, "Destroying window");
}
return 0;
}
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);
return 0;
}
@@ -943,11 +945,11 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
{
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
{
LOG(WindowingManager::LOG_CATEGORY, "Disabled window {}", wndManager->getWindowInfo(cbeHndl).title);
CBE_LOG(WindowingManager::LOG_CATEGORY, "Disabled window {}", wndManager->getWindowInfo(cbeHndl).title);
}
return 0;
}
@@ -959,12 +961,12 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
{
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);
}
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);
}
return 0;
@@ -1002,7 +1004,9 @@ LRESULT WindowsCallbackProcs::windowMsgs(HWND hwnd, UINT uMsg, WPARAM wParam, LP
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)
{
wndInfo.deferredMsgs[cbe_app_wnd_msgs::Resize].emplace<cbe_app_wnd_msgs::WndResize>(oldSize, newSize);

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date November 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -19,11 +19,13 @@ void CbeAudioModule::init()
const std::vector devs = getDevices();
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)
{
LOG(LOG_CATEGORY, " Sample Rate: {}, Data format {}, Channels: {}, Exclusive access: {}", devProp.sampleRate,
static_cast<uint32>(devProp.fmt), devProp.numChannels, devProp.bHasExclusiveAccess);
CBE_LOG(
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
* \date November 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -449,7 +449,7 @@ void MiniAudioBackendContext::copyAudioInst(
if (result != MA_SUCCESS)
{
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;
}
}
@@ -458,7 +458,7 @@ void MiniAudioBackendContext::copyAudioInst(
ma_result result = ma_audio_buffer_ref_init(format, numOfChannels, framesData, framesCount, &dstDataSrc.audioRef);
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;
}
dstDataSrc.audioRef.sampleRate = sampleRate;
@@ -468,7 +468,7 @@ void MiniAudioBackendContext::copyAudioInst(
ma_result result = ma_sound_init_ex(&dstEng.engine, &soundCfg, &dstDataSrc.sound);
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);
if (dstDataSrc.bStreaming)
{
@@ -534,7 +534,7 @@ bool MiniAudioBackendContext::setSampleRateImpl(uint32 newSampleRate)
{
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;
}
}
@@ -709,7 +709,7 @@ cbe::audio::DecodedOutput MiniAudioBackendContext::decodeAudioInfoImpl(cbe::audi
}
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;
}
@@ -739,7 +739,7 @@ cbe::audio::DecodedOutput MiniAudioBackendContext::decodeAudioImpl(cbe::audio::D
}
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;
}
@@ -753,7 +753,7 @@ cbe::audio::EncodedOutput MiniAudioBackendContext::encodeAudioImpl(cbe::audio::E
encodeCfg.allocationCallbacks = localAllocCallbacks;
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 {};
}
@@ -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);
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 {};
}
@@ -811,13 +811,13 @@ cbe::audio::EncodedOutput MiniAudioBackendContext::encodeAudioImpl(cbe::audio::E
ma_encoder_uninit(&encoder);
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 {};
}
if (framesWritten != inFramesCount)
{
LOG_ERROR(
CBE_LOG_ERROR(
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);
if (devices[devIdx].audioDevData->userEngIdx < CBE_AUDIO_MAX_LOCAL_USER)
{
LOG(ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!",
devicesInfo[devices[devIdx].devIdx].devInfo.name);
CBE_LOG(
ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!",
devicesInfo[devices[devIdx].devIdx].devInfo.name
);
return retId;
}
@@ -875,7 +877,7 @@ cbe::audio::EngineId MiniAudioBackendContext::createUserEngineImpl(cbe::audio::L
{
if (result != MA_SUCCESS)
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeAudioModule::LOG_CATEGORY, "Failed to create engine using device {} for local user index {}",
devicesInfo[devIdx].devInfo.name, ci.localUserIdx
);
@@ -961,8 +963,10 @@ bool MiniAudioBackendContext::switchUserEngineDeviceImpl(cbe::audio::EngineId en
auto [_, devIdx] = deviceIdToIndex(deviceId);
if (devices[devIdx].audioDevData->userEngIdx < CBE_AUDIO_MAX_LOCAL_USER)
{
LOG(ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!",
devicesInfo[devices[devIdx].devIdx].devInfo.name);
CBE_LOG(
ICbeAudioModule::LOG_CATEGORY, "Cannot create user engine with already linked audio device {}!",
devicesInfo[devices[devIdx].devIdx].devInfo.name
);
return false;
}
@@ -1386,7 +1390,7 @@ bool MiniAudioBackendContext::createAudioPlayerFromDataSrc(
);
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;
}
playerRef.bStreaming = false;
@@ -1433,7 +1437,7 @@ bool MiniAudioBackendContext::createAudioPlayerFromDataSrc(
if (result != MA_SUCCESS)
{
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;
}
}
@@ -1489,7 +1493,7 @@ bool MiniAudioBackendContext::createAudioPlayerFromDataSrc(
ma_result result = ma_sound_init_ex(&eng.engine, &soundCfg, &playerRef.sound);
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);
if (playerRef.bStreaming)
{
@@ -1547,20 +1551,20 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
dataStream.pageBackingFile = PlatformMemory::openPhysicalMemorySection(bufferingBlockSizeInBytes);
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;
}
/* Need virtual address space mappable to each audio page chunk */
dataStream.pcmFramesAddrs = PlatformMemory::virtualAllocUnmapped(bufferingBlockSizeInBytes, totalMappableBlocks);
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;
}
dataStream.pagesAddr = PlatformMemory::virtualAllocUnmapped(bufferingBlockSizeInBytes, 1);
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;
}
/* Now map the virtual addresses */
@@ -1572,7 +1576,7 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
true
))
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeAudioModule::LOG_CATEGORY, "Failed to map section to PCM read frames at {}(offset {})!", mappedFrameAddrNum,
bufferingBlockSizeInBytes * mappedFrameAddrNum
);
@@ -1585,7 +1589,7 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
}
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;
}
@@ -1595,7 +1599,7 @@ bool MiniAudioBackendContext::initAudioPlayerDataStream(
ma_result result = ma_decoder_init_vfs(&dataStream.maVfs, dataStream.filePath, &decodeCfg, &dataStream.decoder);
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;
}
@@ -1706,7 +1710,7 @@ bool MiniAudioBackendContext::createAudioPlayerInstancesImpl(
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!"
);
return false;
@@ -1799,7 +1803,7 @@ bool MiniAudioBackendContext::destroyAudioPlayerInstancesImpl(ArrayView<cbe::aud
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;
}
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);
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;
}
@@ -3194,7 +3198,7 @@ void MiniAudioBackendContext::destroyLocalDevice(const LocalDevice &dev) const
return;
}
LOG_WARN_C(
CBE_LOG_WARN_C(
isValidUserEngine(dev.audioDevData->userEngIdx), ICbeAudioModule::LOG_CATEGORY,
"All user engines({}) using device {} must be cleared before destroying device!", dev.audioDevData->userEngIdx,
devicesInfo[dev.devIdx].devInfo.name
@@ -3336,17 +3340,17 @@ void MiniAudioBackendContext::miniaudioLog(void *pUserData, ma_uint32 level, con
switch (level)
{
case MA_LOG_LEVEL_DEBUG:
LOG_DEBUG(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
CBE_LOG_DEBUG(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break;
case MA_LOG_LEVEL_INFO:
LOG(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
CBE_LOG(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break;
case MA_LOG_LEVEL_WARNING:
LOG_WARN(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
CBE_LOG_WARN(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break;
case MA_LOG_LEVEL_ERROR:
default:
LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
CBE_LOG_ERROR(ICbeAudioModule::LOG_CATEGORY, "{}", msg);
break;
}
}

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date April 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 Barrier!");
CBESpinLock jobsAllocatorLock;
cbe::SpinLock jobsAllocatorLock;
JobPacketPoolAllocator jobsAllocator;
RingBufferLockFree<AllocHnd> freeJobAllocs;
BarrierPoolAllocator barrierAllocator;
CBESpinLock barrierAllocatorLock;
cbe::SpinLock barrierAllocatorLock;
private:
static JoltCopatTask queueOneJob(copat::JobSystem *jobSystem, JobPacket *job);

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date April 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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);
LOG(ICbePhysicsModule::LOG_CATEGORY, "{}", UTF8_TO_TCHAR(fmted));
CBE_LOG(ICbePhysicsModule::LOG_CATEGORY, "{}", UTF8_TO_TCHAR(fmted));
}
#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();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating box shape with error: {}",
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();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating capsule shape with error: {}",
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());
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;
}
@@ -441,7 +441,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating convex hull shape with error: {}",
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();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating cylinder shape with error: {}",
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());
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;
}
@@ -557,7 +557,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::S
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating sphere shape with error: {}",
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();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered capsule shape with error: {}",
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());
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;
}
@@ -675,7 +675,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating tapered cylinder shape with error: {}",
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());
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;
}
@@ -740,7 +740,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating triangle shape with error: {}",
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);
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;
}
@@ -802,7 +802,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::C
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating static compound shape with error: {}",
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);
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;
}
@@ -864,7 +864,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::M
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating mutable compound shape with error: {}",
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);
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;
}
@@ -922,7 +922,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::T
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating transformed shape with error: {}",
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);
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;
}
@@ -980,7 +980,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::O
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_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"))
);
@@ -1028,7 +1028,7 @@ bool JoltBackend::recreateShape(cbe::physics::Shape shape, const cbe::physics::E
const JPH::ShapeSettings::ShapeResult result = joltSettings.Create();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating empty shape with error: {}",
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();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating height field shape with error: {}",
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();
if (!rotationResult.IsValid())
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating height field orientation shape with error: {}",
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();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating mesh shape with error: {}",
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();
if (!result.IsValid()) [[unlikely]]
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbePhysicsModule::LOG_CATEGORY, "Failed creating empty shape with error: {}",
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();
}
LOG_ERROR(
CBE_LOG_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"))
);

View File

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

View File

@@ -4,7 +4,7 @@
* \author Subity
* \date May 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 (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;
}

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -131,7 +131,7 @@ void ICbeRendererModule::removeMaterialParamDesc(NameString matParamName)
* MAT_ACCESS_SAMPLERS_LIST - Macro provides access to descriptor holding samplers list
*/
/* clang-format off */
constexpr static const TChar *MATERIAL_STRUCT_ACCESSOR_TMPL =
constexpr static const TChar *MATERIAL_STRUCT_ACCESSOR_TMPL =
TCHAR("#include \"Base/BaseCommon.hlsli\"\n\n")
TCHAR("{{#" MAT_TMPL_ACCESSORS_SECTION "}}\n")
TCHAR("{{#" MAT_TMPL_ACCESSOR_IS_TEXTURE "}}")
@@ -143,7 +143,7 @@ TCHAR("{\n")
TCHAR(" const {{" MAT_TMPL_STRUCT_NAME "}} buffer = MAT_ACCESS_BUFFER_AS({{" MAT_TMPL_STRUCT_NAME "}}, baseInstanceIndex);\n")
TCHAR("{{#" MAT_TMPL_ACCESSOR_IS_TEXTURE "}}")
TCHAR(" uint16_t samplerIdx = (MAT_ACCESS_SAMPLER_{{" MAT_TMPL_ACCESSOR_TEXTURE_SAMPLER "}});\n")
TCHAR(" if (getAnisoSetting() > 0 && samplerIdx <= MAX_SAMPLER_WITH_ANISO_SUPPORT)\n")
TCHAR(" {\n")
@@ -197,10 +197,24 @@ String ICbeRendererModule::generateUniformStructCodes(const gal::GPUStructLayout
{
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);
auto typeInfo = gal::EShaderDataFormat::typeToInfo(field.metaData.dataType);
}
FormatArgsMap argsMap{
{ TCHAR(MAT_TMPL_ACCESSOR_IS_ARRAY), (field.elemCount > 1) },

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2024
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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))
{
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++)
{
@@ -110,14 +110,13 @@ CommandPools::allocateAllThreads(const TChar *baseName, gal::EQueue::Type queue,
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();
}
return retVals;
}
gal::GAL_RESOURCE_HND(CommandBuffer
) CommandPools::allocate(const TChar *cmdName, uint32 threadIdx, gal::EQueue::Type queue, bool bSecondary /*= false*/) const
gal::GAL_RESOURCE_HND(CommandBuffer) CommandPools::allocate(const TChar *cmdName, uint32 threadIdx, gal::EQueue::Type queue, bool bSecondary /*= false*/) const
{
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 }
))
{
LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed allocating command buffer {}", cmdName);
CBE_LOG_ERROR(ICbeRendererModule::LOG_CATEGORY, "Failed allocating command buffer {}", cmdName);
}
return retVal;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 } },
.bEnableValidation = cmdLine.hasArg(GAL_VALIDATION_ENABLE_NAME),
.bExtendedValidations = true,
.bDebugGpuVaBinding = true,
.bDebugGpuVaBinding = false, ///< Too verbose right now
.bOffscreenOnly = false,
.driverApi = initInfo.driverApi,
.enableFs = initInfo.enableFs,
@@ -115,13 +115,13 @@ void RendererContext::initialize(RenderContextInitInfo initInfo) 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 */
resourceDeleter.clear();
}
void RendererContext::release() noexcept
{
LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Releasing RendererContext!");
CBE_LOG_VERBOSE(ICbeRendererModule::LOG_CATEGORY, "Releasing RendererContext!");
copat::waitOnAwaitable(std::move(lastCompileTask));
@@ -326,7 +326,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
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;
}
@@ -358,7 +358,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
std::erase_if(generalDirs, erasePred);
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;
}
@@ -371,7 +371,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
const SCompShaderBlob basicSrcs = co_await basicSrcsAwaitable;
const SCompShaderBlob editorSrcs = co_await editorSrcsAwaitable;
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();
/* Now wait and load the new blobs */
@@ -381,13 +381,13 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
if (cState != ECompileState::Failure)
{
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;
bSerializedShaders = false;
}
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)
@@ -409,13 +409,13 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
{
basicShadersCompiled.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;
bSerializedShaders = false;
}
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)
{
@@ -438,13 +438,13 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
basicShadersCompiled.test_and_set(std::memory_order::relaxed);
editorShadersCompiled.test_and_set(std::memory_order::relaxed);
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;
bSerializedShaders = false;
}
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)
@@ -474,7 +474,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync(bool bLoadFr
bSerializedShaders = false;
}
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 (bAnyNewShaders)
@@ -528,7 +528,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync()
bool bFailure = !co_await compiler->compileShadersAsync(newCompiledResults, compileInfo);
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;
}
@@ -543,7 +543,7 @@ RendererContext::CompileTask RendererContext::recompileShadersAsync()
onShadersUpdated(updates);
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
@@ -574,7 +574,7 @@ void RendererContext::updateCompiledResultsStructLayout()
auto itr = vertName2Layout.find(std::get<NameString>(vertEntry.typeNameOrLayout));
if (itr == vertName2Layout.cend())
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Vertex layout {} missing for shader {}!",
std::get<NameString>(vertEntry.typeNameOrLayout), r.first
);
@@ -609,7 +609,7 @@ void RendererContext::updateCompiledResultsStructLayout()
auto itr = vertName2Layout.find(std::get<NameString>(vertEntry.typeNameOrLayout));
if (itr == vertName2Layout.cend())
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Vertex layout {} missing for material {}!",
std::get<NameString>(vertEntry.typeNameOrLayout), matVariant.second.name
);
@@ -698,7 +698,7 @@ bool RendererContext::storeCompiledResults(ShadersUpdate &outUpdates, const SCom
recompiledCount += newCompiledRes.materials.size();
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 */
@@ -1166,7 +1166,7 @@ void RendererContext::createBasicPipelines()
const bool bSuccess = galContext()->createGraphicsPipelines(wgGPipeline, gpCi);
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 = {};
}
@@ -1204,7 +1204,7 @@ void RendererContext::createBasicPipelines()
const bool bSuccess = galContext()->createGraphicsPipelines(defaultGBufferPipeline, gpCi);
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 = {};
}
}
@@ -1222,7 +1222,7 @@ void RendererContext::createBasicPipelines()
const bool bSuccess = galContext()->createComputePipelines(fillBufferWithPcPipeline, fillBufferCompCi);
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 = {};
}
}
@@ -1801,6 +1801,8 @@ CpuBuffersPool::Allocation CpuBuffersPool::getTempBuffer(uint32 minSize) noexcep
}
framesToFree[bestFitIdx] = static_cast<uint8>(rCtx->getSwapchainImgCount());
debugAssert(rCtx->galContext()->isValid(buffers[bestFitIdx]));
return Allocation{ .hndl = buffers[bestFitIdx], .idx = bestFitIdx, .offset = 0, .size = bestFitSize };
}
@@ -1822,10 +1824,10 @@ void GpGpuBindlessDataBuffers::setup(gal::PipelineHndlVariants newPipeline, uint
#endif
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;
}
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);
pipeline = newPipeline;
@@ -2069,10 +2071,10 @@ void GpGpuBindlessTextures::setup(
#endif
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;
}
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());
@@ -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)
{
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;
retVals.resize(imgViews.size());
@@ -2382,7 +2388,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
bool bNoneReachedEnd = true;
while (bNoneReachedEnd)
{
uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]);
const uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]);
bool bAllMatched = true;
/* 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();
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);
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())
{
/* Resize and allocate from the end table index */
resizeDescs(allocator.size() + imgViews.size());
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 */
const SizeT descEntryStartIndex = tableToEntryIdx(tableIdx);
const SizeT descEntryEndIndex = Math::min(descEntryStartIndex + descsCountPerTable, allocator.size());
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;
const bool bAllocated = allocator.findFirstUnsetInRange(descIdx, descEntryStartIndex, descEntryEndIndex - descEntryStartIndex);
debugAssert(bAllocated);
@@ -2462,8 +2524,6 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
.bOwningView = false,
};
const ResKey resKey{ gal::EResourceType::ImageView, GAL_RES_TO_UNTYPE_HNDL(imgViews[i]) };
auto resToEntryItr = imgResToEntryIdx.find(resKey);
if (resToEntryItr == imgResToEntryIdx.end())
{
/* First entry */
@@ -2475,6 +2535,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
TextureData &thisEntry = descEntries[descIdx];
TextureData &prevEntry = descEntries[resToEntryItr->second];
thisEntry.bFirstEntry = false;
thisEntry.prevEntryIdx = resToEntryItr->second;
if (isValidTexAt(prevEntry.nextEntryIdx))
{
@@ -2488,7 +2549,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
retVals[i] = descIdx;
}
/* Write only if pipeline is available */
/* Write only if pipeline is available. */
if (rCtx.galContext()->isValid(pipeline))
{
std::vector<gal::UpdateImageDescriptors> writeDescs;
@@ -2497,6 +2558,12 @@ std::vector<uint64> GpGpuBindlessTextures::addImageViewsToTable(ArrayView<gal::G
writeDescInfo.resize(imgViews.size());
for (SizeT i = 0; i < imgViews.size(); ++i)
{
/* If view already exists no need to update descriptor again */
if (viewsExists[i])
{
continue;
}
writeDescInfo[i] = {
.hndl = descEntries[retVals[i]].viewHndl,
.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)
{
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;
retVals.resize(imgs.size());
@@ -2556,7 +2627,7 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
bool bNoneReachedEnd = true;
while (bNoneReachedEnd)
{
uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]);
const uint32 tableIdx = entryIdxToTable(perImgFoundEntries[0][perImgFoundEntryIdx[0]]);
bool bAllMatched = true;
/* 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();
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);
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())
{
/* Resize and allocate from the end table index */
resizeDescs(allocator.size() + imgs.size());
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 */
const SizeT descEntryStartIndex = tableToEntryIdx(tableIdx);
const SizeT descEntryEndIndex = Math::min(descEntryStartIndex + descsCountPerTable, allocator.size());
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;
const bool bAllocated = allocator.findFirstUnsetInRange(descIdx, descEntryStartIndex, descEntryEndIndex - descEntryStartIndex);
debugAssert(bAllocated);
@@ -2651,8 +2779,6 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
.bOwningView = true,
};
const ResKey resKey{ gal::EResourceType::Image, GAL_RES_TO_UNTYPE_HNDL(imgs[i]) };
auto resToEntryItr = imgResToEntryIdx.find(resKey);
if (resToEntryItr == imgResToEntryIdx.end())
{
/* First entry */
@@ -2686,6 +2812,12 @@ std::vector<uint64> GpGpuBindlessTextures::addImagesToTable(ArrayView<gal::GAL_R
writeDescInfo.resize(imgs.size());
for (SizeT i = 0; i < imgs.size(); ++i)
{
/* If image already exists no need to update descriptor again */
if (viewsExists[i])
{
continue;
}
writeDescInfo[i] = {
.hndl = descEntries[retVals[i]].viewHndl,
.layout = gal::EImageLayout::ShaderReadOnly,

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date September 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2024
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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)
REGISTER_MATERIAL_PARAM_DESC(CbeMatStruct1);
REGISTER_GPU_BUFFER_LAYOUT(CbeMatSInnerStruct);
#endif

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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 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 uint32 MAX_BATCH_COUNT = 8;

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date July 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -68,12 +68,12 @@ void WorldDataTransferManager::resetRenderData()
tempAllocator.reset();
}
void WorldDataTransferManager::addLargeTransfer(LargeTransfer transfer)
void WorldDataTransferManager::addLargeTransfer(LargeTransfer transfer, MAYBE_UNUSED std::source_location srcLoc)
{
CBE_ASSERT_INSIDE_RENDERTHREAD();
debugAssert(transfer.continuation);
#if DEBUG_BUILD
#if DEBUG_VALIDATIONS_ENABLED
for (const GpuBufferTransfer &gpuTransfer : transfer.bufferGpu2Gpu)
{
debugAssert(rCtx.galContext()->isValid(gpuTransfer.srcHndl));
@@ -94,6 +94,9 @@ void WorldDataTransferManager::addLargeTransfer(LargeTransfer transfer)
LargeTransfer *thisTransfer = getLargeTransferAllocator().allocateAligned<LargeTransfer>();
new (thisTransfer) LargeTransfer(transfer);
thisTransfer->next = nullptr;
#if DEBUG_BUILD
thisTransfer->from = srcLoc.function_name();
#endif
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();
#if DEBUG_BUILD
#if DEBUG_VALIDATIONS_ENABLED
SizeT calcTotalTransferSize = 0;
for (const CpuBufferTransfer &cpuTransfer : transfer.bufferCpu2Gpu)
{
@@ -130,6 +133,9 @@ void WorldDataTransferManager::addSmallTransfer(SmallTransfer transfer)
SmallTransfer *thisTransfer = getSmallTransferAllocator().allocateAligned<SmallTransfer>();
new (thisTransfer) SmallTransfer(transfer);
thisTransfer->next = nullptr;
#if DEBUG_BUILD
thisTransfer->from = srcLoc.function_name();
#endif
if (smallTransfersHead == nullptr)
{
@@ -747,6 +753,14 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
SizeT bytesOffset = bytesTransferred;
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"));
SizeT tIdx = 0;
@@ -829,6 +843,14 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
uint32 layerOffset = static_cast<uint32>(bytesTransferred);
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"));
SizeT tIdx = 0;
@@ -980,7 +1002,7 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
rIdx += regionIndexStart;
regionIndexStart = 0;
}
debugAssert(lImageGpuTransfersPending->imageGpu2Gpu.empty() || (tIdx > 0 && lastLayerOffset > 0));
debugAssert((tIdx > 0 && lastLayerOffset > 0));
imageGpuCopiesIdx += tIdx;
/* Add to transfer index which is used in case terminated in middle */
tIdx += transferStart;
@@ -1024,6 +1046,14 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
SizeT bytesOffset = bytesTransferred;
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"));
SizeT tIdx = 0;
@@ -1127,6 +1157,19 @@ void WorldDataTransferManager::consumeGenerateLargeTransfers(
uint32 layerOffset = static_cast<uint32>(bytesTransferred);
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"));
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,
STATIC_MESHES_RESIZE_FACTOR
);
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max static meshes count {} to {}", allSms.allMeshInsts.entries.totalCount(),
newSmsCount
);
@@ -1975,7 +2018,7 @@ WorldRenderer::DeferredAddTask<std::vector<renderer::MeshId>> WorldRenderer::add
),
iAlignment
);
LOG_VERBOSE(
CBE_LOG_VERBOSE(
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
);
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh vertex count {} to {}", allSms.verts.allocator.capacity(),
newCount
);
@@ -2285,7 +2328,7 @@ WorldRenderer::DeferredAddTask<std::vector<renderer::MeshId>> WorldRenderer::add
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;
}
@@ -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)
{
sceneGpData.free(
std::get<"batchBufferIndex">(smTable), std::get<"batchCount">(smTable),
std::get<"batchBufferIndex">(smTable), std::get<"batchByteOffset">(smTable),
static_cast<uint32>(sizeof(GalStaticMeshBatchEntry) * batchCount)
);
}
@@ -2365,7 +2408,7 @@ WorldRenderer::addStaticMeshComps(ArrayView<renderer::StaticMeshRenderableDesc>
),
constants::GPU_MIN_BITS_PER_ELEMENT
);
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max static mesh components count {} to {}", allSmComps.entries.totalCount(),
newSmCompsCount
);
@@ -2529,7 +2572,7 @@ WorldRenderer::addStaticMeshComps(ArrayView<renderer::StaticMeshRenderableDesc>
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;
}
@@ -2648,7 +2691,7 @@ WorldRenderer::addTextures2dRaw(ArrayView<renderer::TextureDescRaw2d> texDescs)
const SizeT newCount = RendererContext::resizeBuffer(
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);
}
@@ -2819,7 +2862,7 @@ std::vector<renderer::MaterialId> WorldRenderer::addMaterials(ArrayView<NameStri
/* Resize allMats */
const SizeT newCount
= 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);
}
@@ -2831,7 +2874,7 @@ std::vector<renderer::MaterialId> WorldRenderer::addMaterials(ArrayView<NameStri
})));
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;
}
@@ -2860,7 +2903,7 @@ void WorldRenderer::removeMaterials(ArrayView<renderer::MaterialId> mats)
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,
"Material {} cleared before all mesh(Type {}) derefs", allMats[matId].materialName, meshT
);
@@ -3084,7 +3127,7 @@ WorldRenderer::addMaterialInstances(ArrayRange<renderer::MaterialDataDesc> matIn
newMatInstsCount = RendererContext::resizeBuffer(
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(
thisMatInstsBuffer,
@@ -3270,7 +3313,7 @@ WorldRenderer::addMaterialInstances(ArrayRange<renderer::MaterialDataDesc> matIn
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;
}
@@ -3397,7 +3440,7 @@ void WorldRenderer::addMaterialInstRefs(renderer::EMeshType meshType, ArrayView<
}
const uint64 oldTotalRefsCount = meshRefsTotalCount[meshType];
meshRefsTotalCount[meshType] += newTotalRefsAdded;
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Added {} material instances reference. Total resized refs count {}(+{})", matInsts.size(),
meshRefsTotalCount[meshType], newTotalRefsAdded
);
@@ -3513,7 +3556,7 @@ void WorldRenderer::removeMaterialInstances(ArrayView<renderer::MaterialDataId>
* Then mark the material for offset recalculation. */
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,
"Material Instance {} cleared before all mesh(Type {}) derefs", matInstId, meshType
);
@@ -4343,7 +4386,7 @@ bool WorldRenderer::isRendererReady() const
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)
(
[updates, this]
@@ -4412,11 +4455,13 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
{
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
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Compute pipeline({}) create failed!",
shader_consts::frustum_cull::IM_DD_SHADER_NAME
);
@@ -4465,14 +4510,14 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
{
rCtx.resourceDeleter.addPipelineToDelete(oldHndl, EDeferredDelStrategy::NextSwapchainSync);
}
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {}",
shader_consts::frustum_cull::IM_DD_GEN_DRAWLIST_SHADER_NAME
);
}
else
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Compute pipeline({}) create failed!",
shader_consts::frustum_cull::IM_DD_GEN_DRAWLIST_SHADER_NAME
);
@@ -4568,7 +4613,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
}
if (bPipelineCreated)
{
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {} for Triangle Draws",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4576,7 +4621,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
}
else
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) for Triangle Draws create failed!",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4616,7 +4661,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
}
if (bPipelineCreated)
{
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {} for Line Draws",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4624,7 +4669,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
}
else
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) for Line Draws create failed!",
shader_consts::direct_draw::DRAW_PRIMITIVES_SHADER_NAME,
shader_consts::direct_draw::DRAW_PRIMITIVES_SEL_PROXY_SHADER_NAME
@@ -4712,7 +4757,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
}
if (bPipelineCreated)
{
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Recreated shader {} and {}",
shader_consts::direct_draw::DRAW_INSTANCES_SHADER_NAME,
shader_consts::direct_draw::DRAW_INSTANCES_SEL_PROXY_SHADER_NAME
@@ -4720,7 +4765,7 @@ void WorldRenderer::onDdShadersCompiled(const NamedShaderUpdate & /*ddShaderUpda
}
else
{
LOG_ERROR(
CBE_LOG_ERROR(
ICbeRendererModule::LOG_CATEGORY, "Graphics pipeline({}/{}) create failed!",
shader_consts::direct_draw::DRAW_INSTANCES_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);
}
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 */
if (!rCtx.galContext()->isValid(p.hndl))
@@ -5471,7 +5516,7 @@ void WorldRenderer::recreateLitModelResolve(WorldRenderer *self, ShaderPipeline
{
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 */
@@ -5551,7 +5596,7 @@ void WorldRenderer::recreateDepthResolve(WorldRenderer *self, ShaderPipeline *)
{
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 */
@@ -7619,7 +7664,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
allImDdMeshes.allMeshInsts.entries.totalCount(), allImDdMeshes.allMeshInsts.entries.size() + addedMeshCount, IM_MESHES_BASE_COUNT,
IM_MESHES_RESIZE_FACTOR
);
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate draw meshes count {} to {}",
allImDdMeshes.allMeshInsts.entries.totalCount(), newImDdMeshCount
);
@@ -7680,7 +7725,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
),
iAlignment
);
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate debug draw mesh indices count {} to {}",
allImDdMeshes.idxsAllocator.capacity(), newCount
);
@@ -7703,7 +7748,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
),
vAlignment
);
LOG_VERBOSE(
CBE_LOG_VERBOSE(
ICbeRendererModule::LOG_CATEGORY, "Resizing max immediate debug draw mesh vertex count {} to {}",
allImDdMeshes.verts.allocator.capacity(), newCount
);
@@ -8088,7 +8133,7 @@ copat::JobSystemFuncAwaiter WorldRenderer::issueImDrawTransfers(SizeT fDatIdx) n
/* Complete the add in draw context */
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)

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date July 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -126,6 +126,11 @@ public:
std::coroutine_handle<void> continuation;
/* To help with debugging */
#if DEBUG_BUILD
const char *from;
#endif
LargeTransfer *next = nullptr;
};
struct SmallTransfer
@@ -133,6 +138,11 @@ public:
ArrayView<CpuBufferTransfer> bufferCpu2Gpu;
SizeT totalCpu2GpuSize = 0;
/* To help with debugging */
#if DEBUG_BUILD
const char *from;
#endif
SmallTransfer *next = nullptr;
};
@@ -182,8 +192,8 @@ public:
return frameData[fDatIdx].smallTransferComplete;
}
void addLargeTransfer(LargeTransfer transfer);
void addSmallTransfer(SmallTransfer transfer);
void addLargeTransfer(LargeTransfer transfer, std::source_location srcLoc = std::source_location::current());
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 */
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
{
public:
@@ -525,11 +537,11 @@ private:
* Gets resized on components list resize and material instance references gets added.
* Offsets gets calculated from material instance references count.
* 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.
* Gets resized on material instances getting added before.
* 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)
{
@@ -581,8 +593,8 @@ private:
#pragma region Textures
struct WorldTexture
{
gal::GAL_RESOURCE_HND(Image) img;
gal::GAL_RESOURCE_HND(ImageView) imgView;
gal::GAL_RESOURCE_HND(Image) img = {};
gal::GAL_RESOURCE_HND(ImageView) imgView = {};
bool bOwnImg:1 = false;
bool bOwnImgView:1 = false;
@@ -668,7 +680,7 @@ private:
* 2. Triangles */
struct ImDdPrimitive
{
gal::GAL_RESOURCE_HND(Buffer) buffer;
gal::GAL_RESOURCE_HND(Buffer) buffer = {};
/* Total count across layers */
uint64 linesCount = 0;
uint64 trisCount = 0;
@@ -700,7 +712,7 @@ private:
/* 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.
* 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 */
uint64 maxInstsCount = 0;
/* 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 only be read by Cull and Draw list fill shaders.
* 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.
* [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.
* 1 for each usable ImDdMesh in allImDdMeshes. Gets resized when allImDdMeshes resizes.
* **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.
* 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.
* **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 */
using DrawsPerDrawMode = std::array<uint32, WorldImDrawContext::DrawModeMaxCount>;
std::array<DrawsPerDrawMode, WorldImDrawContext::DrawLayerMaxCount> drawsCountPerLayer;

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -176,7 +176,9 @@ void GalWgWindowRenderer::renderDrawCmds(
}
);
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)
{

View File

@@ -4,7 +4,7 @@
* \author Jeslas Pravin
* \date June 2024
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* License can be read in LICENSE file at this repository's root
*/
@@ -138,7 +138,7 @@ private:
uint32 descTblIdx;
};
/* 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 */
SparseVector<TextureResourceData, BitArraySparsityPolicy> texEntries;

View File

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

View File

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

View File

@@ -4,7 +4,7 @@
* \author Jeslas
* \date July 2025
* \copyright
* Copyright (C) Jeslas Pravin, 2022-2025
* Copyright (C) Jeslas Pravin, 2022-2026
* @jeslaspravin pravinjeslas@gmail.com
* 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))
, contentDirUnmountEventHnd(CoreObjectDelegates::onContentDirectoryRemoved.bindObject(this, &AssetManager::onContentDirUnmount))
#endif
, assetManLock(std::make_unique<std::shared_mutex>())
{}
AssetManager::~AssetManager()
{
@@ -59,10 +60,14 @@ AssetManager::~AssetManager()
}
}
classToPackages.clear();
assetManLock.reset();
}
AssetManager::TreeNodeIdx AssetManager::findAssetUnder(StringView assetName, TreeNodeIdx folderTreeIdx, bool bRecurse) const
{
const std::shared_lock readLock{ *assetManLock.get() };
debugAssert(folderTree.isValid(folderTreeIdx));
std::vector<TreeNodeIdx> folderTreeIdxs;
folderTree.getChildren(folderTreeIdxs, folderTreeIdx, bRecurse);
@@ -89,6 +94,7 @@ void AssetManager::getClassPackages(std::vector<PackageAllocator::AllocHandle> &
children.emplace_back(baseClass);
IReflectionRuntimeModule::get()->getChildsOf(baseClass, children, bRecurse);
const std::shared_lock readLock{ *assetManLock.get() };
for (CBEClass subclass : children)
{
auto itr = classToPackages.find(subclass);
@@ -107,8 +113,10 @@ void AssetManager::getClassPackages(std::vector<PackageAllocator::AllocHandle> &
}
#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);
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);
}
}
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
@@ -135,7 +158,7 @@ AssetManager::TreeNodeIdx AssetManager::createFolder(TreeNodeIdx folderIdx, Stri
continue;
}
const auto &treeNodeData = getFolderInfo(subFolderTreeIdx)->nodeData;
const auto &treeNodeData = getFolderInfoNoLock(subFolderTreeIdx)->nodeData;
if (TCharStr::isEqual(std::get<FolderTree_Folder>(treeNodeData).folderName, folderName))
{
return subFolderTreeIdx;
@@ -149,13 +172,13 @@ AssetManager::TreeNodeIdx AssetManager::createFolder(TreeNodeIdx folderIdx, Stri
pathElems.resize(pathElemIdxs.size() + 2);
debugAssert(
!getFolderInfo(pathElemIdxs.front())->contentDir.empty()
&& FileSystemFunctions::dirExists(getFolderInfo(pathElemIdxs.front())->contentDir.getChar())
!getFolderInfoNoLock(pathElemIdxs.front())->contentDir.empty()
&& 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)
{
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;
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);
}
const std::unique_lock writeLock{ *assetManLock.get() };
FolderInfo folderInf{
.folderName = folderName,
.folderId = StringID(folderName),
@@ -176,10 +200,18 @@ AssetManager::TreeNodeIdx AssetManager::createFolder(TreeNodeIdx folderIdx, Stri
void AssetManager::onPackageDeleted(StringView packagePath)
{
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);
const AssetPackage *assetPack = packages.getAllocAt(packageHnd);
std::tie(folderTreeIdx, packageHnd) = packagePathToPackage(packagePath);
assetPack = packages.getAllocAt(packageHnd);
}
/* Start writing */
const std::unique_lock writeLock{ *assetManLock.get() };
#if CBE_ENABLE_PACKAGE_REFERENCE_TREE
{
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("ReferenceTree"));
@@ -217,7 +249,7 @@ void AssetManager::onPackageDeleted(StringView packagePath)
while (folderTree.isValid(parentFolderTreeIdx) && !folderTree.hasChild(parentFolderTreeIdx))
{
/* Keep the root always, unless it gets unmounted */
if (isRootFolder(parentFolderTreeIdx))
if (isRootFolderNoLock(parentFolderTreeIdx))
{
break;
}
@@ -230,34 +262,45 @@ void AssetManager::onPackageDeleted(StringView packagePath)
/* Finally destroy the asset package */
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
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager"));
Package *package = cast<Package>(pkgObj);
debugAssert(package != nullptr);
auto [folderTreeIdx, packageHnd] = packagePathToPackage(package->getObjectData().path);
/* If no package found probably new package so ignore. */
if (!folderTree.isValid(folderTreeIdx))
{
return;
}
AssetManager::TreeNodeIdx folderTreeIdx;
AssetManager::PackageAllocator::AllocHandle packageHnd;
AssetPackage *assetPack;
/* After inserting the new dependencies, this will only contain dependencies that needs to be removed. */
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 });
oldDep = LinkedListHelpers::next(oldDep);
const std::shared_lock readLock{ *assetManLock.get() };
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 });
debugAssert(loaderInf != nullptr && loaderInf->loader != nullptr);
/* Start writing */
const std::unique_lock writeLock{ *assetManLock.get() };
std::unordered_set<NameString> uniquePkgPath;
for (const PackageDependencyData &dependency : loaderInf->loader->getDependencyObjects())
{
@@ -313,16 +356,21 @@ void AssetManager::onContentDirMount(StringView contentDir)
}
/* Erase already inserted folder names */
for (const TreeNodeIdx rootIdx : folderTree.getAllRoots())
{
debugAssert(!isPackageNode(rootIdx));
debugAssert(!folderTree[rootIdx].contentDir.empty());
const std::shared_lock readLock{ *assetManLock.get() };
const FolderInfo &folderInfo = std::get<FolderTree_Folder>(folderTree[rootIdx].nodeData);
subfolderNames.erase(folderInfo.folderName);
for (const TreeNodeIdx rootIdx : folderTree.getAllRoots())
{
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 */
const std::unique_lock writeLock{ *assetManLock.get() };
for (const String &folderName : subfolderNames)
{
FolderInfo folderInf{ .folderName = folderName, .folderId = StringID(folderName) };
@@ -331,6 +379,8 @@ void AssetManager::onContentDirMount(StringView contentDir)
}
void AssetManager::onContentDirUnmount(StringView contentDir)
{
const std::unique_lock writeLock{ *assetManLock.get() };
for (const TreeNodeIdx rootIdx : folderTree.getAllRoots())
{
debugAssert(!isPackageNode(rootIdx));
@@ -354,6 +404,8 @@ void AssetManager::onContentDirUnmount(StringView contentDir)
void AssetManager::onPackageScanned(PackageLoader *packageLoader, StringView packagePath, StringView packageFilePath, StringView contentDir)
{
CBE_PROFILER_SCOPE(CBE_PROFILER_CHAR("AssetManager"));
const std::unique_lock writeLock{ *assetManLock.get() };
debugAssert(!packageLoader->getContainedObjects().empty());
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);
if (!bContentDirMatch)
{
LOG_ERROR(
CBE_LOG_ERROR(
CATEGORY, "Package's({}) content directory {} does not match the root folder({}) content directory {}", packagePath,
contentDir, rootFolder.folderName, rootInfo.contentDir
);
LOG(CATEGORY,
"If content directory relative path of any asset is colliding, prefix the asset relative path to ensure uniqueness!");
CBE_LOG(
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
{
const TreeNodeIdx parentNodeIdx = pathToFolderIdxInFolderTree(packagePath);
const TreeNodeIdx parentNodeIdx = pathToFolderIdxInFolderTreeNoLock(packagePath);
std::vector<TreeNodeIdx> leafNodeIdxs;
const bool bRecurse = false;
folderTree.getChildren(leafNodeIdxs, parentNodeIdx, bRecurse);
@@ -623,7 +676,7 @@ std::pair<AssetManager::TreeNodeIdx, AssetManager::PackageAllocator::AllocHandle
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"));

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