Add support for attributes storage in simulation state #107133

Merged
3 changed files with 316 additions and 24 deletions
Showing only changes of commit 9e58eba968 - Show all commits

View File

@ -13,21 +13,89 @@ class SimulationStateItem {
virtual ~SimulationStateItem() = default;
};
class GeometrySimulationStateItem : public SimulationStateItem {
template <typename T>
class TypedSimulationStateItem : public SimulationStateItem {

I think for all the trivial data types, having this templated type can make sense (although I'm not sure if a generic version would be easier). Currently, I don't think that this should be used for GeometrySet. It feels a bit like a wrong abstraction there if it needs to be specialized below.

I think for all the trivial data types, having this templated type can make sense (although I'm not sure if a generic version would be easier). Currently, I don't think that this should be used for `GeometrySet`. It feels a bit like a wrong abstraction there if it needs to be specialized below.
public:
using DataType = T;
private:
GeometrySet geometry_;
T data_;
public:
GeometrySimulationStateItem(GeometrySet geometry) : geometry_(std::move(geometry))
TypedSimulationStateItem() = default;
TypedSimulationStateItem(const T &data)

Most of these methods/constructors seem unnecessary. A simple constructor that takes a T value that is then moved into data_ should be good enough. (Calling std::move on a const reference as is currently done here does not make sense anyway)

Most of these methods/constructors seem unnecessary. A simple constructor that takes a `T value` that is then moved into `data_` should be good enough. (Calling `std::move` on a const reference as is currently done here does not make sense anyway)

TBH i just added a bunch of common constructors without considering the details, just to get it working.

TBH i just added a bunch of common constructors without considering the details, just to get it working.
{
data_ = std::move(data);
}
TypedSimulationStateItem(T &&data)
{
data_ = std::move(data);
}
template <typename U>
TypedSimulationStateItem(const U &data)
{
data_ = data;
}

If this is really need (and I'm not sure why it is right now), consider adding NonCopyable and NonMovable as base class.

If this is really need (and I'm not sure why it is right now), consider adding `NonCopyable` and `NonMovable` as base class.
const GeometrySet &geometry() const
TypedSimulationStateItem(const TypedSimulationStateItem &) = delete;
TypedSimulationStateItem(TypedSimulationStateItem &&) = delete;
virtual ~TypedSimulationStateItem() {
}
TypedSimulationStateItem &operator=(const TypedSimulationStateItem &) = delete;
TypedSimulationStateItem &operator=(TypedSimulationStateItem &&) = delete;
const T &data() const
{
return geometry_;
return data_;
}
};
/** Specialization for GeometrySet which ensures the state owns the geometry data. */
template <>
class TypedSimulationStateItem<GeometrySet> : public SimulationStateItem {
public:
using DataType = GeometrySet;
private:
GeometrySet data_;
public:
TypedSimulationStateItem() = default;
TypedSimulationStateItem(const GeometrySet &data)
{
data_ = std::move(data);
data_.ensure_owns_direct_data();
}
TypedSimulationStateItem(GeometrySet &&data)
{
data_ = std::move(data);
data_.ensure_owns_direct_data();
}
template <typename U>
TypedSimulationStateItem(const U &data)
{
data_ = data;
data_.ensure_owns_direct_data();
}
TypedSimulationStateItem(const TypedSimulationStateItem &) = delete;
TypedSimulationStateItem(TypedSimulationStateItem &&) = delete;
virtual ~TypedSimulationStateItem() {
}
TypedSimulationStateItem &operator=(const TypedSimulationStateItem &) = delete;
TypedSimulationStateItem &operator=(TypedSimulationStateItem &&) = delete;
const GeometrySet &data() const
{
return data_;
}
};
class SimulationZoneState {
public:
Vector<std::unique_ptr<SimulationStateItem>> items;

View File

@ -12,6 +12,128 @@
#include "node_geometry_util.hh"
namespace blender::nodes {
template <typename T>
static void copy_typed_initial_simulation_state(lf::Params &params, int index)
{
T *data = params.try_get_input_data_ptr_or_request<T>(index);
if (data != nullptr) {
params.set_output(index + 1, std::move(*data));

Might be worth adding a comment explaining the + 1 here, so the reader doesn't have to look elsewhere to figure it out.

Might be worth adding a comment explaining the `+ 1` here, so the reader doesn't have to look elsewhere to figure it out.
}
}
static void copy_initial_simulation_state(
lf::Params &params,
int index,
short socket_type)
{
switch (socket_type) {
case SOCK_FLOAT:
LukasTonne marked this conversation as resolved
Review

Maybe I'm being stupid, but I don't get what's meant by "next" here.

Maybe I'm being stupid, but I don't get what's meant by "next" here.
Review

Was just an ad-hoc name: it's copying from the simulation state into the output parameter for the next iteration. I'll add some comments.

Was just an ad-hoc name: it's copying from the simulation state into the output parameter for the next iteration. I'll add some comments.
Review

Renamed the functions and added comments for clarity. Also the copy_simulation_state_to_output_param function is now shared by input and output node, they both need to do this: input node when preparing the next iteration, output node when outputting the sim result.

Renamed the functions and added comments for clarity. Also the `copy_simulation_state_to_output_param` function is now shared by input and output node, they both need to do this: input node when preparing the next iteration, output node when outputting the sim result.
copy_typed_initial_simulation_state<ValueOrField<float>>(params, index);
break;
case SOCK_VECTOR:
copy_typed_initial_simulation_state<ValueOrField<float3>>(params, index);
break;
case SOCK_RGBA:
copy_typed_initial_simulation_state<ValueOrField<ColorGeometry4f>>(params, index);
LukasTonne marked this conversation as resolved Outdated

Might as well include what has to be done in the TODO comment

Might as well include what has to be done in the TODO comment
break;
case SOCK_BOOLEAN:
copy_typed_initial_simulation_state<ValueOrField<bool>>(params, index);
break;
case SOCK_INT:
copy_typed_initial_simulation_state<ValueOrField<int>>(params, index);
break;
case SOCK_STRING:
LukasTonne marked this conversation as resolved Outdated

remove dead code

remove dead code
copy_typed_initial_simulation_state<ValueOrField<std::string>>(params, index);
break;
case SOCK_OBJECT:
copy_typed_initial_simulation_state<Object *>(params, index);
break;
case SOCK_GEOMETRY:
copy_typed_initial_simulation_state<GeometrySet>(params, index);
break;
case SOCK_COLLECTION:
copy_typed_initial_simulation_state<Collection *>(params, index);
break;
case SOCK_TEXTURE:
copy_typed_initial_simulation_state<Tex *>(params, index);
break;
case SOCK_IMAGE:
copy_typed_initial_simulation_state<Image *>(params, index);
break;
case SOCK_MATERIAL:
copy_typed_initial_simulation_state<Material *>(params, index);
break;
default:
BLI_assert_unreachable();
copy_typed_initial_simulation_state<GeometrySet>(params, index);
break;
}
}
template<typename T>
static void copy_typed_next_simulation_state(lf::Params &params,
int index,
const bke::sim::SimulationStateItem &state_item)
{
if (auto *typed_state_item = dynamic_cast<const bke::sim::TypedSimulationStateItem<T> *>(

auto * -> const auto *

`auto *` -> `const auto *`
&state_item)) {
params.set_output(index + 1, typed_state_item->data());
}
}
static void copy_next_simulation_state(lf::Params &params,
int index,
LukasTonne marked this conversation as resolved Outdated

int index -> const int index

`int index` -> `const int index`
short socket_type,

use eNodeSocketDatatype instead of short

use `eNodeSocketDatatype` instead of `short`
const bke::sim::SimulationStateItem &state_item)
{
switch (socket_type) {
case SOCK_FLOAT:
copy_typed_next_simulation_state<ValueOrField<float>>(params, index, state_item);
break;
case SOCK_VECTOR:
copy_typed_next_simulation_state<ValueOrField<float3>>(params, index, state_item);
break;
case SOCK_RGBA:
copy_typed_next_simulation_state<ValueOrField<ColorGeometry4f>>(params, index, state_item);
break;
case SOCK_BOOLEAN:
copy_typed_next_simulation_state<ValueOrField<bool>>(params, index, state_item);
break;
case SOCK_INT:
copy_typed_next_simulation_state<ValueOrField<int>>(params, index, state_item);
break;
case SOCK_STRING:
copy_typed_next_simulation_state<ValueOrField<std::string>>(params, index, state_item);
break;
case SOCK_OBJECT:
copy_typed_next_simulation_state<Object *>(params, index, state_item);
break;
case SOCK_GEOMETRY:
copy_typed_next_simulation_state<GeometrySet>(params, index, state_item);
break;
case SOCK_COLLECTION:
copy_typed_next_simulation_state<Collection *>(params, index, state_item);
break;
case SOCK_TEXTURE:
copy_typed_next_simulation_state<Tex *>(params, index, state_item);
break;
case SOCK_IMAGE:
copy_typed_next_simulation_state<Image *>(params, index, state_item);
break;
case SOCK_MATERIAL:

functional style cast for enums

functional style cast for enums
copy_typed_next_simulation_state<Material *>(params, index, state_item);
break;
default:
BLI_assert_unreachable();
copy_typed_next_simulation_state<GeometrySet>(params, index, state_item);
break;
}
}
}
namespace blender::nodes::node_geo_simulation_input_cc {
NODE_STORAGE_FUNCS(NodeGeometrySimulationInput);
@ -63,10 +185,7 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
if (params.output_was_set(i + 1)) {
continue;
}
GeometrySet *geometry = params.try_get_input_data_ptr_or_request<GeometrySet>(i);
if (geometry != nullptr) {
params.set_output(i + 1, std::move(*geometry));
}
copy_initial_simulation_state(params, i, simulation_items_[i].socket_type);
}
}
else {
@ -75,10 +194,9 @@ class LazyFunctionForSimulationInputNode final : public LazyFunction {
params.set_output(i + 1, GeometrySet());
continue;
}
const bke::sim::SimulationStateItem *item = prev_zone_state->items[i].get();
if (auto *geometry_item = dynamic_cast<const bke::sim::GeometrySimulationStateItem *>(
item)) {
params.set_output(i + 1, geometry_item->geometry());
const bke::sim::SimulationStateItem *state_item = prev_zone_state->items[i].get();
if (state_item != nullptr) {
copy_next_simulation_state(params, i, simulation_items_[i].socket_type, *state_item);
}
}
}

View File

@ -155,6 +155,112 @@ const CPPType &get_simulation_item_cpp_type(const NodeSimulationItem &item)
}
}
template <typename T>
static std::unique_ptr<bke::sim::TypedSimulationStateItem<T>> make_typed_simulation_state_item(lf::Params &params, int index)
{
using bke::sim::TypedSimulationStateItem;
if (const T *data = params.try_get_input_data_ptr_or_request<T>(index)) {
return std::make_unique<TypedSimulationStateItem<T>>(*data);
}
return std::make_unique<TypedSimulationStateItem<T>>();
}
static std::unique_ptr<bke::sim::SimulationStateItem> make_simulation_state_item(
lf::Params &params, int index, short socket_type)
{
switch (socket_type) {
case SOCK_FLOAT:
return make_typed_simulation_state_item<ValueOrField<float>>(params, index);
case SOCK_VECTOR:
return make_typed_simulation_state_item<ValueOrField<float3>>(params, index);
case SOCK_RGBA:
return make_typed_simulation_state_item<ValueOrField<ColorGeometry4f>>(params, index);
case SOCK_BOOLEAN:
return make_typed_simulation_state_item<ValueOrField<bool>>(params, index);
case SOCK_INT:
return make_typed_simulation_state_item<ValueOrField<int>>(params, index);
case SOCK_STRING:
return make_typed_simulation_state_item<ValueOrField<std::string>>(params, index);
case SOCK_OBJECT:
return make_typed_simulation_state_item<Object *>(params, index);
case SOCK_GEOMETRY:
return make_typed_simulation_state_item<GeometrySet>(params, index);
case SOCK_COLLECTION:
return make_typed_simulation_state_item<Collection *>(params, index);
case SOCK_TEXTURE:
return make_typed_simulation_state_item<Tex *>(params, index);
case SOCK_IMAGE:
return make_typed_simulation_state_item<Image *>(params, index);
case SOCK_MATERIAL:
return make_typed_simulation_state_item<Material *>(params, index);
default:
BLI_assert_unreachable();
return make_typed_simulation_state_item<GeometrySet>(params, index);
}
}
template <typename T>
static void copy_typed_simulation_state_output(lf::Params &params, int index, const bke::sim::SimulationStateItem &state_item)
{
using bke::sim::TypedSimulationStateItem;
if (auto *typed_state_item = dynamic_cast<const bke::sim::TypedSimulationStateItem<T> *>(&state_item)) {
params.set_output(index, typed_state_item->data());
}
}
static void copy_simulation_state_output(
lf::Params &params,
int index,
short socket_type,
const bke::sim::SimulationStateItem &state_item)
{
switch (socket_type) {
case SOCK_FLOAT:
copy_typed_simulation_state_output<ValueOrField<float>>(params, index, state_item);
break;
case SOCK_VECTOR:
copy_typed_simulation_state_output<ValueOrField<float3>>(params, index, state_item);
break;
case SOCK_RGBA:
copy_typed_simulation_state_output<ValueOrField<ColorGeometry4f>>(params, index, state_item);
break;
case SOCK_BOOLEAN:
copy_typed_simulation_state_output<ValueOrField<bool>>(params, index, state_item);
break;
case SOCK_INT:
copy_typed_simulation_state_output<ValueOrField<int>>(params, index, state_item);
break;
case SOCK_STRING:
copy_typed_simulation_state_output<ValueOrField<std::string>>(params, index, state_item);
break;
case SOCK_OBJECT:
copy_typed_simulation_state_output<Object *>(params, index, state_item);
break;
case SOCK_GEOMETRY:
copy_typed_simulation_state_output<GeometrySet>(params, index, state_item);
break;
case SOCK_COLLECTION:
copy_typed_simulation_state_output<Collection *>(params, index, state_item);
break;
case SOCK_TEXTURE:
copy_typed_simulation_state_output<Tex *>(params, index, state_item);
break;
case SOCK_IMAGE:
copy_typed_simulation_state_output<Image *>(params, index, state_item);
break;
case SOCK_MATERIAL:
copy_typed_simulation_state_output<Material *>(params, index, state_item);
break;
default:

Better don't support these ID types yet. They need special handling that we don't have to get into now. (have to avoid dangling pointers in the cache and stuff like that)

Better don't support these ID types yet. They need special handling that we don't have to get into now. (have to avoid dangling pointers in the cache and stuff like that)
BLI_assert_unreachable();
copy_typed_simulation_state_output<GeometrySet>(params, index, state_item);
break;
}
}
} // namespace blender::nodes
namespace blender::nodes::node_geo_simulation_output_cc {
@ -224,14 +330,15 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
bool all_available = true;
for (const int i : simulation_items_.index_range()) {
GeometrySet *input_geometry = params.try_get_input_data_ptr_or_request<GeometrySet>(i);
if (input_geometry == nullptr) {
const NodeSimulationItem &item = simulation_items_[i];
void *input_data = params.try_get_input_data_ptr_or_request(i);
if (input_data == nullptr) {
all_available = false;
continue;
}
input_geometry->ensure_owns_direct_data();
new_zone_state.items[i] = std::make_unique<bke::sim::GeometrySimulationStateItem>(
std::move(*input_geometry));
new_zone_state.items[i] = make_simulation_state_item(params, i, item.socket_type);
}
if (all_available) {
@ -242,17 +349,16 @@ class LazyFunctionForSimulationOutputNode final : public LazyFunction {
void output_cached_state(lf::Params &params, const bke::sim::SimulationZoneState &state) const
{
for (const int i : simulation_items_.index_range()) {
const NodeSimulationItem &item = simulation_items_[i];
if (i >= state.items.size()) {
continue;
}
const bke::sim::SimulationStateItem *item = state.items[i].get();
if (item == nullptr) {
const bke::sim::SimulationStateItem *state_item = state.items[i].get();
if (state_item == nullptr) {
continue;
}
if (auto *geometry_item = dynamic_cast<const bke::sim::GeometrySimulationStateItem *>(
item)) {
params.set_output(i, geometry_item->geometry());
}
copy_simulation_state_output(params, i, item.socket_type, *state_item);
}
params.set_default_remaining_outputs();
}