1 /*
2  * Copyright (c) 2024 Huawei Device Co., Ltd.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15 
16 #include <3d/ecs/components/material_component.h>
17 #include <3d/ecs/components/render_handle_component.h>
18 #include <base/containers/refcnt_ptr.h>
19 #include <base/containers/unordered_map.h>
20 #include <base/util/compile_time_hashes.h>
21 #include <base/util/hash.h>
22 #include <core/ecs/intf_ecs.h>
23 #include <core/intf_engine.h>
24 #include <core/property/intf_property_api.h>
25 #include <core/property/property_types.h>
26 #include <render/device/intf_shader_manager.h>
27 #include <render/implementation_uids.h>
28 #include <render/intf_render_context.h>
29 
30 #include "util/property_util.h"
31 
32 #define IMPLEMENT_MANAGER
33 #include <algorithm>
34 #include <iterator>
35 
36 #include "PropertyTools/property_macros.h"
37 
38 CORE_BEGIN_NAMESPACE()
39 using BASE_NS::string_view;
40 using BASE_NS::vector;
41 using CORE3D_NS::MaterialComponent;
42 
43 /** Extend propertysystem with the types */
44 DECLARE_PROPERTY_TYPE(MaterialComponent::Type);
45 DECLARE_PROPERTY_TYPE(MaterialComponent::TextureTransform);
46 DECLARE_PROPERTY_TYPE(MaterialComponent::TextureInfo);
47 DECLARE_PROPERTY_TYPE(MaterialComponent::Shader);
48 DECLARE_PROPERTY_TYPE(vector<MaterialComponent::TextureTransform>);
49 DECLARE_PROPERTY_TYPE(CORE_NS::IPropertyHandle*);
50 
51 // Declare their metadata
52 BEGIN_ENUM(MaterialTypeMetaData, MaterialComponent::Type)
53 DECL_ENUM(MaterialComponent::Type, METALLIC_ROUGHNESS, "Metallic Roughness")
54 DECL_ENUM(MaterialComponent::Type, SPECULAR_GLOSSINESS, "Specular Glossiness")
55 DECL_ENUM(MaterialComponent::Type, UNLIT, "Unlit")
56 DECL_ENUM(MaterialComponent::Type, UNLIT_SHADOW_ALPHA, "Unlit Shadow Alpha")
57 DECL_ENUM(MaterialComponent::Type, CUSTOM, "Custom material")
58 DECL_ENUM(MaterialComponent::Type, CUSTOM_COMPLEX, "Custom complex material")
59 END_ENUM(MaterialTypeMetaData, MaterialComponent::Type)
60 
61 BEGIN_ENUM(MaterialLightingFlagBitsMetaData, MaterialComponent::LightingFlagBits)
62 DECL_ENUM(MaterialComponent::LightingFlagBits, SHADOW_RECEIVER_BIT, "Shadow Receiver")
63 DECL_ENUM(MaterialComponent::LightingFlagBits, SHADOW_CASTER_BIT, "Shadow Caster")
64 DECL_ENUM(MaterialComponent::LightingFlagBits, PUNCTUAL_LIGHT_RECEIVER_BIT, "Punctual Light Receiver")
65 DECL_ENUM(MaterialComponent::LightingFlagBits, INDIRECT_LIGHT_RECEIVER_BIT, "Indirect Light Receiver")
66 END_ENUM(MaterialLightingFlagBitsMetaData, MaterialComponent::LightingFlagBits)
67 
68 BEGIN_ENUM(ExtraMaterialRenderingFlagBitsMetaData, MaterialComponent::ExtraRenderingFlagBits)
69 DECL_ENUM(MaterialComponent::ExtraRenderingFlagBits, DISCARD_BIT, "Discard Special Materials")
70 DECL_ENUM(MaterialComponent::ExtraRenderingFlagBits, DISABLE_BIT, "Disable Material Rendering")
71 DECL_ENUM(MaterialComponent::ExtraRenderingFlagBits, ALLOW_GPU_INSTANCING_BIT, "Allow GPU Instancing")
72 END_ENUM(ExtraMaterialRenderingFlagBitsMetaData, MaterialComponent::ExtraRenderingFlagBits)
73 
74 BEGIN_METADATA(TextureTransformMetaData, MaterialComponent::TextureTransform)
75 DECL_PROPERTY2(MaterialComponent::TextureTransform, translation, "Translation", 0)
76 DECL_PROPERTY2(MaterialComponent::TextureTransform, rotation, "Rotation", 0)
77 DECL_PROPERTY2(MaterialComponent::TextureTransform, scale, "Scale", 0)
78 END_METADATA(TextureTransformMetaData, MaterialComponent::TextureTransform)
79 
80 BEGIN_METADATA(TextureInfoMetaData, MaterialComponent::TextureInfo)
81 DECL_PROPERTY2(MaterialComponent::TextureInfo, image, "Texture", 0)
82 DECL_PROPERTY2(MaterialComponent::TextureInfo, sampler, "Sampler", 0)
83 DECL_PROPERTY2(MaterialComponent::TextureInfo, factor, "Factor", 0)
84 DECL_PROPERTY2(MaterialComponent::TextureInfo, transform, "Transform", 0)
85 END_METADATA(TextureInfoMetaData, MaterialComponent::TextureInfo)
86 
87 BEGIN_METADATA(MaterialComponentShaderMetaData, MaterialComponent::Shader)
88 DECL_PROPERTY2(MaterialComponent::Shader, shader, "Shader", 0)
89 DECL_PROPERTY2(MaterialComponent::Shader, graphicsState, "Graphics State", 0)
90 END_METADATA(MaterialComponentShaderMetaData, MaterialComponent::Shader)
91 
92 constexpr PropertyTypeDecl TEXTURE_INFO_T = PROPERTYTYPE(MaterialComponent::TextureInfo);
93 
operator ==(const Property & lhs,const Property & rhs)94 static constexpr inline bool operator==(const Property& lhs, const Property& rhs) noexcept
95 {
96     return (lhs.type == rhs.type) && (lhs.hash == rhs.hash) && (lhs.name == rhs.name);
97 }
98 CORE_END_NAMESPACE()
99 
100 CORE3D_BEGIN_NAMESPACE()
101 using BASE_NS::array_view;
102 using BASE_NS::string_view;
103 using BASE_NS::countof;
104 using BASE_NS::vector;
105 using BASE_NS::Uid;
106 using BASE_NS::string;
107 using BASE_NS::refcnt_ptr;
108 using BASE_NS::unordered_map;
109 using BASE_NS::move;
110 using BASE_NS::exchange;
111 using BASE_NS::forward;
112 
113 using CORE_NS::Property;
114 using CORE_NS::IPropertyApi;
115 using CORE_NS::IEcs;
116 using CORE_NS::IPropertyHandle;
117 using CORE_NS::IComponentManager;
118 using CORE_NS::Entity;
119 using CORE_NS::PropertyFlags;
120 using CORE_NS::ScopedHandle;
121 using CORE_NS::GetInstance;
122 using CORE_NS::IClassRegister;
123 using CORE_NS::GetManager;
124 
125 using RENDER_NS::RenderHandleReference;
126 using RENDER_NS::RenderHandle;
127 using RENDER_NS::IRenderContext;
128 using RENDER_NS::UID_RENDER_CONTEXT;
129 using RENDER_NS::IShaderManager;
130 
131 namespace {
132 constexpr string_view PROPERTIES = "properties";
133 constexpr string_view NAME = "name";
134 constexpr string_view DISPLAY_NAME = "displayName";
135 constexpr string_view MATERIAL_COMPONENT_NAME = CORE_NS::GetName<MaterialComponent>();
136 
137 // RenderDataDefaultMaterial::MAX_MATERIAL_CUSTOM_PROPERTY_BYTE_SIZE
138 constexpr uint32_t CUSTOM_PROPERTY_POD_CONTAINER_BYTE_SIZE { 256u };
139 
140 constexpr uint32_t MODIFIED = 0x80000000;
141 
142 template<typename Container, typename Predicate>
FindIf(const Container & container,Predicate && predicate)143 inline typename Container::const_iterator FindIf(const Container& container, Predicate&& predicate)
144 {
145     return std::find_if(container.cbegin(), container.cend(), forward<Predicate>(predicate));
146 }
147 
148 template<typename Container, typename Predicate>
FindIf(Container & container,Predicate && predicate)149 inline typename Container::iterator FindIf(Container& container, Predicate&& predicate)
150 {
151     return std::find_if(container.begin(), container.end(), forward<Predicate>(predicate));
152 }
153 
FindMaterialComponentJson(const CORE_NS::json::value & metaJson)154 const CORE_NS::json::value* FindMaterialComponentJson(const CORE_NS::json::value& metaJson)
155 {
156     if (auto material = FindIf(metaJson.array_,
157             [](const CORE_NS::json::value& value) {
158                 auto nameJson = value.find(NAME);
159                 return nameJson && nameJson->is_string() && nameJson->string_ == MATERIAL_COMPONENT_NAME;
160             });
161         material != metaJson.array_.end()) {
162         return &(*material);
163     }
164     return nullptr;
165 }
166 
AppendProperties(vector<Property> & newProperties,vector<string> & newStringPropStorage,const CORE_NS::json::value & material,const array_view<const Property> componentMetaData)167 void AppendProperties(vector<Property>& newProperties, vector<string>& newStringPropStorage,
168     const CORE_NS::json::value& material, const array_view<const Property> componentMetaData)
169 {
170     if (const CORE_NS::json::value* propertiesJson = material.find(PROPERTIES);
171         propertiesJson && propertiesJson->is_object()) {
172         for (const auto& propertyObject : propertiesJson->object_) {
173             if (const auto propertyIt =
174                     FindIf(componentMetaData, [key = propertyObject.key](const Property& p) { return p.name == key; });
175                 propertyIt != componentMetaData.end()) {
176                 // if it's an array, make sure sizes match
177 #if (CORE3D_VALIDATION_ENABLED == 1)
178                 if (propertyObject.value.is_array() && propertyIt->type.isArray &&
179                     propertyObject.value.array_.size() > propertyIt->count) {
180                     CORE_LOG_W("CORE3D_VALIDATION: material property metadata count mismatch (%u <= %u)",
181                         uint32_t(propertyObject.value.array_.size()), uint32_t(propertyIt->count));
182                 }
183 #endif
184                 if (propertyObject.value.is_array() && propertyIt->type.isArray &&
185                     propertyObject.value.array_.size() <= propertyIt->count) {
186                     uint32_t index = 0u;
187                     // reserve strings, name and display name (reason for * 2)
188                     newStringPropStorage.reserve(propertyObject.value.array_.size() * 2);
189                     for (const CORE_NS::json::value& value : propertyObject.value.array_) {
190                         // if the array has objects, the metadata should have containerMethods for
191                         // the members
192                         if (value.is_object() && !value.object_.empty() && propertyIt->metaData.containerMethods) {
193                             const auto& containerProperty = propertyIt->metaData.containerMethods->property;
194 
195                             // calculate offset to current struct in the array
196                             const auto offset = propertyIt->offset + containerProperty.size * index;
197                             Property newProperty = containerProperty;
198 
199                             // add the currect struct offset to the member offset
200                             newProperty.offset += offset;
201 
202                             // update 'name' and 'displayName' if needed
203                             if (auto name = value.find(NAME); name && name->is_string()) {
204                                 newStringPropStorage.push_back(string(name->string_));
205                                 newProperty.name = newStringPropStorage.back();
206                                 newProperty.hash = BASE_NS::FNV1aHash(newProperty.name.data(), newProperty.name.size());
207                             }
208                             if (auto displayName = value.find(DISPLAY_NAME); displayName && displayName->is_string()) {
209                                 newStringPropStorage.push_back(string(displayName->string_));
210                                 newProperty.displayName = newStringPropStorage.back();
211                             }
212                             newProperties.push_back(newProperty);
213                         } else {
214                             // NOTE: handle non-object properties?
215                         }
216                         ++index;
217                     }
218                 } else {
219                     // NOTE: handle non-array properties?
220                 }
221             }
222         }
223     }
224 }
225 
MapTextureSlots(array_view<MaterialComponent::TextureInfo> textures,size_t baseOffset,array_view<const Property> newProperties,array_view<const Property> oldProperties)226 void MapTextureSlots(array_view<MaterialComponent::TextureInfo> textures, size_t baseOffset,
227     array_view<const Property> newProperties, array_view<const Property> oldProperties)
228 {
229     MaterialComponent::TextureInfo tmp[MaterialComponent::TextureIndex::TEXTURE_COUNT];
230     for (const auto& newProp : newProperties) {
231         if (newProp.type == CORE_NS::TEXTURE_INFO_T) {
232             if (auto it = std::find(oldProperties.cbegin(), oldProperties.cend(), newProp);
233                 it != oldProperties.cend()) {
234                 auto& oldProp = *it;
235                 // theoretically the offset of TextureInfos can't be less than the offset of the first and this could be
236                 // an assertion
237                 if (oldProp.offset >= baseOffset && newProp.offset >= baseOffset) {
238                     auto oldIndex = (oldProp.offset - baseOffset) / sizeof(MaterialComponent::TextureInfo);
239                     auto newIndex = (newProp.offset - baseOffset) / sizeof(MaterialComponent::TextureInfo);
240                     if (newIndex < BASE_NS::countof(tmp) && oldIndex < textures.size()) {
241                         tmp[newIndex] = BASE_NS::move(textures[oldIndex]);
242                     }
243                 }
244             }
245         }
246     }
247     std::move(std::begin(tmp), std::end(tmp), std::begin(textures));
248 }
249 
UpdateCustomPropertyMetadata(const CORE_NS::json::value & customProperties,CustomPropertyPodContainer & properties)250 void UpdateCustomPropertyMetadata(const CORE_NS::json::value& customProperties, CustomPropertyPodContainer& properties)
251 {
252     for (const CORE_NS::json::value& propertyValue : customProperties.array_) {
253         if (propertyValue.is_object()) {
254             // NOTE: set and binding are currently ignored as the custom material property cannot be mapped
255             // to user defined sets and bindings
256             for (const auto& object : propertyValue.object_) {
257                 if (object.key == "data" && object.value.is_array()) {
258                     // reserve the property count
259                     properties.ReservePropertyCount(object.value.array_.size());
260                     for (const auto& dataValue : object.value.array_) {
261                         if (dataValue.is_object()) {
262                             string_view name;
263                             string_view displayName;
264                             string_view type;
265                             const CORE_NS::json::value* value = nullptr;
266                             for (const auto& dataObject : dataValue.object_) {
267                                 if (dataObject.key == NAME && dataObject.value.is_string()) {
268                                     name = dataObject.value.string_;
269                                 } else if (dataObject.key == DISPLAY_NAME && dataObject.value.is_string()) {
270                                     displayName = dataObject.value.string_;
271                                 } else if (dataObject.key == "type" && dataObject.value.is_string()) {
272                                     type = dataObject.value.string_;
273                                 } else if (dataObject.key == "value") {
274                                     value = &dataObject.value;
275                                 }
276                             }
277                             const CORE_NS::PropertyTypeDecl typeDecl = CustomPropertyPodHelper::GetPropertyTypeDeclaration(type);
278                             const size_t align = CustomPropertyPodHelper::GetPropertyTypeAlignment(typeDecl);
279                             const size_t offset = [](size_t value, size_t align) -> size_t {
280                                 if (align == 0U) {
281                                     return value;
282                                 }
283                                 return ((value + align - 1U) / align) * align;
284                             }(properties.GetByteSize(), align);
285                             properties.AddOffsetProperty(name, displayName, offset, typeDecl);
286                             CustomPropertyPodHelper::SetCustomPropertyBlobValue(typeDecl, value, properties, offset);
287                         }
288                     }
289                 }
290             }
291         }
292     }
293 }
294 } // namespace
295 
296 class MaterialComponentManager : public IMaterialComponentManager, public IPropertyApi {
297     using ComponentId = IComponentManager::ComponentId;
298 
299 public:
300     explicit MaterialComponentManager(IEcs& ecs) noexcept;
301     ~MaterialComponentManager() override;
302 
303     // IPropertyApi
304     size_t PropertyCount() const override;
305     const Property* MetaData(size_t index) const override;
306     array_view<const Property> MetaData() const override;
307     IPropertyHandle* Create() const override;
308     IPropertyHandle* Clone(const IPropertyHandle*) const override;
309     void Release(IPropertyHandle*) const override;
310     uint64_t Type() const override;
311 
312     // IComponentManager
313     virtual string_view GetName() const override;
314     virtual Uid GetUid() const override;
315     size_t GetComponentCount() const override;
316     const IPropertyApi& GetPropertyApi() const override;
317     Entity GetEntity(ComponentId index) const override;
318     uint32_t GetComponentGeneration(ComponentId index) const override;
319     bool HasComponent(Entity entity) const override;
320     IComponentManager::ComponentId GetComponentId(Entity entity) const override;
321     void Create(Entity entity) override;
322     bool Destroy(Entity entity) override;
323     void Gc() override;
324     void Destroy(array_view<const Entity> gcList) override;
325     vector<Entity> GetAddedComponents() override;
326     vector<Entity> GetRemovedComponents() override;
327     vector<Entity> GetUpdatedComponents() override;
328     CORE_NS::ComponentManagerModifiedFlags GetModifiedFlags() const override;
329     void ClearModifiedFlags() override;
330     uint32_t GetGenerationCounter() const override;
331     void SetData(Entity entity, const IPropertyHandle& dataHandle) override;
332     const IPropertyHandle* GetData(Entity entity) const override;
333     IPropertyHandle* GetData(Entity entity) override;
334     void SetData(ComponentId index, const IPropertyHandle& dataHandle) override;
335     const IPropertyHandle* GetData(ComponentId index) const override;
336     IPropertyHandle* GetData(ComponentId index) override;
337     IEcs& GetEcs() const override;
338 
339     // IMaterialComponentManager
340     MaterialComponent Get(ComponentId index) const override;
341     MaterialComponent Get(Entity entity) const override;
342     void Set(ComponentId index, const MaterialComponent& aData) override;
343     void Set(Entity entity, const MaterialComponent& aData) override;
344     ScopedHandle<const MaterialComponent> Read(ComponentId index) const override;
345     ScopedHandle<const MaterialComponent> Read(Entity entity) const override;
346     ScopedHandle<MaterialComponent> Write(ComponentId index) override;
347     ScopedHandle<MaterialComponent> Write(Entity entity) override;
348 
349     // internal, non-public
350     void Updated(Entity entity);
351 
352 private:
353     bool IsMatchingHandle(const IPropertyHandle& handle);
354 
355     BEGIN_PROPERTY(MaterialComponent, componentProperties_)
356 #include <3d/ecs/components/material_component.h>
357     END_PROPERTY();
358     static constexpr array_view<const Property> componentMetaData_ { componentProperties_,
359         countof(componentProperties_) };
360     // clang-format off
361     static constexpr CORE_NS::Property texturesMetaData_[] = {
362         DECL_NAMED_PROPERTY2(baseColor, textures[MaterialComponent::TextureIndex::BASE_COLOR], "Base Color", 0)
363         DECL_NAMED_PROPERTY2(normal, textures[MaterialComponent::TextureIndex::NORMAL], "Normal Map", 0)
364         DECL_NAMED_PROPERTY2(material, textures[MaterialComponent::TextureIndex::MATERIAL], "Material", 0)
365         DECL_NAMED_PROPERTY2(emissive, textures[MaterialComponent::TextureIndex::EMISSIVE], "Emissive", 0)
366         DECL_NAMED_PROPERTY2(ambientOcclusion, textures[MaterialComponent::TextureIndex::AO], "Ambient Occlusion", 0)
367         DECL_NAMED_PROPERTY2(clearcoat, textures[MaterialComponent::TextureIndex::CLEARCOAT], "Clearcoat", 0)
368         DECL_NAMED_PROPERTY2(clearcoatRoughness, textures[MaterialComponent::TextureIndex::CLEARCOAT_ROUGHNESS],
369             "Clearcoat Roughness", 0)
370         DECL_NAMED_PROPERTY2(clearcoatNormal, textures[MaterialComponent::TextureIndex::CLEARCOAT_NORMAL],
371             "Clearcoat Normal", 0)
372         DECL_NAMED_PROPERTY2(sheen, textures[MaterialComponent::TextureIndex::SHEEN], "Sheen", 0)
373         DECL_NAMED_PROPERTY2(transmission, textures[MaterialComponent::TextureIndex::TRANSMISSION], "Transmission", 0)
374         DECL_NAMED_PROPERTY2(specular, textures[MaterialComponent::TextureIndex::SPECULAR], "Specular", 0)
375     };
376     // clang-format on
377     static constexpr array_view<const Property> defaultMetaData_ { texturesMetaData_, countof(texturesMetaData_) };
378 
379     static constexpr uint64_t typeHash_ = BASE_NS::CompileTime::FNV1aHash(CORE_NS::GetName<MaterialComponent>().data());
380 
381     struct ReferencedProperties {
RefMaterialComponentManager::ReferencedProperties382         void Ref()
383         {
384             ++cnt;
385         }
386 
UnrefMaterialComponentManager::ReferencedProperties387         void Unref()
388         {
389             if (--cnt == 0) {
390                 delete this;
391             }
392         }
393 
394         vector<Property> properties;
395         // need to be stored to have a valid string_view in properties (for name and display name)
396         vector<string> stringStorage;
397         uint32_t cnt { 0u };
398     };
399 
400     class ComponentHandle : public IPropertyHandle, IPropertyApi {
401     public:
402         ComponentHandle() = delete;
403         ComponentHandle(MaterialComponentManager* owner, Entity entity) noexcept;
404         ComponentHandle(MaterialComponentManager* owner, Entity entity, const MaterialComponent& data) noexcept;
405         ~ComponentHandle() override = default;
406         ComponentHandle(const ComponentHandle& other) = delete;
407         ComponentHandle(ComponentHandle&& other) noexcept;
408         ComponentHandle& operator=(const ComponentHandle& other);
409         ComponentHandle& operator=(ComponentHandle&& other) noexcept;
410 
411         // IPropertyHandle
412         const IPropertyApi* Owner() const override;
413         size_t Size() const override;
414         const void* RLock() const override;
415         void RUnlock() const override;
416         void* WLock() override;
417         void WUnlock() override;
418 
419         // IPropertyApi
420         size_t PropertyCount() const override;
421         const Property* MetaData(size_t index) const override;
422         array_view<const Property> MetaData() const override;
423         uint64_t Type() const override;
424         IPropertyHandle* Create() const override;
425         IPropertyHandle* Clone(const IPropertyHandle* src) const override;
426         void Release(IPropertyHandle* handle) const override;
427 
428         void UpdateMetadata() const;
429 
430         MaterialComponentManager* manager_ { nullptr };
431         Entity entity_;
432         struct CachedShader {
433             // we do not keep reference here, if it's not in use then it could be destroyed
434             RenderHandle shader {};
435             // frame index to see the shader "timestamp"
436             uint64_t frameIndex { 0 };
437         };
438         mutable CachedShader cachedShader_;
439         mutable refcnt_ptr<ReferencedProperties> metaData_;
440         uint32_t generation_ { 0 };
441         mutable uint32_t rLocked_ { 0 };
442         mutable bool wLocked_ { false };
443         bool dirty_ { false };
444         mutable MaterialComponent data_;
445         mutable BASE_NS::unique_ptr<CustomPropertyPodContainer> custom_;
446     };
447 
448     IEcs& ecs_;
449     IShaderManager& shaderManager_;
450     IRenderHandleComponentManager* renderHandleManager_ { nullptr };
451 
452     uint32_t generationCounter_ { 0 };
453     uint32_t modifiedFlags_ { 0 };
454     unordered_map<Entity, ComponentId> entityComponent_;
455     vector<ComponentHandle> components_;
456     vector<Entity> added_;
457     vector<Entity> removed_;
458     vector<Entity> updated_;
459 
460     unordered_map<RenderHandle, refcnt_ptr<ReferencedProperties>> properties_;
461 };
462 
MaterialComponentManager(IEcs & ecs)463 MaterialComponentManager::MaterialComponentManager(IEcs& ecs) noexcept
464     : ecs_(ecs), shaderManager_(GetInstance<IRenderContext>(
465                      *ecs.GetClassFactory().GetInterface<IClassRegister>(), UID_RENDER_CONTEXT)
466                                     ->GetDevice()
467                                     .GetShaderManager()),
468       renderHandleManager_(GetManager<IRenderHandleComponentManager>(ecs))
469 {
470     // Initial reservation for 64 components/entities.
471     // Will resize as needed.
472     constexpr size_t initialComponentReserveSize = 64;
473     components_.reserve(initialComponentReserveSize);
474     entityComponent_.reserve(initialComponentReserveSize);
475 
476     auto result =
477         properties_.insert_or_assign(RenderHandle {}, refcnt_ptr<ReferencedProperties>(new ReferencedProperties));
478     auto& properties = result.first->second->properties;
479     properties.insert(properties.end(), componentMetaData_.begin(), componentMetaData_.end());
480     properties.insert(properties.end(), defaultMetaData_.begin(), defaultMetaData_.end());
481 }
482 
483 MaterialComponentManager::~MaterialComponentManager() = default;
484 
485 // IPropertyApi
PropertyCount() const486 size_t MaterialComponentManager::PropertyCount() const
487 {
488     return componentMetaData_.size();
489 }
490 
MetaData(size_t index) const491 const Property* MaterialComponentManager::MetaData(size_t index) const
492 {
493     if (index < componentMetaData_.size()) {
494         return &componentMetaData_[index];
495     }
496     return nullptr;
497 }
498 
MetaData() const499 array_view<const Property> MaterialComponentManager::MetaData() const
500 {
501     return componentMetaData_;
502 }
503 
Create() const504 IPropertyHandle* MaterialComponentManager::Create() const
505 {
506     return new ComponentHandle(const_cast<MaterialComponentManager*>(this), {}, {});
507 }
508 
Clone(const IPropertyHandle * src) const509 IPropertyHandle* MaterialComponentManager::Clone(const IPropertyHandle* src) const
510 {
511     if (src) {
512         auto owner = src->Owner();
513         if (owner == this) {
514             auto* h = static_cast<const ComponentHandle*>(src);
515             return new ComponentHandle(const_cast<MaterialComponentManager*>(this), {}, h->data_);
516         } else if (owner) {
517             return owner->Clone(src);
518         }
519     }
520     return nullptr;
521 }
522 
Release(IPropertyHandle * dst) const523 void MaterialComponentManager::Release(IPropertyHandle* dst) const
524 {
525     if (dst) {
526         auto owner = dst->Owner();
527         if (owner == this) {
528             // we can only destroy things we "own" (know)
529             auto* handle = static_cast<ComponentHandle*>(dst);
530             if (auto id = GetComponentId(handle->entity_); id != IComponentManager::INVALID_COMPONENT_ID) {
531                 if (&components_[id] == handle) {
532                     // This is one of the components (bound to an entity) so do nothing
533                     return;
534                 }
535             }
536             delete handle;
537         } else if (owner) {
538             owner->Release(dst);
539         }
540     }
541 }
542 
Type() const543 uint64_t MaterialComponentManager::Type() const
544 {
545     return typeHash_;
546 }
547 
548 // IComponentManager
GetName() const549 string_view MaterialComponentManager::GetName() const
550 {
551     constexpr auto name = CORE_NS::GetName<MaterialComponent>();
552     return name;
553 }
554 
GetUid() const555 Uid MaterialComponentManager::GetUid() const
556 {
557     return IMaterialComponentManager::UID;
558 }
559 
GetComponentCount() const560 size_t MaterialComponentManager::GetComponentCount() const
561 {
562     return components_.size();
563 }
564 
GetPropertyApi() const565 const IPropertyApi& MaterialComponentManager::GetPropertyApi() const
566 {
567     return *this;
568 }
569 
GetEntity(ComponentId index) const570 Entity MaterialComponentManager::GetEntity(ComponentId index) const
571 {
572     if (index < components_.size()) {
573         return components_[index].entity_;
574     }
575     return Entity();
576 }
577 
GetComponentGeneration(ComponentId index) const578 uint32_t MaterialComponentManager::GetComponentGeneration(ComponentId index) const
579 {
580     if (index < components_.size()) {
581         return components_[index].generation_;
582     }
583     return 0;
584 }
585 
HasComponent(Entity entity) const586 bool MaterialComponentManager::HasComponent(Entity entity) const
587 {
588     return GetComponentId(entity) != IComponentManager::INVALID_COMPONENT_ID;
589 }
590 
GetComponentId(Entity entity) const591 IComponentManager::ComponentId MaterialComponentManager::GetComponentId(Entity entity) const
592 {
593     if (CORE_NS::EntityUtil::IsValid(entity)) {
594         if (auto it = entityComponent_.find(entity); it != entityComponent_.end()) {
595             return it->second;
596         }
597     }
598     return IComponentManager::INVALID_COMPONENT_ID;
599 }
600 
Create(Entity entity)601 void MaterialComponentManager::Create(Entity entity)
602 {
603     if (CORE_NS::EntityUtil::IsValid(entity)) {
604         if (auto it = entityComponent_.find(entity); it == entityComponent_.end()) {
605             entityComponent_.insert({ entity, static_cast<ComponentId>(components_.size()) });
606             auto& component = components_.emplace_back(this, entity);
607             added_.push_back(entity);
608             modifiedFlags_ |= CORE_NS::CORE_COMPONENT_MANAGER_COMPONENT_ADDED_BIT;
609             generationCounter_++;
610 
611             // lock/unlock for toggling component updated behavior.
612             component.WLock();
613             component.WUnlock();
614         } else {
615             if (auto dst = CORE_NS::ScopedHandle<MaterialComponent>(&components_[it->second]); dst) {
616                 *dst = {};
617             }
618         }
619     }
620 }
621 
Destroy(Entity entity)622 bool MaterialComponentManager::Destroy(Entity entity)
623 {
624     if (CORE_NS::EntityUtil::IsValid(entity)) {
625         if (auto it = entityComponent_.find(entity); it != entityComponent_.end()) {
626             components_[it->second].entity_ = {}; // invalid entity. (marks it as ready for re-use)
627             entityComponent_.erase(it);
628             removed_.push_back(entity);
629             modifiedFlags_ |= CORE_NS::CORE_COMPONENT_MANAGER_COMPONENT_REMOVED_BIT;
630             generationCounter_++;
631             return true;
632         }
633     }
634     return false;
635 }
636 
Gc()637 void MaterialComponentManager::Gc()
638 {
639     const bool hasRemovedComponents = modifiedFlags_ & CORE_NS::CORE_COMPONENT_MANAGER_COMPONENT_REMOVED_BIT;
640     if (!hasRemovedComponents) {
641         return;
642     }
643     ComponentId componentCount = static_cast<ComponentId>(components_.size());
644     for (ComponentId id = 0; id < componentCount;) {
645         auto* it = &components_[id];
646         // invalid entity.. if so clean garbage
647         if (!CORE_NS::EntityUtil::IsValid(it->entity_)) {
648             // find last valid and swap with it
649             for (ComponentId rid = componentCount - 1; rid > id; rid--) {
650                 auto* rit = &components_[rid];
651                 // valid entity? if so swap the components.
652                 if (CORE_NS::EntityUtil::IsValid(rit->entity_)) {
653                     // fix the entityComponent_ map (update the component id for the entity)
654                     entityComponent_[rit->entity_] = id;
655                     *it = move(*rit);
656                     break;
657                 }
658             }
659             componentCount--;
660             continue;
661         }
662         id++;
663     }
664     if (components_.size() > componentCount) {
665         auto diff = static_cast<typename decltype(components_)::difference_type>(componentCount);
666         components_.erase(components_.cbegin() + diff, components_.cend());
667     }
668 }
669 
Destroy(const array_view<const Entity> gcList)670 void MaterialComponentManager::Destroy(const array_view<const Entity> gcList)
671 {
672     for (const Entity e : gcList) {
673         Destroy(e);
674     }
675 }
676 
GetAddedComponents()677 vector<Entity> MaterialComponentManager::GetAddedComponents()
678 {
679     return BASE_NS::move(added_);
680 }
681 
GetRemovedComponents()682 vector<Entity> MaterialComponentManager::GetRemovedComponents()
683 {
684     return BASE_NS::move(removed_);
685 }
686 
GetUpdatedComponents()687 vector<Entity> MaterialComponentManager::GetUpdatedComponents()
688 {
689     vector<Entity> updated;
690     if (modifiedFlags_ & MODIFIED) {
691         modifiedFlags_ &= ~MODIFIED;
692         updated.reserve(components_.size() / 2); // 2: half
693         for (auto& handle : components_) {
694             if (handle.dirty_) {
695                 handle.dirty_ = false;
696                 updated.push_back(handle.entity_);
697             }
698         }
699     }
700     return updated;
701 }
702 
GetModifiedFlags() const703 CORE_NS::ComponentManagerModifiedFlags MaterialComponentManager::GetModifiedFlags() const
704 {
705     return modifiedFlags_ & ~MODIFIED;
706 }
707 
ClearModifiedFlags()708 void MaterialComponentManager::ClearModifiedFlags()
709 {
710     modifiedFlags_ &= MODIFIED;
711 }
712 
GetGenerationCounter() const713 uint32_t MaterialComponentManager::GetGenerationCounter() const
714 {
715     return generationCounter_;
716 }
717 
SetData(Entity entity,const IPropertyHandle & dataHandle)718 void MaterialComponentManager::SetData(Entity entity, const IPropertyHandle& dataHandle)
719 {
720     if (!IsMatchingHandle(dataHandle)) {
721         return;
722     }
723     if (const auto src = ScopedHandle<const MaterialComponent>(&dataHandle); src) {
724         if (const auto it = entityComponent_.find(entity); it != entityComponent_.end()) {
725             components_[it->second] = static_cast<const ComponentHandle&>(dataHandle);
726         }
727     }
728 }
729 
GetData(Entity entity) const730 const IPropertyHandle* MaterialComponentManager::GetData(Entity entity) const
731 {
732     if (CORE_NS::EntityUtil::IsValid(entity)) {
733         if (const auto it = entityComponent_.find(entity); it != entityComponent_.end()) {
734             if (it->second < components_.size()) {
735                 return &components_[it->second];
736             }
737         }
738     }
739     return nullptr;
740 }
741 
GetData(Entity entity)742 IPropertyHandle* MaterialComponentManager::GetData(Entity entity)
743 {
744     if (CORE_NS::EntityUtil::IsValid(entity)) {
745         if (const auto it = entityComponent_.find(entity); it != entityComponent_.end()) {
746             if (it->second < components_.size()) {
747                 return &components_[it->second];
748             }
749         }
750     }
751     return nullptr;
752 }
753 
SetData(ComponentId index,const IPropertyHandle & dataHandle)754 void MaterialComponentManager::SetData(ComponentId index, const IPropertyHandle& dataHandle)
755 {
756     if (!IsMatchingHandle(dataHandle)) {
757         // We could verify the metadata here.
758         // And in copy only the matching properties one-by-one also.
759         return;
760     }
761     if (index < components_.size()) {
762         if (const auto src = ScopedHandle<const MaterialComponent>(&dataHandle); src) {
763             components_[index] = static_cast<const ComponentHandle&>(dataHandle);
764         }
765     }
766 }
767 
GetData(ComponentId index) const768 const IPropertyHandle* MaterialComponentManager::GetData(ComponentId index) const
769 {
770     if (index < components_.size()) {
771         return &components_[index];
772     }
773     return nullptr;
774 }
775 
GetData(ComponentId index)776 IPropertyHandle* MaterialComponentManager::GetData(ComponentId index)
777 {
778     if (index < components_.size()) {
779         return &components_[index];
780     }
781     return nullptr;
782 }
783 
GetEcs() const784 IEcs& MaterialComponentManager::GetEcs() const
785 {
786     return ecs_;
787 }
788 
789 // IMaterialComponentManager
Get(ComponentId index) const790 MaterialComponent MaterialComponentManager::Get(ComponentId index) const
791 {
792     if (auto handle = Read(index); handle) {
793         return *handle;
794     }
795     return MaterialComponent {};
796 }
797 
Get(Entity entity) const798 MaterialComponent MaterialComponentManager::Get(Entity entity) const
799 {
800     if (auto handle = Read(entity); handle) {
801         return *handle;
802     }
803     return MaterialComponent {};
804 }
805 
Set(ComponentId index,const MaterialComponent & data)806 void MaterialComponentManager::Set(ComponentId index, const MaterialComponent& data)
807 {
808     if (auto handle = Write(index); handle) {
809         *handle = data;
810     }
811 }
812 
Set(Entity entity,const MaterialComponent & data)813 void MaterialComponentManager::Set(Entity entity, const MaterialComponent& data)
814 {
815     if (CORE_NS::EntityUtil::IsValid(entity)) {
816         if (auto handle = Write(entity); handle) {
817             *handle = data;
818             // NOTE: customProperties are valid when updating itself
819         } else {
820             entityComponent_.insert({ entity, static_cast<ComponentId>(components_.size()) });
821             auto& component = components_.emplace_back(this, entity, data);
822             added_.push_back(entity);
823             modifiedFlags_ |= CORE_NS::CORE_COMPONENT_MANAGER_COMPONENT_ADDED_BIT;
824             generationCounter_++;
825 
826             // lock/unlock for toggling component updated behavior.
827             component.WLock();
828             component.WUnlock();
829         }
830     }
831 }
832 
Read(ComponentId index) const833 ScopedHandle<const MaterialComponent> MaterialComponentManager::Read(ComponentId index) const
834 {
835     return ScopedHandle<const MaterialComponent> { GetData(index) };
836 }
837 
Read(Entity entity) const838 ScopedHandle<const MaterialComponent> MaterialComponentManager::Read(Entity entity) const
839 {
840     return ScopedHandle<const MaterialComponent> { GetData(entity) };
841 }
842 
Write(ComponentId index)843 ScopedHandle<MaterialComponent> MaterialComponentManager::Write(ComponentId index)
844 {
845     return ScopedHandle<MaterialComponent> { GetData(index) };
846 }
847 
Write(Entity entity)848 ScopedHandle<MaterialComponent> MaterialComponentManager::Write(Entity entity)
849 {
850     return ScopedHandle<MaterialComponent> { GetData(entity) };
851 }
852 
853 // Internal
Updated(Entity entity)854 void MaterialComponentManager::Updated(Entity entity)
855 {
856     CORE_ASSERT_MSG(CORE_NS::EntityUtil::IsValid(entity), "Invalid ComponentId, bound to INVALID_ENTITY");
857     modifiedFlags_ |= CORE_NS::CORE_COMPONENT_MANAGER_COMPONENT_UPDATED_BIT | MODIFIED;
858     generationCounter_++;
859 }
860 
IsMatchingHandle(const IPropertyHandle & dataHandle)861 bool MaterialComponentManager::IsMatchingHandle(const IPropertyHandle& dataHandle)
862 {
863     if (dataHandle.Owner() == this) {
864         return true;
865     }
866     if (dataHandle.Owner() && (dataHandle.Owner()->Type() == typeHash_)) {
867         return true;
868     }
869     return false;
870 }
871 
872 // Handle implementation
ComponentHandle(MaterialComponentManager * owner,Entity entity)873 MaterialComponentManager::ComponentHandle::ComponentHandle(MaterialComponentManager* owner, Entity entity) noexcept
874     : ComponentHandle(owner, entity, {})
875 {}
876 
ComponentHandle(MaterialComponentManager * owner,Entity entity,const MaterialComponent & data)877 MaterialComponentManager::ComponentHandle::ComponentHandle(
878     MaterialComponentManager* owner, Entity entity, const MaterialComponent& data) noexcept
879     : manager_(owner), entity_(entity), data_(data)
880 {}
881 
ComponentHandle(ComponentHandle && other)882 MaterialComponentManager::ComponentHandle::ComponentHandle(ComponentHandle&& other) noexcept
883     : manager_(other.manager_), entity_(exchange(other.entity_, {})), cachedShader_(exchange(other.cachedShader_, {})),
884       metaData_(exchange(other.metaData_, nullptr)), generation_(exchange(other.generation_, 0U)),
885       rLocked_(exchange(other.rLocked_, 0U)), wLocked_(exchange(other.wLocked_, false)),
886       data_(exchange(other.data_, {})), custom_(exchange(other.custom_, {}))
887 {}
888 
operator =(ComponentHandle && other)889 MaterialComponentManager::ComponentHandle& MaterialComponentManager::ComponentHandle::operator=(
890     ComponentHandle&& other) noexcept
891 {
892     if (this != &other) {
893         CORE_ASSERT(manager_ == other.manager_);
894         entity_ = exchange(other.entity_, {});
895         cachedShader_ = exchange(other.cachedShader_, {});
896         metaData_ = exchange(other.metaData_, nullptr);
897         generation_ = exchange(other.generation_, 0U);
898         rLocked_ = exchange(other.rLocked_, 0U);
899         wLocked_ = exchange(other.wLocked_, false);
900         dirty_ = exchange(other.dirty_, false);
901         data_ = exchange(other.data_, {});
902         custom_ = exchange(other.custom_, {});
903     }
904     return *this;
905 }
906 
907 // ComponentHandle IPropertyHandle
Owner() const908 const IPropertyApi* MaterialComponentManager::ComponentHandle::Owner() const
909 {
910     return this;
911 }
912 
Size() const913 size_t MaterialComponentManager::ComponentHandle::Size() const
914 {
915     return sizeof(MaterialComponent);
916 }
917 
RLock() const918 const void* MaterialComponentManager::ComponentHandle::RLock() const
919 {
920     CORE_ASSERT(manager_);
921     CORE_ASSERT(!wLocked_);
922     rLocked_++;
923     return &data_;
924 }
925 
RUnlock() const926 void MaterialComponentManager::ComponentHandle::RUnlock() const
927 {
928     CORE_ASSERT(manager_);
929     CORE_ASSERT(rLocked_ > 0U);
930     rLocked_--;
931 }
932 
WLock()933 void* MaterialComponentManager::ComponentHandle::WLock()
934 {
935     CORE_ASSERT(manager_);
936     CORE_ASSERT(rLocked_ <= 1U && !wLocked_);
937     wLocked_ = true;
938     return &data_;
939 }
940 
WUnlock()941 void MaterialComponentManager::ComponentHandle::WUnlock()
942 {
943     CORE_ASSERT(manager_);
944     CORE_ASSERT(wLocked_);
945     wLocked_ = false;
946     // update generation etc..
947     generation_++;
948     if (CORE_NS::EntityUtil::IsValid(entity_)) {
949         dirty_ = true;
950         UpdateMetadata();
951         manager_->Updated(entity_);
952     }
953 }
954 
955 // ComponentHandle IPropertyApi
PropertyCount() const956 size_t MaterialComponentManager::ComponentHandle::PropertyCount() const
957 {
958     if (!metaData_) {
959         return manager_->PropertyCount();
960     } else {
961         return metaData_->properties.size();
962     }
963 }
964 
MetaData(size_t index) const965 const Property* MaterialComponentManager::ComponentHandle::MetaData(size_t index) const
966 {
967     if (!metaData_) {
968         return manager_->MetaData(index);
969     } else if (index < metaData_->properties.size()) {
970         return &metaData_->properties[index];
971     }
972     return nullptr;
973 }
974 
MetaData() const975 array_view<const Property> MaterialComponentManager::ComponentHandle::MetaData() const
976 {
977     if (!metaData_) {
978         return manager_->MetaData();
979     } else {
980         return metaData_->properties;
981     }
982 }
983 
Type() const984 uint64_t MaterialComponentManager::ComponentHandle::Type() const
985 {
986     return manager_->Type();
987 }
988 
Create() const989 IPropertyHandle* MaterialComponentManager::ComponentHandle::Create() const
990 {
991     return new ComponentHandle(manager_, {}, {});
992 }
993 
Clone(const IPropertyHandle * src) const994 IPropertyHandle* MaterialComponentManager::ComponentHandle::Clone(const IPropertyHandle* src) const
995 {
996     if (src) {
997         auto owner = src->Owner();
998         if (owner == this) {
999             auto* h = static_cast<const ComponentHandle*>(src);
1000             return new ComponentHandle(h->manager_, {}, h->data_);
1001         } else if (owner) {
1002             return owner->Clone(src);
1003         }
1004         return manager_->Clone(src);
1005     }
1006     return nullptr;
1007 }
1008 
Release(IPropertyHandle * handle) const1009 void MaterialComponentManager::ComponentHandle::Release(IPropertyHandle* handle) const
1010 {
1011     if (handle) {
1012         auto owner = handle->Owner();
1013         if (owner == this) {
1014             auto* componentHandle = static_cast<ComponentHandle*>(handle);
1015             if (auto id = manager_->GetComponentId(componentHandle->entity_);
1016                 id != IComponentManager::INVALID_COMPONENT_ID) {
1017                 if (manager_->GetData(id) == componentHandle) {
1018                     // This is one of the components (bound to an entity) so do nothing
1019                     return;
1020                 }
1021             }
1022             delete componentHandle;
1023         } else if (owner) {
1024             owner->Release(handle);
1025         }
1026     }
1027 }
1028 
1029 // ComponentHandle
operator =(const MaterialComponentManager::ComponentHandle & other)1030 MaterialComponentManager::ComponentHandle& MaterialComponentManager::ComponentHandle::operator=(
1031     const MaterialComponentManager::ComponentHandle& other)
1032 {
1033     if (this != &other) {
1034         // copy the material component, but..
1035         data_ = other.data_;
1036         // ..forget the custom property values and create new ones
1037         data_.customProperties = nullptr;
1038 
1039         if (manager_ == other.manager_) {
1040             // for the same manager instance we can use the same metadata
1041             cachedShader_ = other.cachedShader_;
1042             metaData_ = other.metaData_;
1043         } else {
1044             // for a different manager instance find metadata matching the shader or create new metadata
1045             if (!manager_->renderHandleManager_) {
1046                 manager_->renderHandleManager_ = GetManager<IRenderHandleComponentManager>(manager_->ecs_);
1047             }
1048 
1049             if (manager_->renderHandleManager_) {
1050                 auto currentShader =
1051                     manager_->renderHandleManager_->GetRenderHandleReference(data_.materialShader.shader);
1052                 const auto frameIndex = currentShader ? manager_->shaderManager_.GetFrameIndex(currentShader) : 0U;
1053                 const bool updatedShader = other.cachedShader_.frameIndex != frameIndex;
1054                 cachedShader_ = { currentShader.GetHandle(), frameIndex };
1055 
1056                 if (auto pos = manager_->properties_.find(cachedShader_.shader);
1057                     (pos != manager_->properties_.end()) && !updatedShader) {
1058                     metaData_ = pos->second;
1059                 } else {
1060                     // start with a blank property collection
1061                     auto newMetaData = refcnt_ptr<ReferencedProperties>(new ReferencedProperties);
1062                     auto& newProperties = newMetaData->properties;
1063 
1064                     // insert basic properties from the component
1065                     newProperties.insert(newProperties.end(), componentMetaData_.begin(), componentMetaData_.end());
1066                     const auto basicProperties = newProperties.size();
1067 
1068                     // go through the metadata which should be an array of metadata for different component types
1069                     if (auto* metaJson = manager_->shaderManager_.GetMaterialMetadata(currentShader);
1070                         metaJson && metaJson->is_array()) {
1071                         if (auto* material = FindMaterialComponentJson(*metaJson); material) {
1072                             // insert properties for textures
1073                             AppendProperties(newProperties, newMetaData->stringStorage, *material, componentMetaData_);
1074                         }
1075                     }
1076 
1077                     // if the material had metadata with customized TextureInfos try to map old locations to new ones.
1078                     if (updatedShader && other.metaData_ && (other.metaData_->properties.size() > basicProperties) &&
1079                         (newProperties.size() > basicProperties)) {
1080                         auto& oldProperties = other.metaData_->properties;
1081                         MapTextureSlots(data_.textures, offsetof(MaterialComponent, textures),
1082                             array_view(newProperties.cbegin().ptr() + basicProperties, newProperties.cend().ptr()),
1083                             array_view(oldProperties.cbegin().ptr() + basicProperties, oldProperties.cend().ptr()));
1084                     }
1085 
1086                     // replace previous properties with the new updated properties in the cache and this component
1087                     // handle
1088                     manager_->properties_.insert_or_assign(currentShader.GetHandle(), newMetaData);
1089                     metaData_ = move(newMetaData);
1090                 }
1091             }
1092         }
1093 
1094         // find the custom properties for the shader
1095         const CORE_NS::json::value* propertiesJson = nullptr;
1096         if (data_.materialShader.shader) {
1097             if (!manager_->renderHandleManager_) {
1098                 manager_->renderHandleManager_ = GetManager<IRenderHandleComponentManager>(manager_->ecs_);
1099             }
1100 
1101             if (manager_->renderHandleManager_) {
1102                 auto currentShader =
1103                     manager_->renderHandleManager_->GetRenderHandleReference(data_.materialShader.shader);
1104 
1105                 if (auto* metaJson = manager_->shaderManager_.GetMaterialMetadata(currentShader);
1106                     metaJson && metaJson->is_array()) {
1107                     if (auto* material = FindMaterialComponentJson(*metaJson); material) {
1108                         if (auto* propJson = material->find("customProperties"); propJson && propJson->is_array()) {
1109                             propertiesJson = propJson;
1110                         }
1111                     }
1112                 }
1113             }
1114         }
1115 
1116         if (metaData_ && propertiesJson) {
1117             // create and fill new POD container
1118             auto newPod = BASE_NS::make_unique<CustomPropertyPodContainer>(CUSTOM_PROPERTY_POD_CONTAINER_BYTE_SIZE);
1119             UpdateCustomPropertyMetadata(*propertiesJson, *newPod);
1120 
1121             if (other.custom_) {
1122                 newPod->CopyValues(*other.custom_);
1123             }
1124             custom_ = BASE_NS::move(newPod);
1125             data_.customProperties = custom_.get();
1126         } else if (!metaData_) {
1127             if (auto pos = manager_->properties_.find(RenderHandle {}); pos != manager_->properties_.cend()) {
1128                 metaData_ = pos->second;
1129             }
1130         }
1131 
1132         ++generation_;
1133         dirty_ = true;
1134         manager_->Updated(entity_);
1135     }
1136 
1137     return *this;
1138 }
1139 
UpdateMetadata() const1140 void MaterialComponentManager::ComponentHandle::UpdateMetadata() const
1141 {
1142     if (!manager_->renderHandleManager_) {
1143         manager_->renderHandleManager_ = GetManager<IRenderHandleComponentManager>(manager_->ecs_);
1144         if (!manager_->renderHandleManager_) {
1145             return;
1146         }
1147     }
1148     const auto currentShader = manager_->renderHandleManager_->GetRenderHandleReference(data_.materialShader.shader);
1149     const auto frameIndex = manager_->shaderManager_.GetFrameIndex(currentShader);
1150     const bool newShader = (cachedShader_.shader != currentShader.GetHandle());
1151     const bool updatedShader = !newShader && (cachedShader_.frameIndex != frameIndex);
1152     if (!metaData_ || newShader || updatedShader) {
1153         cachedShader_ = { currentShader.GetHandle(), frameIndex };
1154 
1155         const CORE_NS::json::value* propertiesJson = nullptr;
1156         auto& propertyCache = manager_->properties_;
1157         if ((newShader || updatedShader) && currentShader) {
1158             // start with a blank property collection
1159             auto newMetaData = refcnt_ptr<ReferencedProperties>(new ReferencedProperties);
1160             auto& newProperties = newMetaData->properties;
1161 
1162             // insert basic properties from the component
1163             newProperties.insert(newProperties.end(), componentMetaData_.begin(), componentMetaData_.end());
1164             const auto basicProperties = newProperties.size();
1165 
1166             // go through the metadata which should be an array of metadata for different component types
1167             if (auto* metaJson = manager_->shaderManager_.GetMaterialMetadata(currentShader);
1168                 metaJson && metaJson->is_array()) {
1169                 if (auto* material = FindMaterialComponentJson(*metaJson); material) {
1170                     // insert properties for textures
1171                     AppendProperties(newProperties, newMetaData->stringStorage, *material, componentMetaData_);
1172 
1173                     if (auto* propJson = material->find("customProperties"); propJson && propJson->is_array()) {
1174                         propertiesJson = propJson;
1175                     }
1176                 }
1177             }
1178             // if the material had metadata with customized TextureInfos try to map old locations to new ones.
1179             if (updatedShader && metaData_ && (metaData_->properties.size() > basicProperties) &&
1180                 (newProperties.size() > basicProperties)) {
1181                 auto& oldProperties = metaData_->properties;
1182                 MapTextureSlots(data_.textures, offsetof(MaterialComponent, textures),
1183                     array_view(newProperties.cbegin().ptr() + basicProperties, newProperties.cend().ptr()),
1184                     array_view(oldProperties.cbegin().ptr() + basicProperties, oldProperties.cend().ptr()));
1185             }
1186 
1187             // replace previous properties with the new updated properties in the cache and this component handle
1188             propertyCache.insert_or_assign(currentShader.GetHandle(), newMetaData);
1189             metaData_ = move(newMetaData);
1190         } else if (auto cached = propertyCache.find(cachedShader_.shader); cached != propertyCache.end()) {
1191             metaData_ = cached->second;
1192         }
1193 
1194         data_.customProperties = nullptr;
1195         if (metaData_ && propertiesJson) {
1196             // create and fill new POD container
1197             auto newPod = BASE_NS::make_unique<CustomPropertyPodContainer>(CUSTOM_PROPERTY_POD_CONTAINER_BYTE_SIZE);
1198             UpdateCustomPropertyMetadata(*propertiesJson, *newPod);
1199 
1200             if (custom_ && updatedShader) {
1201                 newPod->CopyValues(*custom_);
1202             }
1203             custom_ = BASE_NS::move(newPod);
1204             data_.customProperties = custom_.get();
1205         } else if (!metaData_) {
1206             if (auto pos = manager_->properties_.find(RenderHandle {}); pos != manager_->properties_.cend()) {
1207                 metaData_ = pos->second;
1208             }
1209         }
1210     }
1211 }
1212 
IMaterialComponentManagerInstance(IEcs & ecs)1213 IComponentManager* IMaterialComponentManagerInstance(IEcs& ecs)
1214 {
1215     return new MaterialComponentManager(ecs);
1216 }
1217 
IMaterialComponentManagerDestroy(IComponentManager * instance)1218 void IMaterialComponentManagerDestroy(IComponentManager* instance)
1219 {
1220     delete static_cast<MaterialComponentManager*>(instance);
1221 }
1222 CORE3D_END_NAMESPACE()
1223