Skip to content

Commit

Permalink
Added documentation about Custom Data
Browse files Browse the repository at this point in the history
  • Loading branch information
jankrassnigg committed Jan 24, 2024
1 parent b1c2604 commit 9afcd6d
Show file tree
Hide file tree
Showing 21 changed files with 202 additions and 56 deletions.
7 changes: 7 additions & 0 deletions GenerateDocs.bat
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@echo off

echo Generating TOC...
python scripts\create-toc.py

echo Updating Snippets...
python scripts\code-snippets.py
3 changes: 3 additions & 0 deletions pages/docs/Miscellaneous/config-file-resource.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Since all resources can be hot reloaded at runtime, using `ezConfigFileResource`
>
> Currently, interacting with resources is only possible from C++ code.
An alternative to *config file resources* is [custom data](custom-data.md).

## File Structure

The layout of config files is similar to C/C++ code, including the support for the [C preprocessor](https://en.wikipedia.org/wiki/C_preprocessor).
Expand Down Expand Up @@ -81,3 +83,4 @@ int PlayerHealth = 100

* [Resource Management (TODO)](../runtime/resource-management.md)
* [C preprocessor (Wikipedia)](https://en.wikipedia.org/wiki/C_preprocessor)
* [Custom Data](custom-data.md)
145 changes: 143 additions & 2 deletions pages/docs/Miscellaneous/custom-data.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,148 @@
# Custom Data

<!-- PAGE IS TODO -->
By writing [custom components with C++](../custom-code/cpp/custom-cpp-component.md) you can define typed configuration properties that show up in the editor UI. These can also use type attributes to limit the valid input range or change how the value is displayed and editable. When you use such components inside [prefabs](../prefabs/prefabs-overview.md), this enables you to configure an object once, but have many instances of it at runtime.

However, there are two downsides to this approach:
1. Although you only configure a prefab once, each prefab instance still creates a copy of that data when it is spawned from the prefab template. Thus there is per-instance memory overhead for data that could be fully shared across all instances.
1. You may want to share the same setup across a large variety of prefab types. For example you may only have a few different creatures in your game, but lots of visual variations of them, and you want to edit their behavior properties in a single place and share it across many prefabs, rather than having to go through all of them and manually making sure that they all have the same configuration.

The *Custom Data* feature enables you to solve both of these issues.

An alternative to custom data is the [config file resource](config-file-resource.md), though it has no editor integration.

## Creating Custom Data Types

*Custom data* types are fully defined in C++ code.

To create a new custom data type, derive from the `ezCustomData` base class and add reflection capabilities. Then add all the properties that you need. Additionally we need to declare a dedicated [resource (TODO)](../runtime/resource-management.md) for your data type.

The code snippet below shows what should be added to a *header file (.h)*:

<!-- BEGIN-DOCS-CODE-SNIPPET: customdata-decl -->
```cpp
class SampleCustomData : public ezCustomData
{
EZ_ADD_DYNAMIC_REFLECTION(SampleCustomData, ezCustomData);

public:
ezString m_sText;
ezInt32 m_iSize = 42;
ezColor m_Color;
};

EZ_DECLARE_CUSTOM_DATA_RESOURCE(SampleCustomData);
```
<!-- END-DOCS-CODE-SNIPPET -->
Now in a corresponding *source file (.cpp)* add the [reflection block](../runtime/reflection-system.md) that enables the engine to know about the properties. Additionally we use a macro to finish the definition of our resource type.
<!-- BEGIN-DOCS-CODE-SNIPPET: customdata-impl -->
```cpp
// clang-format off
EZ_BEGIN_DYNAMIC_REFLECTED_TYPE(SampleCustomData, 1, ezRTTIDefaultAllocator<SampleCustomData>)
{
EZ_BEGIN_PROPERTIES
{
EZ_MEMBER_PROPERTY("Text", m_sText),
EZ_MEMBER_PROPERTY("Size", m_iSize)->AddAttributes(new ezDefaultValueAttribute(42), new ezClampValueAttribute(16, 64)),
EZ_MEMBER_PROPERTY("Color", m_Color)->AddAttributes(new ezDefaultValueAttribute(ezColor::CornflowerBlue)),
}
EZ_END_PROPERTIES;
}
EZ_END_DYNAMIC_REFLECTED_TYPE;
// clang-format on
EZ_DEFINE_CUSTOM_DATA_RESOURCE(SampleCustomData);
```
<!-- END-DOCS-CODE-SNIPPET -->

This is all that needs to be done to add a new custom data type and have it show up in the editor.

## Using Custom Data Types in the Editor

In the editor create a *Custom Data* [asset](../assets/assets-overview.md) from the [asset browser](../assets/asset-browser.md).

Inside the asset, your new custom data type should show up in the menu:

![Create Custom Data](media/custom-data-asset-create.png)

If it doesn't show up here, make sure your code is properly compiled, your plugin is [referenced](../projects/plugin-selection.md) and [loaded](../custom-code/cpp/cpp-code-reload.md) without errors by the editor.

When you add your custom data type, the editor displays all the properties that you configured on it:

![Edit Custom Data](media/custom-data-asset-edit.png)

If a property doesn't show up, at all, make sure it is added to the reflection block in the source file. If it should show a different default value, limit the value to a certain range or show a different UI, you can add *attributes* to the reflected properties. For details look at the [reflection system](../runtime/reflection-system.md) documentation and have a look at what other components do.

## Using Custom Data Types in C++ Code

To make use of your custom data type in a [C++ components](../custom-code/cpp/custom-cpp-component.md) you first need to make it possible to reference your *custom data asset* in that component, so that you can select on your component which asset it should use.

To do so, add the corresponding *resource handle* as a member to your component and set up the necessary functions to modify the handle:

<!-- BEGIN-DOCS-CODE-SNIPPET: customdata-interface -->
```cpp
SampleCustomDataResourceHandle m_hCustomData;

void SetSampleCustomDataResource(const char* szFile)
{
SampleCustomDataResourceHandle hCustomData;

if (!ezStringUtils::IsNullOrEmpty(szFile))
{
hCustomData = ezResourceManager::LoadResource<SampleCustomDataResource>(szFile);
}

m_hCustomData = hCustomData;
}

const char* GetSampleCustomDataResource() const
{
if (m_hCustomData.IsValid())
return m_hCustomData.GetResourceID();

return "";
}
```
<!-- END-DOCS-CODE-SNIPPET -->
Then, in the source file of your component, add the property to the component's reflection block:
<!-- BEGIN-DOCS-CODE-SNIPPET: customdata-property -->
```cpp
EZ_ACCESSOR_PROPERTY("CustomData", GetSampleCustomDataResource, SetSampleCustomDataResource)->AddAttributes(new ezAssetBrowserAttribute("CompatibleAsset_CustomData")),
```
<!-- END-DOCS-CODE-SNIPPET -->

Make sure to add the necessary asset browser attribute, otherwise it only shows up as an ordinary string property.

> **Note:**
>
> Using a reflected resource handle isn't strictly necessary, you can also get a custom data resource directly in code by using the [asset GUID](../assets/assets-overview.md#asset-guid) to [load the resource (TODO)](../runtime/resource-management.md) manually. It is just more convenient and enables you to select the desired resource in the editor.
Now when you look at the properties of your component in the editor, it should show a field that allows you to select *custom data assets*.

> **Important:**
>
> Be aware that currently there is no filtering for the specific custom data type, so all custom data assets show up, even if they don't match the desired custom data type. If you select an incompatible one, at runtime it will log an error and give you a default constructed object.
Finally, to actually access your custom data inside your game code, you have to get a *resource lock* using your *resource handle*:

<!-- BEGIN-DOCS-CODE-SNIPPET: customdata-access -->
```cpp
ezResourceLock<SampleCustomDataResource> pCustomDataResource(m_hCustomData, ezResourceAcquireMode::BlockTillLoaded);

const SampleCustomData* pCustomData = pCustomDataResource->GetData();

ezDebugRenderer::Draw3DText(GetWorld(), ezFmt(pCustomData->m_sText), GetOwner()->GetGlobalPosition(), pCustomData->m_Color, pCustomData->m_iSize);
```
<!-- END-DOCS-CODE-SNIPPET -->
As you can see in the snippet above, once you have a *resource lock* you can access your custom data type like a regular C++ class. Make sure not to hold direct pointers to the data struct, but always go through the resource lock and keep the resource handle around for the entire duration that you potentially want to access the resource, as it determines the resource's lifetime.
For more details about how to use resources, see the [resource management (TODO)](../runtime/resource-management.md) chapter.
## See Also
* [Config File Resource](config-file-resource.md)
* [Config File Resource](config-file-resource.md)
* [Sample Game Plugin](../../samples/sample-game-plugin.md)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 12 additions & 12 deletions pages/docs/custom-code/cpp/custom-cpp-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class DemoComponent : public ezComponent
// ezComponent
public:
virtual void SerializeComponent(ezWorldWriter& stream) const override;
virtual void DeserializeComponent(ezWorldReader& stream) override;
virtual void SerializeComponent(ezWorldWriter& inout_stream) const override;
virtual void DeserializeComponent(ezWorldReader& inout_stream) override;
protected:
virtual void OnSimulationStarted() override;
Expand All @@ -51,7 +51,7 @@ private:
void Update();
float m_fAmplitude = 1.0f; // [ property ]
ezAngle m_Speed = ezAngle::Degree(90); // [ property ]
ezAngle m_Speed = ezAngle::MakeFromDegree(90); // [ property ]
};
```
<!-- END-DOCS-CODE-SNIPPET -->
Expand All @@ -72,7 +72,7 @@ EZ_BEGIN_COMPONENT_TYPE(DemoComponent, 3 /* version */, ezComponentMode::Dynamic
EZ_BEGIN_PROPERTIES
{
EZ_MEMBER_PROPERTY("Amplitude", m_fAmplitude)->AddAttributes(new ezDefaultValueAttribute(1), new ezClampValueAttribute(0, 10)),
EZ_MEMBER_PROPERTY("Speed", m_Speed)->AddAttributes(new ezDefaultValueAttribute(ezAngle::Degree(90))),
EZ_MEMBER_PROPERTY("Speed", m_Speed)->AddAttributes(new ezDefaultValueAttribute(ezAngle::MakeFromDegree(90))),
}
EZ_END_PROPERTIES;

Expand Down Expand Up @@ -127,11 +127,11 @@ Finally, to make our component also work in exported scenes, we need to implemen

<!-- BEGIN-DOCS-CODE-SNIPPET: component-serialize -->
```cpp
void DemoComponent::SerializeComponent(ezWorldWriter& stream) const
void DemoComponent::SerializeComponent(ezWorldWriter& inout_stream) const
{
SUPER::SerializeComponent(stream);
SUPER::SerializeComponent(inout_stream);

auto& s = stream.GetStream();
auto& s = inout_stream.GetStream();

s << m_fAmplitude;
s << m_Speed;
Expand All @@ -145,12 +145,12 @@ Obviously, at runtime we also need to deserialize our component. This is where w
<!-- BEGIN-DOCS-CODE-SNIPPET: component-deserialize -->
```cpp
void DemoComponent::DeserializeComponent(ezWorldReader& stream)
void DemoComponent::DeserializeComponent(ezWorldReader& inout_stream)
{
SUPER::DeserializeComponent(stream);
const ezUInt32 uiVersion = stream.GetComponentTypeVersion(GetStaticRTTI());
SUPER::DeserializeComponent(inout_stream);
const ezUInt32 uiVersion = inout_stream.GetComponentTypeVersion(GetStaticRTTI());
auto& s = stream.GetStream();
auto& s = inout_stream.GetStream();
s >> m_fAmplitude;
Expand All @@ -160,7 +160,7 @@ void DemoComponent::DeserializeComponent(ezWorldReader& stream)
// convert this to ezAngle
float fDegree;
s >> fDegree;
m_Speed = ezAngle::Degree(fDegree);
m_Speed = ezAngle::MakeFromDegree(fDegree);
}
else
{
Expand Down
4 changes: 2 additions & 2 deletions pages/docs/custom-code/cpp/engine-plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ The file `SampleGamePluginDLL.h` only contains `#define`s for DLL import/export
// Configure the DLL Import/Export Define
#if EZ_ENABLED(EZ_COMPILE_ENGINE_AS_DLL)
# ifdef BUILDSYSTEM_BUILDING_SAMPLEGAMEPLUGIN_LIB
# define EZ_SAMPLEGAMEPLUGIN_DLL __declspec(dllexport)
# define EZ_SAMPLEGAMEPLUGIN_DLL EZ_DECL_EXPORT
# else
# define EZ_SAMPLEGAMEPLUGIN_DLL __declspec(dllimport)
# define EZ_SAMPLEGAMEPLUGIN_DLL EZ_DECL_IMPORT
# endif
#else
# define EZ_SAMPLEGAMEPLUGIN_DLL
Expand Down
2 changes: 1 addition & 1 deletion pages/docs/debugging/cvars.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Then you can just treat it like a regular variable to read or write its value:
```cpp
if (cvar_DebugDisplay)
{
ezDebugRenderer::DrawLineSphere(m_pMainWorld, ezBoundingSphere(ezVec3::ZeroVector(), 1.0f), ezColor::Orange);
ezDebugRenderer::DrawLineSphere(m_pMainWorld, ezBoundingSphere::MakeFromCenterAndRadius(ezVec3::MakeZero(), 1.0f), ezColor::Orange);
}
```
<!-- END-DOCS-CODE-SNIPPET -->
Expand Down
3 changes: 1 addition & 2 deletions pages/docs/debugging/debug-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,7 @@ The following code snippet is sufficient to render a wireframe sphere at the loc

<!-- BEGIN-DOCS-CODE-SNIPPET: debugrender-sphere -->
```cpp
ezBoundingSphere sphere;
sphere.SetElements(ezVec3::ZeroVector(), m_fSize);
ezBoundingSphere sphere = ezBoundingSphere::MakeFromCenterAndRadius(ezVec3::MakeZero(), m_fSize);
ezDebugRenderer::DrawLineSphere(GetWorld(), sphere, m_Color, ownerTransform);
```
<!-- END-DOCS-CODE-SNIPPET -->
Expand Down
2 changes: 1 addition & 1 deletion pages/docs/graphics/meshes/custom-mesh-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Each component only uses a single [material](../../materials/materials-overview.

Custom mesh components use *vertex colors*, meaning every vertex stores its own color information. This is different to typical mesh data in EZ, where per-vertex colors are usually not used. If you do not require vertex colors, this is fine and you can use any default [material](../../materials/materials-overview.md) (and thus shader). The extra information will simply be ignored.

However, if you do need the vertex color information, for instance to precisely control transparency, then you also need to set a material, which uses a [shader (TODO)](../shaders/shaders-overview.md) that actually reads the vertex color value and uses it in the way that you desire. You may need to write a custom shader for that.
However, if you do need the vertex color information, for instance to precisely control transparency, then you also need to set a material, which uses a [shader](../shaders/shaders-overview.md) that actually reads the vertex color value and uses it in the way that you desire. You may need to write a custom shader for that.

## See Also

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ In shader code, permutation variables are exposed as `#define`'d preprocessor va

## The Shader Permutations Section

Each shader is made up of several [sections](./shaders-overview.md#shader-sections):
Each shader is made up of several [sections](shaders-overview.md#shader-sections):

```cpp
[PLATFORMS]
Expand Down
2 changes: 1 addition & 1 deletion pages/docs/graphics/shaders/shader-render-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This design follows what rendering APIs such as DirectX 12 and Vulkan require.

## The Shader Render State Section

Each shader is made up of several [sections](./shaders-overview.md#shader-sections):
Each shader is made up of several [sections](shaders-overview.md#shader-sections):

```cpp
[PLATFORMS]
Expand Down
2 changes: 1 addition & 1 deletion pages/docs/graphics/shaders/shader-resources.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Shaders Resources

Shader resources are things like textures, samplers constant buffers etc. that need to be separately bound in the renderer for the shader to function. Each resource must be bound to a set and slot. Depending on the [platform](./shaders-overview.md#platforms) used, the requirements for this binding can be very different. E.g. in Vulkan slot assignments must be unique within a set across all stages while in DX11 most slots only need to be unique within a stage. Not following these rules will result in a runtime error. Manually assigning slots is an option but is very tedious. To make this easier, the shader system can automate this process provided some constraints are met how resourced are declared.
Shader resources are things like textures, samplers constant buffers etc. that need to be separately bound in the renderer for the shader to function. Each resource must be bound to a set and slot. Depending on the [platform](shaders-overview.md#platforms) used, the requirements for this binding can be very different. E.g. in Vulkan slot assignments must be unique within a set across all stages while in DX11 most slots only need to be unique within a stage. Not following these rules will result in a runtime error. Manually assigning slots is an option but is very tedious. To make this easier, the shader system can automate this process provided some constraints are met how resourced are declared.

1. Currently, EZ does not support arrays of resources like `Texture2D Diffuse[3]` in its shaders.
2. Resources must have unique names across all shader stages. The same resource name can be used in multiple stages as long as the resource it maps to is exactly the same.
Expand Down
Loading

0 comments on commit 9afcd6d

Please sign in to comment.