diff --git a/example/assets/compiled.mat b/example/assets/compiled.mat new file mode 100644 index 00000000..1addeedc Binary files /dev/null and b/example/assets/compiled.mat differ diff --git a/example/assets/ubershader_gpu.mat.in b/example/assets/ubershader_gpu.mat.in new file mode 100644 index 00000000..235c791f --- /dev/null +++ b/example/assets/ubershader_gpu.mat.in @@ -0,0 +1,185 @@ +material { + name : ubershader_gpu, + requires : [ uv0, uv1, color ], + shadingModel : lit, + blending : transparent, + depthWrite : true, + doubleSided : true, + flipUV : false, + specularAmbientOcclusion : simple, + specularAntiAliasing : true, + clearCoatIorChange : false, + vertexDomain: world, + parameters : [ + + { type : float3, name : specularFactor }, + { type : float, name : glossinessFactor }, + + // Base Color + { type : int, name : baseColorIndex }, + { type : float4, name : baseColorFactor }, + { type : sampler2d, name : baseColorMap }, + { type : mat3, name : baseColorUvMatrix, precision: high }, + + // Metallic-Roughness Map + { type : int, name : metallicRoughnessIndex }, + { type : float, name : metallicFactor }, + { type : float, name : roughnessFactor }, + { type : sampler2d, name : metallicRoughnessMap }, + { type : mat3, name : metallicRoughnessUvMatrix, precision: high }, + + // Normal Map + { type : int, name : normalIndex }, + { type : float, name : normalScale }, + { type : sampler2d, name : normalMap }, + { type : mat3, name : normalUvMatrix, precision: high }, + + // Ambient Occlusion + { type : int, name : aoIndex }, + { type : float, name : aoStrength }, + { type : sampler2d, name : occlusionMap }, + { type : mat3, name : occlusionUvMatrix, precision: high }, + + // Emissive Map + { type : int, name : emissiveIndex }, + { type : float3, name : emissiveFactor }, + { type : sampler2d, name : emissiveMap }, + { type : mat3, name : emissiveUvMatrix, precision: high }, + + // Cleat coat + { type : float, name : clearCoatFactor }, + { type : float, name : clearCoatRoughnessFactor }, + { type : int, name : clearCoatIndex }, + { type : sampler2d, name : clearCoatMap }, + { type : mat3, name : clearCoatUvMatrix, precision: high }, + { type : int, name : clearCoatRoughnessIndex }, + { type : sampler2d, name : clearCoatRoughnessMap }, + { type : mat3, name : clearCoatRoughnessUvMatrix, precision: high }, + { type : int, name : clearCoatNormalIndex }, + { type : sampler2d, name : clearCoatNormalMap }, + { type : mat3, name : clearCoatNormalUvMatrix, precision: high }, + { type : float, name : clearCoatNormalScale }, + + // Reflectance + { type : float, name : reflectance }, + { type : int3, name: dimensions }, + { type : float[255], name:"morphTargetWeights" }, + { type: sampler2dArray, format: float, name:"morphTargets" } + ], +} + +fragment { + void material(inout MaterialInputs material) { + highp float2 uvs[2]; + uvs[0] = getUV0(); + uvs[1] = getUV1(); + + if (materialParams.normalIndex > -1) { + highp float2 uv = uvs[materialParams.normalIndex]; + uv = (vec3(uv, 1.0) * materialParams.normalUvMatrix).xy; + material.normal = texture(materialParams_normalMap, uv).xyz * 2.0 - 1.0; + material.normal.xy *= materialParams.normalScale; + } + if (materialParams.clearCoatNormalIndex > -1) { + highp float2 uv = uvs[materialParams.clearCoatNormalIndex]; + uv = (vec3(uv, 1.0) * materialParams.clearCoatNormalUvMatrix).xy; + material.clearCoatNormal = texture(materialParams_clearCoatNormalMap, uv).xyz * 2.0 - 1.0; + material.clearCoatNormal.xy *= materialParams.clearCoatNormalScale; + } + + prepareMaterial(material); + material.baseColor = materialParams.baseColorFactor; + + if (materialParams.baseColorIndex > -1) { + highp float2 uv = uvs[materialParams.baseColorIndex]; + uv = (vec3(uv, 1.0) * materialParams.baseColorUvMatrix).xy; + material.baseColor *= texture(materialParams_baseColorMap, uv); + } + + material.baseColor *= getColor(); + material.baseColor.rgb *= material.baseColor.a; + + + material.roughness = materialParams.roughnessFactor; + material.metallic = materialParams.metallicFactor; + + // KHR_materials_clearcoat forbids clear coat from + // being applied in the specular/glossiness model + material.clearCoat = materialParams.clearCoatFactor; + material.clearCoatRoughness = materialParams.clearCoatRoughnessFactor; + + if (materialParams.clearCoatIndex > -1) { + highp float2 uv = uvs[materialParams.clearCoatIndex]; + uv = (vec3(uv, 1.0) * materialParams.clearCoatUvMatrix).xy; + material.clearCoat *= texture(materialParams_clearCoatMap, uv).r; + } + if (materialParams.clearCoatRoughnessIndex > -1) { + highp float2 uv = uvs[materialParams.clearCoatRoughnessIndex]; + uv = (vec3(uv, 1.0) * materialParams.clearCoatRoughnessUvMatrix).xy; + material.clearCoatRoughness *= texture(materialParams_clearCoatRoughnessMap, uv).g; + } + + material.emissive = vec4(materialParams.emissiveFactor.rgb, 0.0); + + if (materialParams.metallicRoughnessIndex > -1) { + highp float2 uv = uvs[materialParams.metallicRoughnessIndex]; + uv = (vec3(uv, 1.0) * materialParams.metallicRoughnessUvMatrix).xy; + + vec4 mr = texture(materialParams_metallicRoughnessMap, uv); + material.roughness *= mr.g; + material.metallic *= mr.b; + } + + if (materialParams.aoIndex > -1) { + highp float2 uv = uvs[materialParams.aoIndex]; + uv = (vec3(uv, 1.0) * materialParams.occlusionUvMatrix).xy; + material.ambientOcclusion = texture(materialParams_occlusionMap, uv).r * + materialParams.aoStrength; + } + if (materialParams.emissiveIndex > -1) { + highp float2 uv = uvs[materialParams.emissiveIndex]; + uv = (vec3(uv, 1.0) * materialParams.emissiveUvMatrix).xy; + material.emissive.rgb *= texture(materialParams_emissiveMap, uv).rgb; + } + } +} +vertex { + vec3 getMorphTarget(int vertexIndex, int morphTargetIndex) { + // our texture is laid out as (x,y,z) where y is 1, z is the number of morph targets, and x is the number of vertices * 2 (multiplication accounts for position + normal) + // UV coordinates are normalized to (-1,1), so we divide the current vertex index by the total number of vertices to find the correct coordinate for this vertex + vec3 uv = vec3( + (float(vertexIndex) + 0.5) / float(materialParams.dimensions.x), + 0.0f, + //(float(morphTargetIndex) + 0.5f) / float(materialParams.dimensions.z)); + float(morphTargetIndex)); + return texture(materialParams_morphTargets, uv).xyz; + } + + void materialVertex(inout MaterialVertexInputs material) { + // for every morph target + for(int morphTargetIndex = 0; morphTargetIndex < materialParams.dimensions.z; morphTargetIndex++) { + + // get the weight to apply + float weight = materialParams.morphTargetWeights[morphTargetIndex]; + + // get the ID of this vertex, which will be the x-offset of the position attribute in the texture sampler + int vertexId = getVertexIndex(); + + // get the position of the target for this vertex + vec3 morphTargetPosition = getMorphTarget(vertexId, morphTargetIndex); + // update the world position of this vertex + material.worldPosition.xyz += (weight * morphTargetPosition); + + // increment the vertexID by half the size of the texture to get the x-offset of the normal (all positions stored in the first half, all normals stored in the second half) + + vertexId += (materialParams.dimensions.x / 2); + + // get the normal of this target for this vertex + vec3 morphTargetNormal = getMorphTarget(vertexId, morphTargetIndex); + material.worldNormal += (weight * morphTargetNormal); + } + mat4 transform = getWorldFromModelMatrix(); + material.worldPosition = mulMat4x4Float3(transform, material.worldPosition.xyz); + } +} + diff --git a/example/lib/main.dart b/example/lib/main.dart index b029fad2..8798e189 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'dart:async'; @@ -19,6 +21,10 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { final FilamentController _filamentController = MimeticFilamentController(); + double _zoomValue = 0; + int _primitiveIndex = 0; + double _weight = 0.0; + @override void initState() { super.initState(); @@ -38,7 +44,6 @@ class _MyAppState extends State { details.localPosition.dx, details.localPosition.dy); }, onPanUpdate: (details) { - print(details.localPosition.dx); _filamentController.panUpdate( details.localPosition.dx, details.localPosition.dy); }, @@ -51,22 +56,78 @@ class _MyAppState extends State { ElevatedButton( child: Text("initialize"), onPressed: () { - _filamentController.initialize(); - }), - ElevatedButton( - child: Text("load skybox"), - onPressed: () { + _filamentController.initialize( + materialPath: "assets/compiled.mat"); _filamentController.loadSkybox( "assets/default_env/default_env_skybox.ktx", "assets/default_env/default_env_ibl.ktx"); - }), - ElevatedButton( - child: Text("load gltf"), - onPressed: () { _filamentController.loadGltf( - "assets/BusterDrone/scene.gltf", - "assets/BusterDrone"); + "assets/guy.gltf", "assets", "Material"); + _filamentController.createMorpher( + "CC_Base_Body.003", "CC_Base_Body.003", + materialName: "Material"); }), + // ElevatedButton( + // child: Text("load skybox"), + // onPressed: () { + // _filamentController.loadSkybox( + // "assets/default_env/default_env_skybox.ktx", + // "assets/default_env/default_env_ibl.ktx"); + // }), + // ElevatedButton( + // child: Text("load gltf"), + // onPressed: () { + // _filamentController.loadGltf( + // "assets/guy.gltf", "assets", "Material"); + // }), + // ElevatedButton( + // child: Text("create morpher"), + // onPressed: () { + // _filamentController.createMorpher( + // "CC_Base_Body.003", "CC_Base_Body.003", + // materialName: "Material"); + // }), + Row(children: [ + Container( + padding: EdgeInsets.all(10), + color: Colors.white, + child: Text(_primitiveIndex.toString())), + ElevatedButton( + child: Text("+"), + onPressed: () { + setState(() { + _primitiveIndex = min(_primitiveIndex + 1, 5); + }); + }), + ElevatedButton( + child: Text("-"), + onPressed: () { + setState(() { + _primitiveIndex = max(_primitiveIndex - 1, 0); + }); + }), + ]), + Slider( + min: 0, + max: 1, + divisions: 10, + value: _weight, + onChanged: (v) { + setState(() { + _weight = v; + _filamentController.applyWeights( + List.filled(255, _weight), _primitiveIndex.toInt()); + print("Set $_primitiveIndex to $_weight"); + }); + }), + Row(children: [ + ElevatedButton( + onPressed: () => _filamentController.zoom(1.0), + child: Text("+")), + ElevatedButton( + onPressed: () => _filamentController.zoom(-1.0), + child: Text("-")) + ]) ]), ])), ), diff --git a/example/pubspec.yaml b/example/pubspec.yaml index aa7dcf95..81bbb135 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -47,6 +47,7 @@ flutter: - assets/BusterDrone/textures/ - assets/FlightHelmet/ - assets/FlightHelmet/textures/ + - assets/textures/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware. diff --git a/ios/Classes/filament/FilamentMethodCallHandler.mm b/ios/Classes/filament/FilamentMethodCallHandler.mm index bed066a1..00d0e704 100644 --- a/ios/Classes/filament/FilamentMethodCallHandler.mm +++ b/ios/Classes/filament/FilamentMethodCallHandler.mm @@ -42,7 +42,10 @@ static void* freeResourceGlobal(void* mem, size_t size, void* misc) { - (void)handleMethodCall:(FlutterMethodCall* _Nonnull)call result:(FlutterResult _Nonnull )result { if([@"initialize" isEqualToString:call.method]) { - _viewer = new mimetic::FilamentViewer(_layer, loadResourceGlobal, freeResourceGlobal); + if(!call.arguments) + _viewer = new mimetic::FilamentViewer(_layer, nullptr, loadResourceGlobal, freeResourceGlobal); + else + _viewer = new mimetic::FilamentViewer(_layer, [call.arguments UTF8String], loadResourceGlobal, freeResourceGlobal); [_controller setViewer:_viewer]; [_controller startDisplayLink]; result(@"OK"); @@ -54,7 +57,7 @@ static void* freeResourceGlobal(void* mem, size_t size, void* misc) { } else if([@"loadGltf" isEqualToString:call.method]) { if(!_viewer) return; - _viewer->loadGltf([call.arguments[0] UTF8String], [call.arguments[1] UTF8String]); + _viewer->loadGltf([call.arguments[0] UTF8String], [call.arguments[1] UTF8String], [call.arguments[2] UTF8String]); result(@"OK"); } else if([@"panStart" isEqualToString:call.method]) { if(!_viewer) @@ -68,6 +71,25 @@ static void* freeResourceGlobal(void* mem, size_t size, void* misc) { if(!_viewer) return; _viewer->manipulator->grabEnd(); + } else if([@"createMorpher" isEqualToString:call.method]) { + _viewer->createMorpher([call.arguments[0] UTF8String], [call.arguments[1] UTF8String],[call.arguments[2] UTF8String]); + } else if([@"applyWeights" isEqualToString:call.method]) { + if(!_viewer) + return; + NSArray* nWeights = call.arguments[0]; + NSNumber* nPrimitiveIndex = call.arguments[1]; + int primitiveIndex = [nPrimitiveIndex intValue]; + + int count = [nWeights count]; + float weights[count]; + for(int i=0; i < count; i++) { + weights[i] = [nWeights[i] floatValue]; + } + _viewer->morphHelper->applyWeights(weights, count, primitiveIndex); + } else if([@"zoom" isEqualToString:call.method]) { + if(!_viewer) + return; + _viewer->manipulator->scroll(0.0f, 0.0f, [call.arguments floatValue]); } else { result(FlutterMethodNotImplemented); } diff --git a/ios/include/trie/array-hash/array_growth_policy.h b/ios/include/trie/array-hash/array_growth_policy.h new file mode 100644 index 00000000..641e0cb7 --- /dev/null +++ b/ios/include/trie/array-hash/array_growth_policy.h @@ -0,0 +1,307 @@ +/** + * MIT License + * + * Copyright (c) 2017 Thibaut Goetghebuer-Planchon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ARRAY_GROWTH_POLICY_H +#define TSL_ARRAY_GROWTH_POLICY_H + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef __EXCEPTIONS +# define THROW(_e, _m) throw _e(_m) +#else +# include +# ifndef NDEBUG +# define THROW(_e, _m) do { fprintf(stderr, _m); std::terminate(); } while(0) +# else +# define THROW(_e, _m) std::terminate() +# endif +#endif + + +namespace tsl { +namespace ah { + +/** + * Grow the hash table by a factor of GrowthFactor keeping the bucket count to a power of two. It allows + * the table to use a mask operation instead of a modulo operation to map a hash to a bucket. + * + * GrowthFactor must be a power of two >= 2. + */ +template +class power_of_two_growth_policy { +public: + /** + * Called on the hash table creation and on rehash. The number of buckets for the table is passed in parameter. + * This number is a minimum, the policy may update this value with a higher value if needed (but not lower). + * + * If 0 is given, min_bucket_count_in_out must still be 0 after the policy creation and + * bucket_for_hash must always return 0 in this case. + */ + explicit power_of_two_growth_policy(std::size_t& min_bucket_count_in_out) { + if(min_bucket_count_in_out > max_bucket_count()) { + THROW(std::length_error, "The hash table exceeds its maximum size."); + } + + if(min_bucket_count_in_out > 0) { + min_bucket_count_in_out = round_up_to_power_of_two(min_bucket_count_in_out); + m_mask = min_bucket_count_in_out - 1; + } + else { + m_mask = 0; + } + } + + /** + * Return the bucket [0, bucket_count()) to which the hash belongs. + * If bucket_count() is 0, it must always return 0. + */ + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash & m_mask; + } + + /** + * Return the number of buckets that should be used on next growth. + */ + std::size_t next_bucket_count() const { + if((m_mask + 1) > max_bucket_count() / GrowthFactor) { + THROW(std::length_error, "The hash table exceeds its maximum size."); + } + + return (m_mask + 1) * GrowthFactor; + } + + /** + * Return the maximum number of buckets supported by the policy. + */ + std::size_t max_bucket_count() const { + // Largest power of two. + return (std::numeric_limits::max() / 2) + 1; + } + + /** + * Reset the growth policy as if it was created with a bucket count of 0. + * After a clear, the policy must always return 0 when bucket_for_hash is called. + */ + void clear() noexcept { + m_mask = 0; + } + +private: + static std::size_t round_up_to_power_of_two(std::size_t value) { + if(is_power_of_two(value)) { + return value; + } + + if(value == 0) { + return 1; + } + + --value; + for(std::size_t i = 1; i < sizeof(std::size_t) * CHAR_BIT; i *= 2) { + value |= value >> i; + } + + return value + 1; + } + + static constexpr bool is_power_of_two(std::size_t value) { + return value != 0 && (value & (value - 1)) == 0; + } + +protected: + static_assert(is_power_of_two(GrowthFactor) && GrowthFactor >= 2, "GrowthFactor must be a power of two >= 2."); + + std::size_t m_mask; +}; + + +/** + * Grow the hash table by GrowthFactor::num / GrowthFactor::den and use a modulo to map a hash + * to a bucket. Slower but it can be useful if you want a slower growth. + */ +template> +class mod_growth_policy { +public: + explicit mod_growth_policy(std::size_t& min_bucket_count_in_out) { + if(min_bucket_count_in_out > max_bucket_count()) { + THROW(std::length_error, "The hash table exceeds its maximum size."); + } + + if(min_bucket_count_in_out > 0) { + m_mod = min_bucket_count_in_out; + } + else { + m_mod = 1; + } + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return hash % m_mod; + } + + std::size_t next_bucket_count() const { + if(m_mod == max_bucket_count()) { + THROW(std::length_error, "The hash table exceeds its maximum size."); + } + + const double next_bucket_count = std::ceil(double(m_mod) * REHASH_SIZE_MULTIPLICATION_FACTOR); + if(!std::isnormal(next_bucket_count)) { + THROW(std::length_error, "The hash table exceeds its maximum size."); + } + + if(next_bucket_count > double(max_bucket_count())) { + return max_bucket_count(); + } + else { + return std::size_t(next_bucket_count); + } + } + + std::size_t max_bucket_count() const { + return MAX_BUCKET_COUNT; + } + + void clear() noexcept { + m_mod = 1; + } + +private: + static constexpr double REHASH_SIZE_MULTIPLICATION_FACTOR = 1.0 * GrowthFactor::num / GrowthFactor::den; + static const std::size_t MAX_BUCKET_COUNT = + std::size_t(double( + std::numeric_limits::max() / REHASH_SIZE_MULTIPLICATION_FACTOR + )); + + static_assert(REHASH_SIZE_MULTIPLICATION_FACTOR >= 1.1, "Growth factor should be >= 1.1."); + + std::size_t m_mod; +}; + + + +namespace detail { + +static constexpr const std::array PRIMES = {{ + 1ul, 5ul, 17ul, 29ul, 37ul, 53ul, 67ul, 79ul, 97ul, 131ul, 193ul, 257ul, 389ul, 521ul, 769ul, 1031ul, + 1543ul, 2053ul, 3079ul, 6151ul, 12289ul, 24593ul, 49157ul, 98317ul, 196613ul, 393241ul, 786433ul, + 1572869ul, 3145739ul, 6291469ul, 12582917ul, 25165843ul, 50331653ul, 100663319ul, 201326611ul, + 402653189ul, 805306457ul, 1610612741ul, 3221225473ul, 4294967291ul +}}; + +template +static constexpr std::size_t mod(std::size_t hash) { return hash % PRIMES[IPrime]; } + +// MOD_PRIME[iprime](hash) returns hash % PRIMES[iprime]. This table allows for faster modulo as the +// compiler can optimize the modulo code better with a constant known at the compilation. +static constexpr const std::array MOD_PRIME = {{ + &mod<0>, &mod<1>, &mod<2>, &mod<3>, &mod<4>, &mod<5>, &mod<6>, &mod<7>, &mod<8>, &mod<9>, &mod<10>, + &mod<11>, &mod<12>, &mod<13>, &mod<14>, &mod<15>, &mod<16>, &mod<17>, &mod<18>, &mod<19>, &mod<20>, + &mod<21>, &mod<22>, &mod<23>, &mod<24>, &mod<25>, &mod<26>, &mod<27>, &mod<28>, &mod<29>, &mod<30>, + &mod<31>, &mod<32>, &mod<33>, &mod<34>, &mod<35>, &mod<36>, &mod<37> , &mod<38>, &mod<39> +}}; + +} + +/** + * Grow the hash table by using prime numbers as bucket count. Slower than tsl::ah::power_of_two_growth_policy in + * general but will probably distribute the values around better in the buckets with a poor hash function. + * + * To allow the compiler to optimize the modulo operation, a lookup table is used with constant primes numbers. + * + * With a switch the code would look like: + * \code + * switch(iprime) { // iprime is the current prime of the hash table + * case 0: hash % 5ul; + * break; + * case 1: hash % 17ul; + * break; + * case 2: hash % 29ul; + * break; + * ... + * } + * \endcode + * + * Due to the constant variable in the modulo the compiler is able to optimize the operation + * by a series of multiplications, substractions and shifts. + * + * The 'hash % 5' could become something like 'hash - (hash * 0xCCCCCCCD) >> 34) * 5' in a 64 bits environment. + */ +class prime_growth_policy { +public: + explicit prime_growth_policy(std::size_t& min_bucket_count_in_out) { + auto it_prime = std::lower_bound(detail::PRIMES.begin(), + detail::PRIMES.end(), min_bucket_count_in_out); + if(it_prime == detail::PRIMES.end()) { + THROW(std::length_error, "The hash table exceeds its maximum size."); + } + + m_iprime = static_cast(std::distance(detail::PRIMES.begin(), it_prime)); + if(min_bucket_count_in_out > 0) { + min_bucket_count_in_out = *it_prime; + } + else { + min_bucket_count_in_out = 0; + } + } + + std::size_t bucket_for_hash(std::size_t hash) const noexcept { + return detail::MOD_PRIME[m_iprime](hash); + } + + std::size_t next_bucket_count() const { + if(m_iprime + 1 >= detail::PRIMES.size()) { + THROW(std::length_error, "The hash table exceeds its maximum size."); + } + + return detail::PRIMES[m_iprime + 1]; + } + + std::size_t max_bucket_count() const { + return detail::PRIMES.back(); + } + + void clear() noexcept { + m_iprime = 0; + } + +private: + unsigned int m_iprime; + + static_assert(std::numeric_limits::max() >= detail::PRIMES.size(), + "The type of m_iprime is not big enough."); +}; + +} +} + +#endif diff --git a/ios/include/trie/array-hash/array_hash.h b/ios/include/trie/array-hash/array_hash.h new file mode 100644 index 00000000..ccb204ca --- /dev/null +++ b/ios/include/trie/array-hash/array_hash.h @@ -0,0 +1,1766 @@ +/** + * MIT License + * + * Copyright (c) 2017 Thibaut Goetghebuer-Planchon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ARRAY_HASH_H +#define TSL_ARRAY_HASH_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "array_growth_policy.h" + + +/* + * __has_include is a bit useless (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=79433), + * check also __cplusplus version. + */ +#ifdef __has_include +# if __has_include() && __cplusplus >= 201703L +# define TSL_AH_HAS_STRING_VIEW +# endif +#endif + + +#ifdef TSL_AH_HAS_STRING_VIEW +# include +#endif + + +#ifdef TSL_DEBUG +# define tsl_ah_assert(expr) assert(expr) +#else +# define tsl_ah_assert(expr) (static_cast(0)) +#endif + + + +/** + * Implementation of the array hash structure described in the + * "Cache-conscious collision resolution in string hash tables." (Askitis Nikolas and Justin Zobel, 2005) paper. + */ +namespace tsl { + +namespace ah { + +template +struct str_hash { +#ifdef TSL_AH_HAS_STRING_VIEW + std::size_t operator()(const CharT* key, std::size_t key_size) const { + return std::hash>()(std::basic_string_view(key, key_size)); + } +#else + /** + * FNV-1a hash + */ + std::size_t operator()(const CharT* key, std::size_t key_size) const { + static const std::size_t init = std::size_t((sizeof(std::size_t) == 8)?0xcbf29ce484222325:0x811c9dc5); + static const std::size_t multiplier = std::size_t((sizeof(std::size_t) == 8)?0x100000001b3:0x1000193); + + std::size_t hash = init; + for (std::size_t i = 0; i < key_size; ++i) { + hash ^= key[i]; + hash *= multiplier; + } + + return hash; + } +#endif +}; + +template +struct str_equal { + bool operator()(const CharT* key_lhs, std::size_t key_size_lhs, + const CharT* key_rhs, std::size_t key_size_rhs) const + { + if(key_size_lhs != key_size_rhs) { + return false; + } + else { + return std::memcmp(key_lhs, key_rhs, key_size_lhs * sizeof(CharT)) == 0; + } + } +}; +} + + +namespace detail_array_hash { + +template +struct is_iterator: std::false_type { +}; + +template +struct is_iterator::iterator_category, void>::value>::type>: std::true_type { +}; + +static constexpr bool is_power_of_two(std::size_t value) { + return value != 0 && (value & (value - 1)) == 0; +} + +template +static T numeric_cast(U value, const char* error_message = "numeric_cast() failed.") { + T ret = static_cast(value); + if(static_cast(ret) != value) { + THROW(std::runtime_error, error_message); + } + + const bool is_same_signedness = (std::is_unsigned::value && std::is_unsigned::value) || + (std::is_signed::value && std::is_signed::value); + if(!is_same_signedness && (ret < T{}) != (value < U{})) { + THROW(std::runtime_error, error_message); + } + + return ret; +} + + + +/** + * Fixed size type used to represent size_type values on serialization. Need to be big enough + * to represent a std::size_t on 32 and 64 bits platforms, and must be the same size on both platforms. + */ +using slz_size_type = std::uint64_t; + +template +static T deserialize_value(Deserializer& deserializer) { + // MSVC < 2017 is not conformant, circumvent the problem by removing the template keyword +#if defined (_MSC_VER) && _MSC_VER < 1910 + return deserializer.Deserializer::operator()(); +#else + return deserializer.Deserializer::template operator()(); +#endif +} + +/** + * For each string in the bucket, store the size of the string, the chars of the string + * and T, if it's not void. T should be either void or an unsigned type. + * + * End the buffer with END_OF_BUCKET flag. END_OF_BUCKET has the same type as the string size variable. + * + * m_buffer (CharT*): + * | size of str1 (KeySizeT) | str1 (const CharT*) | value (T if T != void) | ... | + * | size of strN (KeySizeT) | strN (const CharT*) | value (T if T != void) | END_OF_BUCKET (KeySizeT) | + * + * m_buffer is null if there is no string in the bucket. + * + * KeySizeT and T are extended to be a multiple of CharT when stored in the buffer. + * + * Use std::malloc and std::free instead of new and delete so we can have access to std::realloc. + */ +template +class array_bucket { + template + using has_mapped_type = typename std::integral_constant::value>; + + static_assert(!has_mapped_type::value || std::is_unsigned::value, + "T should be either void or an unsigned type."); + + static_assert(std::is_unsigned::value, "KeySizeT should be an unsigned type."); + +public: + template + class array_bucket_iterator; + + using char_type = CharT; + using key_size_type = KeySizeT; + using mapped_type = T; + using size_type = std::size_t; + using key_equal = KeyEqual; + using iterator = array_bucket_iterator; + using const_iterator = array_bucket_iterator; + + static_assert(sizeof(KeySizeT) <= sizeof(size_type), "sizeof(KeySizeT) should be <= sizeof(std::size_t;)"); + static_assert(std::is_unsigned::value, ""); + +private: + /** + * Return how much space in bytes the type U will take when stored in the buffer. + * As the buffer is of type CharT, U may take more space than sizeof(U). + * + * Example: sizeof(CharT) = 4, sizeof(U) = 2 => U will take 4 bytes in the buffer instead of 2. + */ + template + static constexpr size_type sizeof_in_buff() noexcept { + static_assert(is_power_of_two(sizeof(U)), "sizeof(U) should be a power of two."); + static_assert(is_power_of_two(sizeof(CharT)), "sizeof(CharT) should be a power of two."); + + return std::max(sizeof(U), sizeof(CharT)); + } + + /** + * Same as sizeof_in_buff, but instead of returning the size in bytes return it in term of sizeof(CharT). + */ + template + static constexpr size_type size_as_char_t() noexcept { + return sizeof_in_buff() / sizeof(CharT); + } + + static key_size_type read_key_size(const CharT* buffer) noexcept { + key_size_type key_size; + std::memcpy(&key_size, buffer, sizeof(key_size)); + + return key_size; + } + + static mapped_type read_value(const CharT* buffer) noexcept { + mapped_type value; + std::memcpy(&value, buffer, sizeof(value)); + + return value; + } + + static bool is_end_of_bucket(const CharT* buffer) noexcept { + return read_key_size(buffer) == END_OF_BUCKET; + } + +public: + /** + * Return the size required for an entry with a key of size 'key_size'. + */ + template::value>::type* = nullptr> + static size_type entry_required_bytes(size_type key_size) noexcept { + return sizeof_in_buff() + (key_size + KEY_EXTRA_SIZE) * sizeof(CharT); + } + + template::value>::type* = nullptr> + static size_type entry_required_bytes(size_type key_size) noexcept { + return sizeof_in_buff() + (key_size + KEY_EXTRA_SIZE) * sizeof(CharT) + + sizeof_in_buff(); + } + +private: + /** + * Return the size of the current entry in buffer. + */ + static size_type entry_size_bytes(const CharT* buffer) noexcept { + return entry_required_bytes(read_key_size(buffer)); + } + +public: + template + class array_bucket_iterator { + friend class array_bucket; + + using buffer_type = typename std::conditional::type; + + explicit array_bucket_iterator(buffer_type* position) noexcept: m_position(position) { + } + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = void; + using difference_type = std::ptrdiff_t; + using reference = void; + using pointer = void; + + public: + array_bucket_iterator() noexcept: m_position(nullptr) { + } + + const CharT* key() const { + return m_position + size_as_char_t(); + } + + size_type key_size() const { + return read_key_size(m_position); + } + + template::value>::type* = nullptr> + U value() const { + return read_value(m_position + size_as_char_t() + key_size() + KEY_EXTRA_SIZE); + } + + + template::value && !IsConst && std::is_same::value>::type* = nullptr> + void set_value(U value) noexcept { + std::memcpy(m_position + size_as_char_t() + key_size() + KEY_EXTRA_SIZE, + &value, sizeof(value)); + } + + array_bucket_iterator& operator++() { + m_position += entry_size_bytes(m_position)/sizeof(CharT); + if(is_end_of_bucket(m_position)) { + m_position = nullptr; + } + + return *this; + } + + array_bucket_iterator operator++(int) { + array_bucket_iterator tmp(*this); + ++*this; + + return tmp; + } + + friend bool operator==(const array_bucket_iterator& lhs, const array_bucket_iterator& rhs) { + return lhs.m_position == rhs.m_position; + } + + friend bool operator!=(const array_bucket_iterator& lhs, const array_bucket_iterator& rhs) { + return !(lhs == rhs); + } + + private: + buffer_type* m_position; + }; + + + + static iterator end_it() noexcept { + return iterator(nullptr); + } + + static const_iterator cend_it() noexcept { + return const_iterator(nullptr); + } + +public: + array_bucket(): m_buffer(nullptr) { + } + + /** + * Reserve 'size' in the buffer of the bucket. The created bucket is empty. + */ + array_bucket(std::size_t size): m_buffer(nullptr) { + if(size == 0) { + return; + } + + m_buffer = static_cast(std::malloc(size*sizeof(CharT) + sizeof_in_buff())); + if(m_buffer == nullptr) { + THROW(std::runtime_error, "Out of memory"); + } + + const auto end_of_bucket = END_OF_BUCKET; + std::memcpy(m_buffer, &end_of_bucket, sizeof(end_of_bucket)); + } + + ~array_bucket() { + clear(); + } + + array_bucket(const array_bucket& other) { + if(other.m_buffer == nullptr) { + m_buffer = nullptr; + return; + } + + const size_type other_buffer_size = other.size(); + m_buffer = static_cast(std::malloc(other_buffer_size*sizeof(CharT) + sizeof_in_buff())); + if(m_buffer == nullptr) { + THROW(std::runtime_error, "Out of memory"); + } + + std::memcpy(m_buffer, other.m_buffer, other_buffer_size*sizeof(CharT)); + + const auto end_of_bucket = END_OF_BUCKET; + std::memcpy(m_buffer + other_buffer_size, &end_of_bucket, sizeof(end_of_bucket)); + } + + array_bucket(array_bucket&& other) noexcept: m_buffer(other.m_buffer) { + other.m_buffer = nullptr; + } + + array_bucket& operator=(array_bucket other) noexcept { + other.swap(*this); + + return *this; + } + + void swap(array_bucket& other) noexcept { + std::swap(m_buffer, other.m_buffer); + } + + iterator begin() noexcept { return iterator(m_buffer); } + iterator end() noexcept { return iterator(nullptr); } + const_iterator begin() const noexcept { return cbegin(); } + const_iterator end() const noexcept { return cend(); } + const_iterator cbegin() const noexcept { return const_iterator(m_buffer); } + const_iterator cend() const noexcept { return const_iterator(nullptr); } + + /** + * Return an iterator pointing to the key entry if presents or, if not there, to the position + * past the last element of the bucket. Return end() if the bucket has not be initialized yet. + * + * The boolean of the pair is set to true if the key is there, false otherwise. + */ + std::pair find_or_end_of_bucket(const CharT* key, size_type key_size) const noexcept { + if(m_buffer == nullptr) { + return std::make_pair(cend(), false); + } + + const CharT* buffer_ptr_in_out = m_buffer; + const bool found = find_or_end_of_bucket_impl(key, key_size, buffer_ptr_in_out); + + return std::make_pair(const_iterator(buffer_ptr_in_out), found); + } + + /** + * Append the element 'key' with its potential value at the end of the bucket. + * 'end_of_bucket' should point past the end of the last element in the bucket, end() if the bucket + * was not initialized yet. You usually get this value from find_or_end_of_bucket. + * + * Return the position where the element was actually inserted. + */ + template + const_iterator append(const_iterator end_of_bucket, const CharT* key, size_type key_size, + ValueArgs&&... value) + { + const key_size_type key_sz = as_key_size_type(key_size); + + if(end_of_bucket == cend()) { + tsl_ah_assert(m_buffer == nullptr); + + const size_type buffer_size = entry_required_bytes(key_sz) + sizeof_in_buff(); + + m_buffer = static_cast(std::malloc(buffer_size)); + if(m_buffer == nullptr) { + THROW(std::runtime_error, "Out of memory"); + } + + append_impl(key, key_sz, m_buffer, std::forward(value)...); + + return const_iterator(m_buffer); + } + else { + tsl_ah_assert(is_end_of_bucket(end_of_bucket.m_position)); + + const size_type current_size = ((end_of_bucket.m_position + size_as_char_t()) - + m_buffer) * sizeof(CharT); + const size_type new_size = current_size + entry_required_bytes(key_sz); + + + CharT* new_buffer = static_cast(std::realloc(m_buffer, new_size)); + if(new_buffer == nullptr) { + THROW(std::runtime_error, "Out of memory"); + } + m_buffer = new_buffer; + + + CharT* buffer_append_pos = m_buffer + current_size / sizeof(CharT) - + size_as_char_t(); + append_impl(key, key_sz, buffer_append_pos, std::forward(value)...); + + return const_iterator(buffer_append_pos); + } + + } + + const_iterator erase(const_iterator position) noexcept { + tsl_ah_assert(position.m_position != nullptr && !is_end_of_bucket(position.m_position)); + + // get mutable pointers + CharT* start_entry = m_buffer + (position.m_position - m_buffer); + CharT* start_next_entry = start_entry + entry_size_bytes(start_entry) / sizeof(CharT); + + + CharT* end_buffer_ptr = start_next_entry; + while(!is_end_of_bucket(end_buffer_ptr)) { + end_buffer_ptr += entry_size_bytes(end_buffer_ptr) / sizeof(CharT); + } + end_buffer_ptr += size_as_char_t(); + + + const size_type size_to_move = (end_buffer_ptr - start_next_entry) * sizeof(CharT); + std::memmove(start_entry, start_next_entry, size_to_move); + + + if(is_end_of_bucket(m_buffer)) { + clear(); + return cend(); + } + else if(is_end_of_bucket(start_entry)) { + return cend(); + } + else { + return const_iterator(start_entry); + } + } + + /** + * Return true if an element has been erased + */ + bool erase(const CharT* key, size_type key_size) noexcept { + if(m_buffer == nullptr) { + return false; + } + + const CharT* entry_buffer_ptr_in_out = m_buffer; + bool found = find_or_end_of_bucket_impl(key, key_size, entry_buffer_ptr_in_out); + if(found) { + erase(const_iterator(entry_buffer_ptr_in_out)); + + return true; + } + else { + return false; + } + } + + /** + * Bucket should be big enough and there is no check to see if the key already exists. + * No check on key_size. + */ + template + void append_in_reserved_bucket_no_check(const CharT* key, size_type key_size, ValueArgs&&... value) noexcept { + CharT* buffer_ptr = m_buffer; + while(!is_end_of_bucket(buffer_ptr)) { + buffer_ptr += entry_size_bytes(buffer_ptr)/sizeof(CharT); + } + + append_impl(key, key_size_type(key_size), buffer_ptr, std::forward(value)...); + } + + bool empty() const noexcept { + return m_buffer == nullptr || is_end_of_bucket(m_buffer); + } + + void clear() noexcept { + std::free(m_buffer); + m_buffer = nullptr; + } + + iterator mutable_iterator(const_iterator pos) noexcept { + return iterator(m_buffer + (pos.m_position - m_buffer)); + } + + template + void serialize(Serializer& serializer) const { + const slz_size_type bucket_size = size(); + tsl_ah_assert(m_buffer != nullptr || bucket_size == 0); + + serializer(bucket_size); + serializer(m_buffer, bucket_size); + } + + template + static array_bucket deserialize(Deserializer& deserializer) { + array_bucket bucket; + const slz_size_type bucket_size_ds = deserialize_value(deserializer); + + if(bucket_size_ds == 0) { + return bucket; + } + + const std::size_t bucket_size = numeric_cast(bucket_size_ds, "Deserialized bucket_size is too big."); + bucket.m_buffer = static_cast(std::malloc(bucket_size*sizeof(CharT) + sizeof_in_buff())); + if(bucket.m_buffer == nullptr) { + THROW(std::runtime_error, "Out of memory"); + } + + + deserializer(bucket.m_buffer, bucket_size); + + const auto end_of_bucket = END_OF_BUCKET; + std::memcpy(bucket.m_buffer + bucket_size, &end_of_bucket, sizeof(end_of_bucket)); + + + tsl_ah_assert(bucket.size() == bucket_size); + return bucket; + } + +private: + key_size_type as_key_size_type(size_type key_size) const { + if(key_size > MAX_KEY_SIZE) { + THROW(std::length_error, "Key is too long."); + } + + return key_size_type(key_size); + } + + /* + * Return true if found, false otherwise. + * If true, buffer_ptr_in_out points to the start of the entry matching 'key'. + * If false, buffer_ptr_in_out points to where the 'key' should be inserted. + * + * Start search from buffer_ptr_in_out. + */ + bool find_or_end_of_bucket_impl(const CharT* key, size_type key_size, + const CharT* & buffer_ptr_in_out) const noexcept + { + while(!is_end_of_bucket(buffer_ptr_in_out)) { + const key_size_type buffer_key_size = read_key_size(buffer_ptr_in_out); + const CharT* buffer_str = buffer_ptr_in_out + size_as_char_t(); + if(KeyEqual()(buffer_str, buffer_key_size, key, key_size)) { + return true; + } + + buffer_ptr_in_out += entry_size_bytes(buffer_ptr_in_out)/sizeof(CharT); + } + + return false; + } + + template::value>::type* = nullptr> + void append_impl(const CharT* key, key_size_type key_size, CharT* buffer_append_pos) noexcept { + std::memcpy(buffer_append_pos, &key_size, sizeof(key_size)); + buffer_append_pos += size_as_char_t(); + + std::memcpy(buffer_append_pos, key, key_size * sizeof(CharT)); + buffer_append_pos += key_size; + + const CharT zero = 0; + std::memcpy(buffer_append_pos, &zero, KEY_EXTRA_SIZE * sizeof(CharT)); + buffer_append_pos += KEY_EXTRA_SIZE; + + const auto end_of_bucket = END_OF_BUCKET; + std::memcpy(buffer_append_pos, &end_of_bucket, sizeof(end_of_bucket)); + } + + template::value>::type* = nullptr> + void append_impl(const CharT* key, key_size_type key_size, CharT* buffer_append_pos, + typename array_bucket::mapped_type value) noexcept + { + std::memcpy(buffer_append_pos, &key_size, sizeof(key_size)); + buffer_append_pos += size_as_char_t(); + + std::memcpy(buffer_append_pos, key, key_size * sizeof(CharT)); + buffer_append_pos += key_size; + + const CharT zero = 0; + std::memcpy(buffer_append_pos, &zero, KEY_EXTRA_SIZE * sizeof(CharT)); + buffer_append_pos += KEY_EXTRA_SIZE; + + std::memcpy(buffer_append_pos, &value, sizeof(value)); + buffer_append_pos += size_as_char_t(); + + const auto end_of_bucket = END_OF_BUCKET; + std::memcpy(buffer_append_pos, &end_of_bucket, sizeof(end_of_bucket)); + } + + /** + * Return the number of CharT in m_buffer. As the size of the buffer is not stored to gain some space, + * the method need to find the EOF marker and is thus in O(n). + */ + size_type size() const noexcept { + if(m_buffer == nullptr) { + return 0; + } + + CharT* buffer_ptr = m_buffer; + while(!is_end_of_bucket(buffer_ptr)) { + buffer_ptr += entry_size_bytes(buffer_ptr)/sizeof(CharT); + } + + return buffer_ptr - m_buffer; + } + +private: + static const key_size_type END_OF_BUCKET = std::numeric_limits::max(); + static const key_size_type KEY_EXTRA_SIZE = StoreNullTerminator?1:0; + + CharT* m_buffer; + +public: + static const key_size_type MAX_KEY_SIZE = + // -1 for END_OF_BUCKET + key_size_type(std::numeric_limits::max() - KEY_EXTRA_SIZE - 1); +}; + + +template +class value_container { +public: + void clear() noexcept { + m_values.clear(); + } + + void reserve(std::size_t new_cap) { + m_values.reserve(new_cap); + } + + void shrink_to_fit() { + m_values.shrink_to_fit(); + } + + friend void swap(value_container& lhs, value_container& rhs) { + lhs.m_values.swap(rhs.m_values); + } + +protected: + static constexpr float VECTOR_GROWTH_RATE = 1.5f; + + // TODO use a sparse array? or a std::deque + std::vector m_values; +}; + +template<> +class value_container { +public: + void clear() noexcept { + } + + void shrink_to_fit() { + } + + void reserve(std::size_t /*new_cap*/) { + } +}; + + + +/** + * If there is no value in the array_hash (in the case of a set for example), T should be void. + * + * The size of a key string is limited to std::numeric_limits::max() - 1. + * + * The number of elements in the map is limited to std::numeric_limits::max(). + */ +template +class array_hash: private value_container, private Hash, private GrowthPolicy { +private: + template + using has_mapped_type = typename std::integral_constant::value>; + + /** + * If there is a mapped type in array_hash, we store the values in m_values of value_container class + * and we store an index to m_values in the bucket. The index is of type IndexSizeT. + */ + using array_bucket = tsl::detail_array_hash::array_bucket::value, + IndexSizeT, + void>::type, + KeyEqual, KeySizeT, StoreNullTerminator>; + +public: + template + class array_hash_iterator; + + using char_type = CharT; + using key_size_type = KeySizeT; + using index_size_type = IndexSizeT; + using size_type = std::size_t; + using hasher = Hash; + using key_equal = KeyEqual; + using iterator = array_hash_iterator; + using const_iterator = array_hash_iterator; + + +/* + * Iterator classes + */ +public: + template + class array_hash_iterator { + friend class array_hash; + + private: + using iterator_array_bucket = typename array_bucket::const_iterator; + + using iterator_buckets = typename std::conditional::const_iterator, + typename std::vector::iterator>::type; + + using array_hash_ptr = typename std::conditional::type; + + public: + using iterator_category = std::forward_iterator_tag; + using value_type = typename std::conditional::value, T, void>::type; + using difference_type = std::ptrdiff_t; + using reference = typename std::conditional::value, + typename std::conditional< + IsConst, + typename std::add_lvalue_reference::type, + typename std::add_lvalue_reference::type>::type, + void>::type; + using pointer = typename std::conditional::value, + typename std::conditional::type, + void>::type; + + + private: + array_hash_iterator(iterator_buckets buckets_iterator, iterator_array_bucket array_bucket_iterator, + array_hash_ptr array_hash_p) noexcept: + m_buckets_iterator(buckets_iterator), + m_array_bucket_iterator(array_bucket_iterator), + m_array_hash(array_hash_p) + { + tsl_ah_assert(m_array_hash != nullptr); + } + + public: + array_hash_iterator() noexcept: m_array_hash(nullptr) { + } + + template::type* = nullptr> + array_hash_iterator(const array_hash_iterator& other) noexcept : + m_buckets_iterator(other.m_buckets_iterator), + m_array_bucket_iterator(other.m_array_bucket_iterator), + m_array_hash(other.m_array_hash) + { + } + + array_hash_iterator(const array_hash_iterator& other) = default; + array_hash_iterator(array_hash_iterator&& other) = default; + array_hash_iterator& operator=(const array_hash_iterator& other) = default; + array_hash_iterator& operator=(array_hash_iterator&& other) = default; + + const CharT* key() const { + return m_array_bucket_iterator.key(); + } + + size_type key_size() const { + return m_array_bucket_iterator.key_size(); + } + +#ifdef TSL_AH_HAS_STRING_VIEW + std::basic_string_view key_sv() const { + return std::basic_string_view(key(), key_size()); + } +#endif + + template::value>::type* = nullptr> + reference value() const { + return this->m_array_hash->m_values[value_position()]; + } + + template::value>::type* = nullptr> + reference operator*() const { + return value(); + } + + template::value>::type* = nullptr> + pointer operator->() const { + return std::addressof(value()); + } + + array_hash_iterator& operator++() { + tsl_ah_assert(m_buckets_iterator != m_array_hash->m_buckets_data.end()); + tsl_ah_assert(m_array_bucket_iterator != m_buckets_iterator->cend()); + + ++m_array_bucket_iterator; + if(m_array_bucket_iterator == m_buckets_iterator->cend()) { + do { + ++m_buckets_iterator; + } while(m_buckets_iterator != m_array_hash->m_buckets_data.end() && + m_buckets_iterator->empty()); + + if(m_buckets_iterator != m_array_hash->m_buckets_data.end()) { + m_array_bucket_iterator = m_buckets_iterator->cbegin(); + } + } + + return *this; + } + + array_hash_iterator operator++(int) { + array_hash_iterator tmp(*this); + ++*this; + + return tmp; + } + + friend bool operator==(const array_hash_iterator& lhs, const array_hash_iterator& rhs) { + return lhs.m_buckets_iterator == rhs.m_buckets_iterator && + lhs.m_array_bucket_iterator == rhs.m_array_bucket_iterator && + lhs.m_array_hash == rhs.m_array_hash; + } + + friend bool operator!=(const array_hash_iterator& lhs, const array_hash_iterator& rhs) { + return !(lhs == rhs); + } + + private: + template::value>::type* = nullptr> + IndexSizeT value_position() const { + return this->m_array_bucket_iterator.value(); + } + + private: + iterator_buckets m_buckets_iterator; + iterator_array_bucket m_array_bucket_iterator; + + array_hash_ptr m_array_hash; + }; + + + +public: + array_hash(size_type bucket_count, + const Hash& hash, + float max_load_factor): value_container(), + Hash(hash), + GrowthPolicy(bucket_count), + m_buckets_data(bucket_count > max_bucket_count()? + max_bucket_count(): + bucket_count), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():m_buckets_data.data()), + m_nb_elements(0) + { + this->max_load_factor(max_load_factor); + } + + array_hash(const array_hash& other): value_container(other), + Hash(other), + GrowthPolicy(other), + m_buckets_data(other.m_buckets_data), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():m_buckets_data.data()), + m_nb_elements(other.m_nb_elements), + m_max_load_factor(other.m_max_load_factor), + m_load_threshold(other.m_load_threshold) + { + } + + array_hash(array_hash&& other) noexcept(std::is_nothrow_move_constructible>::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible::value && + std::is_nothrow_move_constructible>::value) + : value_container(std::move(other)), + Hash(std::move(other)), + GrowthPolicy(std::move(other)), + m_buckets_data(std::move(other.m_buckets_data)), + m_buckets(m_buckets_data.empty()?static_empty_bucket_ptr():m_buckets_data.data()), + m_nb_elements(other.m_nb_elements), + m_max_load_factor(other.m_max_load_factor), + m_load_threshold(other.m_load_threshold) + { + other.value_container::clear(); + other.GrowthPolicy::clear(); + other.m_buckets_data.clear(); + other.m_buckets = static_empty_bucket_ptr(); + other.m_nb_elements = 0; + other.m_load_threshold = 0; + } + + array_hash& operator=(const array_hash& other) { + if(&other != this) { + value_container::operator=(other); + Hash::operator=(other); + GrowthPolicy::operator=(other); + + m_buckets_data = other.m_buckets_data; + m_buckets = m_buckets_data.empty()?static_empty_bucket_ptr(): + m_buckets_data.data(); + m_nb_elements = other.m_nb_elements; + m_max_load_factor = other.m_max_load_factor; + m_load_threshold = other.m_load_threshold; + } + + return *this; + } + + array_hash& operator=(array_hash&& other) { + other.swap(*this); + other.clear(); + + return *this; + } + + + /* + * Iterators + */ + iterator begin() noexcept { + auto begin = m_buckets_data.begin(); + while(begin != m_buckets_data.end() && begin->empty()) { + ++begin; + } + + return (begin != m_buckets_data.end())?iterator(begin, begin->cbegin(), this):end(); + } + + const_iterator begin() const noexcept { + return cbegin(); + } + + const_iterator cbegin() const noexcept { + auto begin = m_buckets_data.cbegin(); + while(begin != m_buckets_data.cend() && begin->empty()) { + ++begin; + } + + return (begin != m_buckets_data.cend())?const_iterator(begin, begin->cbegin(), this):cend(); + } + + iterator end() noexcept { + return iterator(m_buckets_data.end(), array_bucket::cend_it(), this); + } + + const_iterator end() const noexcept { + return cend(); + } + + const_iterator cend() const noexcept { + return const_iterator(m_buckets_data.end(), array_bucket::cend_it(), this); + } + + + /* + * Capacity + */ + bool empty() const noexcept { + return m_nb_elements == 0; + } + + size_type size() const noexcept { + return m_nb_elements; + } + + size_type max_size() const noexcept { + return std::numeric_limits::max(); + } + + size_type max_key_size() const noexcept { + return MAX_KEY_SIZE; + } + + void shrink_to_fit() { + clear_old_erased_values(); + value_container::shrink_to_fit(); + + rehash_impl(size_type(std::ceil(float(size())/max_load_factor()))); + } + + /* + * Modifiers + */ + void clear() noexcept { + value_container::clear(); + + for(auto& bucket: m_buckets_data) { + bucket.clear(); + } + + m_nb_elements = 0; + } + + + + template + std::pair emplace(const CharT* key, size_type key_size, ValueArgs&&... value_args) { + const std::size_t hash = hash_key(key, key_size); + std::size_t ibucket = bucket_for_hash(hash); + + auto it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + if(it_find.second) { + return std::make_pair(iterator(m_buckets_data.begin() + ibucket, it_find.first, this), false); + } + + if(grow_on_high_load()) { + ibucket = bucket_for_hash(hash); + it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + } + + return emplace_impl(ibucket, it_find.first, key, key_size, std::forward(value_args)...); + } + + template + std::pair insert_or_assign(const CharT* key, size_type key_size, M&& obj) { + auto it = emplace(key, key_size, std::forward(obj)); + if(!it.second) { + it.first.value() = std::forward(obj); + } + + return it; + } + + + + iterator erase(const_iterator pos) { + if(should_clear_old_erased_values()) { + clear_old_erased_values(); + } + + return erase_from_bucket(mutable_iterator(pos)); + } + + iterator erase(const_iterator first, const_iterator last) { + if(first == last) { + return mutable_iterator(first); + } + + /** + * When erasing an element from a bucket with erase_from_bucket, it invalidates all the iterators + * in the array bucket of the element (m_array_bucket_iterator) but not the iterators of the buckets + * itself (m_buckets_iterator). + * + * So first erase all the values between first and last which are not part of the bucket of last, + * and then erase carefully the values in last's bucket. + */ + auto to_delete = mutable_iterator(first); + while(to_delete.m_buckets_iterator != last.m_buckets_iterator) { + to_delete = erase_from_bucket(to_delete); + } + + std::size_t nb_elements_until_last = std::distance(to_delete.m_array_bucket_iterator, + last.m_array_bucket_iterator); + while(nb_elements_until_last > 0) { + to_delete = erase_from_bucket(to_delete); + nb_elements_until_last--; + } + + if(should_clear_old_erased_values()) { + clear_old_erased_values(); + } + + return to_delete; + } + + + + size_type erase(const CharT* key, size_type key_size) { + return erase(key, key_size, hash_key(key, key_size)); + } + + size_type erase(const CharT* key, size_type key_size, std::size_t hash) { + if(should_clear_old_erased_values()) { + clear_old_erased_values(); + } + + const std::size_t ibucket = bucket_for_hash(hash); + if(m_buckets[ibucket].erase(key, key_size)) { + m_nb_elements--; + return 1; + } + else { + return 0; + } + } + + + + void swap(array_hash& other) { + using std::swap; + + swap(static_cast&>(*this), static_cast&>(other)); + swap(static_cast(*this), static_cast(other)); + swap(static_cast(*this), static_cast(other)); + swap(m_buckets_data, other.m_buckets_data); + swap(m_buckets, other.m_buckets); + swap(m_nb_elements, other.m_nb_elements); + swap(m_max_load_factor, other.m_max_load_factor); + swap(m_load_threshold, other.m_load_threshold); + } + + /* + * Lookup + */ + template::value>::type* = nullptr> + U& at(const CharT* key, size_type key_size) { + return at(key, key_size, hash_key(key, key_size)); + } + + template::value>::type* = nullptr> + const U& at(const CharT* key, size_type key_size) const { + return at(key, key_size, hash_key(key, key_size)); + } + + template::value>::type* = nullptr> + U& at(const CharT* key, size_type key_size, std::size_t hash) { + return const_cast(static_cast(this)->at(key, key_size, hash)); + } + + template::value>::type* = nullptr> + const U& at(const CharT* key, size_type key_size, std::size_t hash) const { + const std::size_t ibucket = bucket_for_hash(hash); + + auto it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + if(it_find.second) { + return this->m_values[it_find.first.value()]; + } + else { + THROW(std::out_of_range, "Couldn't find key."); + } + } + + + + template::value>::type* = nullptr> + U& access_operator(const CharT* key, size_type key_size) { + const std::size_t hash = hash_key(key, key_size); + std::size_t ibucket = bucket_for_hash(hash); + + auto it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + if(it_find.second) { + return this->m_values[it_find.first.value()]; + } + else { + if(grow_on_high_load()) { + ibucket = bucket_for_hash(hash); + it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + } + + return emplace_impl(ibucket, it_find.first, key, key_size, U{}).first.value(); + } + } + + + + size_type count(const CharT* key, size_type key_size) const { + return count(key, key_size, hash_key(key, key_size)); + } + + size_type count(const CharT* key, size_type key_size, std::size_t hash) const { + const std::size_t ibucket = bucket_for_hash(hash); + + auto it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + if(it_find.second) { + return 1; + } + else { + return 0; + } + } + + + + iterator find(const CharT* key, size_type key_size) { + return find(key, key_size, hash_key(key, key_size)); + } + + const_iterator find(const CharT* key, size_type key_size) const { + return find(key, key_size, hash_key(key, key_size)); + } + + iterator find(const CharT* key, size_type key_size, std::size_t hash) { + const std::size_t ibucket = bucket_for_hash(hash); + + auto it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + if(it_find.second) { + return iterator(m_buckets_data.begin() + ibucket, it_find.first, this); + } + else { + return end(); + } + } + + const_iterator find(const CharT* key, size_type key_size, std::size_t hash) const { + const std::size_t ibucket = bucket_for_hash(hash); + + auto it_find = m_buckets[ibucket].find_or_end_of_bucket(key, key_size); + if(it_find.second) { + return const_iterator(m_buckets_data.cbegin() + ibucket, it_find.first, this); + } + else { + return cend(); + } + } + + + + std::pair equal_range(const CharT* key, size_type key_size) { + return equal_range(key, key_size, hash_key(key, key_size)); + } + + std::pair equal_range(const CharT* key, size_type key_size) const { + return equal_range(key, key_size, hash_key(key, key_size)); + } + + std::pair equal_range(const CharT* key, size_type key_size, std::size_t hash) { + iterator it = find(key, key_size, hash); + return std::make_pair(it, (it == end())?it:std::next(it)); + } + + std::pair equal_range(const CharT* key, size_type key_size, + std::size_t hash) const + { + const_iterator it = find(key, key_size, hash); + return std::make_pair(it, (it == cend())?it:std::next(it)); + } + + /* + * Bucket interface + */ + size_type bucket_count() const { + return m_buckets_data.size(); + } + + size_type max_bucket_count() const { + return std::min(GrowthPolicy::max_bucket_count(), m_buckets_data.max_size()); + } + + + /* + * Hash policy + */ + float load_factor() const { + if(bucket_count() == 0) { + return 0; + } + + return float(m_nb_elements) / float(bucket_count()); + } + + float max_load_factor() const { + return m_max_load_factor; + } + + void max_load_factor(float ml) { + m_max_load_factor = std::max(0.1f, ml); + m_load_threshold = size_type(float(bucket_count())*m_max_load_factor); + } + + void rehash(size_type count) { + count = std::max(count, size_type(std::ceil(float(size())/max_load_factor()))); + rehash_impl(count); + } + + void reserve(size_type count) { + rehash(size_type(std::ceil(float(count)/max_load_factor()))); + } + + /* + * Observers + */ + hasher hash_function() const { + return static_cast(*this); + } + + // TODO add support for statefull KeyEqual + key_equal key_eq() const { + return KeyEqual(); + } + + /* + * Other + */ + iterator mutable_iterator(const_iterator it) noexcept { + auto it_bucket = m_buckets_data.begin() + std::distance(m_buckets_data.cbegin(), it.m_buckets_iterator); + return iterator(it_bucket, it.m_array_bucket_iterator, this); + } + + template + void serialize(Serializer& serializer) const { + serialize_impl(serializer); + } + + template + void deserialize(Deserializer& deserializer, bool hash_compatible) { + deserialize_impl(deserializer, hash_compatible); + } + +private: + std::size_t hash_key(const CharT* key, size_type key_size) const { + return Hash::operator()(key, key_size); + } + + std::size_t bucket_for_hash(std::size_t hash) const { + return GrowthPolicy::bucket_for_hash(hash); + } + + /** + * If there is a mapped_type, the mapped value in m_values is not erased now. + * It will be erased when the ratio between the size of the map and + * the size of the map + the number of deleted values still stored is low enough (see clear_old_erased_values). + */ + iterator erase_from_bucket(iterator pos) noexcept { + auto array_bucket_next_it = pos.m_buckets_iterator->erase(pos.m_array_bucket_iterator); + m_nb_elements--; + + if(array_bucket_next_it != pos.m_buckets_iterator->cend()) { + return iterator(pos.m_buckets_iterator, array_bucket_next_it, this); + } + else { + do { + ++pos.m_buckets_iterator; + } while(pos.m_buckets_iterator != m_buckets_data.end() && pos.m_buckets_iterator->empty()); + + if(pos.m_buckets_iterator != m_buckets_data.end()) { + return iterator(pos.m_buckets_iterator, pos.m_buckets_iterator->cbegin(), this); + } + else { + return end(); + } + } + } + + + template::value>::type* = nullptr> + bool should_clear_old_erased_values(float /*threshold*/ = DEFAULT_CLEAR_OLD_ERASED_VALUE_THRESHOLD) const { + return false; + } + + template::value>::type* = nullptr> + bool should_clear_old_erased_values(float threshold = DEFAULT_CLEAR_OLD_ERASED_VALUE_THRESHOLD) const { + if(this->m_values.size() == 0) { + return false; + } + + return float(m_nb_elements)/float(this->m_values.size()) < threshold; + } + + template::value>::type* = nullptr> + void clear_old_erased_values() { + } + + template::value>::type* = nullptr> + void clear_old_erased_values() { + static_assert(std::is_nothrow_move_constructible::value || + std::is_copy_constructible::value, + "mapped_value must be either copy constructible or nothrow move constructible."); + + if(m_nb_elements == this->m_values.size()) { + return; + } + + std::vector new_values; + new_values.reserve(size()); + + for(auto it = begin(); it != end(); ++it) { + new_values.push_back(std::move_if_noexcept(it.value())); + } + + + IndexSizeT ivalue = 0; + for(auto it = begin(); it != end(); ++it) { + auto it_array_bucket = it.m_buckets_iterator->mutable_iterator(it.m_array_bucket_iterator); + it_array_bucket.set_value(ivalue); + ivalue++; + } + + new_values.swap(this->m_values); + tsl_ah_assert(m_nb_elements == this->m_values.size()); + } + + /** + * Return true if a rehash occurred. + */ + bool grow_on_high_load() { + if(size() >= m_load_threshold) { + rehash_impl(GrowthPolicy::next_bucket_count()); + return true; + } + + return false; + } + + template::value>::type* = nullptr> + std::pair emplace_impl(std::size_t ibucket, typename array_bucket::const_iterator end_of_bucket, + const CharT* key, size_type key_size, ValueArgs&&... value_args) + { + if(this->m_values.size() >= max_size()) { + // Try to clear old erased values lingering in m_values. Throw if it doesn't change anything. + clear_old_erased_values(); + if(this->m_values.size() >= max_size()) { + THROW(std::length_error, "Can't insert value, too much values in the map."); + } + } + + if(this->m_values.size() == this->m_values.capacity()) { + this->m_values.reserve(std::size_t(float(this->m_values.size()) * value_container::VECTOR_GROWTH_RATE)); + } + + + this->m_values.emplace_back(std::forward(value_args)...); + + auto it = m_buckets[ibucket].append(end_of_bucket, key, key_size, IndexSizeT(this->m_values.size() - 1)); + m_nb_elements++; + + return std::make_pair(iterator(m_buckets_data.begin() + ibucket, it, this), true); + } + + template::value>::type* = nullptr> + std::pair emplace_impl(std::size_t ibucket, typename array_bucket::const_iterator end_of_bucket, + const CharT* key, size_type key_size) + { + if(m_nb_elements >= max_size()) { + THROW(std::length_error, "Can't insert value, too much values in the map."); + } + + auto it = m_buckets[ibucket].append(end_of_bucket, key, key_size); + m_nb_elements++; + + return std::make_pair(iterator(m_buckets_data.begin() + ibucket, it, this), true); + } + + void rehash_impl(size_type bucket_count) { + GrowthPolicy new_growth_policy(bucket_count); + if(bucket_count == this->bucket_count()) { + return; + } + + + if(should_clear_old_erased_values(REHASH_CLEAR_OLD_ERASED_VALUE_THRESHOLD)) { + clear_old_erased_values(); + } + + + std::vector required_size_for_bucket(bucket_count, 0); + std::vector bucket_for_ivalue(size(), 0); + + std::size_t ivalue = 0; + for(auto it = begin(); it != end(); ++it) { + const std::size_t hash = hash_key(it.key(), it.key_size()); + const std::size_t ibucket = new_growth_policy.bucket_for_hash(hash); + + bucket_for_ivalue[ivalue] = ibucket; + required_size_for_bucket[ibucket] += array_bucket::entry_required_bytes(it.key_size()); + ivalue++; + } + + + + + std::vector new_buckets; + new_buckets.reserve(bucket_count); + for(std::size_t ibucket = 0; ibucket < bucket_count; ibucket++) { + new_buckets.emplace_back(required_size_for_bucket[ibucket]); + } + + + ivalue = 0; + for(auto it = begin(); it != end(); ++it) { + const std::size_t ibucket = bucket_for_ivalue[ivalue]; + append_iterator_in_reserved_bucket_no_check(new_buckets[ibucket], it); + + ivalue++; + } + + + using std::swap; + swap(static_cast(*this), new_growth_policy); + + m_buckets_data.swap(new_buckets); + m_buckets = !m_buckets_data.empty()?m_buckets_data.data(): + static_empty_bucket_ptr(); + + // Call max_load_factor to change m_load_threshold + max_load_factor(m_max_load_factor); + } + + template::value>::type* = nullptr> + void append_iterator_in_reserved_bucket_no_check(array_bucket& bucket, iterator it) { + bucket.append_in_reserved_bucket_no_check(it.key(), it.key_size()); + } + + template::value>::type* = nullptr> + void append_iterator_in_reserved_bucket_no_check(array_bucket& bucket, iterator it) { + bucket.append_in_reserved_bucket_no_check(it.key(), it.key_size(), it.value_position()); + } + + + + /** + * On serialization the values of each bucket (if has_mapped_type is true) are serialized + * next to the bucket. The potential old erased values in value_container are thus not serialized. + * + * On deserialization, when hash_compatible is true, we reaffect the value index (IndexSizeT) of each + * bucket with set_value as the position of each value is no more the same in value_container compared + * to when they were serialized. + * + * It's done this way as we can't call clear_old_erased_values() because we want the serialize + * method to remain const and we don't want to serialize/deserialize old erased values. As we may + * not serialize all the values in value_container, the values we keep can change of index. + * We thus have to modify the value indexes in the buckets. + */ + template + void serialize_impl(Serializer& serializer) const { + const slz_size_type version = SERIALIZATION_PROTOCOL_VERSION; + serializer(version); + + const slz_size_type bucket_count = m_buckets_data.size(); + serializer(bucket_count); + + const slz_size_type nb_elements = m_nb_elements; + serializer(nb_elements); + + const float max_load_factor = m_max_load_factor; + serializer(max_load_factor); + + for(const array_bucket& bucket: m_buckets_data) { + bucket.serialize(serializer); + serialize_bucket_values(serializer, bucket); + } + } + + template::value>::type* = nullptr> + void serialize_bucket_values(Serializer& /*serializer*/, const array_bucket& /*bucket*/) const { + } + + template::value>::type* = nullptr> + void serialize_bucket_values(Serializer& serializer, const array_bucket& bucket) const { + for(auto it = bucket.begin(); it != bucket.end(); ++it) { + serializer(this->m_values[it.value()]); + } + } + + template + void deserialize_impl(Deserializer& deserializer, bool hash_compatible) { + tsl_ah_assert(m_buckets_data.empty()); // Current hash table must be empty + + const slz_size_type version = deserialize_value(deserializer); + // For now we only have one version of the serialization protocol. + // If it doesn't match there is a problem with the file. + if(version != SERIALIZATION_PROTOCOL_VERSION) { + THROW(std::runtime_error, "Can't deserialize the array_map/set. The protocol version header is invalid."); + } + + const slz_size_type bucket_count_ds = deserialize_value(deserializer); + const slz_size_type nb_elements = deserialize_value(deserializer); + const float max_load_factor = deserialize_value(deserializer); + + + m_nb_elements = numeric_cast(nb_elements, "Deserialized nb_elements is too big."); + + size_type bucket_count = numeric_cast(bucket_count_ds, "Deserialized bucket_count is too big."); + GrowthPolicy::operator=(GrowthPolicy(bucket_count)); + + + this->max_load_factor(max_load_factor); + value_container::reserve(m_nb_elements); + + + if(hash_compatible) { + if(bucket_count != bucket_count_ds) { + THROW(std::runtime_error, "The GrowthPolicy is not the same even though hash_compatible is true."); + } + + m_buckets_data.reserve(bucket_count); + for(size_type i = 0; i < bucket_count; i++) { + m_buckets_data.push_back(array_bucket::deserialize(deserializer)); + deserialize_bucket_values(deserializer, m_buckets_data.back()); + } + } + else { + m_buckets_data.resize(bucket_count); + for(size_type i = 0; i < bucket_count; i++) { + // TODO use buffer to avoid reallocation on each deserialization. + array_bucket bucket = array_bucket::deserialize(deserializer); + deserialize_bucket_values(deserializer, bucket); + + for(auto it_val = bucket.cbegin(); it_val != bucket.cend(); ++it_val) { + const std::size_t ibucket = bucket_for_hash(hash_key(it_val.key(), it_val.key_size())); + + auto it_find = m_buckets_data[ibucket].find_or_end_of_bucket(it_val.key(), it_val.key_size()); + if(it_find.second) { + THROW(std::runtime_error, "Error on deserialization, the same key is presents multiple times."); + } + + append_array_bucket_iterator_in_bucket(m_buckets_data[ibucket], it_find.first, it_val); + } + } + } + + m_buckets = m_buckets_data.data(); + + + if(load_factor() > this->max_load_factor()) { + THROW(std::runtime_error, "Invalid max_load_factor. Check that the serializer and deserializer support " + "floats correctly as they can be converted implicitely to ints."); + } + } + + template::value>::type* = nullptr> + void deserialize_bucket_values(Deserializer& /*deserializer*/, array_bucket& /*bucket*/) { + } + + template::value>::type* = nullptr> + void deserialize_bucket_values(Deserializer& deserializer, array_bucket& bucket) { + for(auto it = bucket.begin(); it != bucket.end(); ++it) { + this->m_values.emplace_back(deserialize_value(deserializer)); + + tsl_ah_assert(this->m_values.size() - 1 <= std::numeric_limits::max()); + it.set_value(static_cast(this->m_values.size() - 1)); + } + } + + template::value>::type* = nullptr> + void append_array_bucket_iterator_in_bucket(array_bucket& bucket, + typename array_bucket::const_iterator end_of_bucket, + typename array_bucket::const_iterator it_val) + { + bucket.append(end_of_bucket, it_val.key(), it_val.key_size()); + } + + template::value>::type* = nullptr> + void append_array_bucket_iterator_in_bucket(array_bucket& bucket, + typename array_bucket::const_iterator end_of_bucket, + typename array_bucket::const_iterator it_val) + { + bucket.append(end_of_bucket, it_val.key(), it_val.key_size(), it_val.value()); + } + +public: + static const size_type DEFAULT_INIT_BUCKET_COUNT = 0; + static constexpr float DEFAULT_MAX_LOAD_FACTOR = 2.0f; + static const size_type MAX_KEY_SIZE = array_bucket::MAX_KEY_SIZE; + +private: + /** + * Protocol version currenlty used for serialization. + */ + static const slz_size_type SERIALIZATION_PROTOCOL_VERSION = 1; + + + static constexpr float DEFAULT_CLEAR_OLD_ERASED_VALUE_THRESHOLD = 0.6f; + static constexpr float REHASH_CLEAR_OLD_ERASED_VALUE_THRESHOLD = 0.9f; + + + /** + * Return an always valid pointer to a static empty array_bucket. + */ + array_bucket* static_empty_bucket_ptr() { + static array_bucket empty_bucket; + return &empty_bucket; + } + +private: + std::vector m_buckets_data; + + /** + * Points to m_buckets_data.data() if !m_buckets_data.empty() otherwise points to static_empty_bucket_ptr. + * This variable is useful to avoid the cost of checking if m_buckets_data is empty when trying + * to find an element. + * + * TODO Remove m_buckets_data and only use a pointer+size instead of a pointer+vector to save some space in the array_hash object. + */ + array_bucket* m_buckets; + + IndexSizeT m_nb_elements; + float m_max_load_factor; + size_type m_load_threshold; +}; + +} // end namespace detail_array_hash +} //end namespace tsl + +#endif diff --git a/ios/include/trie/array-hash/array_map.h b/ios/include/trie/array-hash/array_map.h new file mode 100644 index 00000000..bc534bf2 --- /dev/null +++ b/ios/include/trie/array-hash/array_map.h @@ -0,0 +1,863 @@ +/** + * MIT License + * + * Copyright (c) 2017 Thibaut Goetghebuer-Planchon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ARRAY_MAP_H +#define TSL_ARRAY_MAP_H + +#include +#include +#include +#include +#include +#include +#include +#include "array_hash.h" + +namespace tsl { + + +/** + * Implementation of a cache-conscious string hash map. + * + * The map stores the strings as `const CharT*`. If `StoreNullTerminator` is true, + * the strings are stored with the a null-terminator (the `key()` method of the iterators + * will return a pointer to this null-terminated string). Otherwise the null character + * is not stored (which allow an economy of 1 byte per string). + * + * The value `T` must be either nothrow move-constructible, copy-constructible or both. + * + * The size of a key string is limited to `std::numeric_limits::max() - 1`. + * That is 65 535 characters by default, but can be raised with the `KeySizeT` template parameter. + * See `max_key_size()` for an easy access to this limit. + * + * The number of elements in the map is limited to `std::numeric_limits::max()`. + * That is 4 294 967 296 elements, but can be raised with the `IndexSizeT` template parameter. + * See `max_size()` for an easy access to this limit. + * + * Iterators invalidation: + * - clear, operator=: always invalidate the iterators. + * - insert, emplace, operator[]: always invalidate the iterators. + * - erase: always invalidate the iterators. + * - shrink_to_fit: always invalidate the iterators. + */ +template, + class KeyEqual = tsl::ah::str_equal, + bool StoreNullTerminator = true, + class KeySizeT = std::uint16_t, + class IndexSizeT = std::uint32_t, + class GrowthPolicy = tsl::ah::power_of_two_growth_policy<2>> +class array_map { +private: + template + using is_iterator = tsl::detail_array_hash::is_iterator; + + using ht = tsl::detail_array_hash::array_hash; + +public: + using char_type = typename ht::char_type; + using mapped_type = T; + using key_size_type = typename ht::key_size_type; + using index_size_type = typename ht::index_size_type; + using size_type = typename ht::size_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + +public: + array_map(): array_map(ht::DEFAULT_INIT_BUCKET_COUNT) { + } + + explicit array_map(size_type bucket_count, + const Hash& hash = Hash()): m_ht(bucket_count, hash, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + template::value>::type* = nullptr> + array_map(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash& hash = Hash()): array_map(bucket_count, hash) + { + insert(first, last); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + array_map(std::initializer_list, T>> init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash& hash = Hash()): array_map(bucket_count, hash) + { + insert(init); + } +#else + array_map(std::initializer_list> init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash& hash = Hash()): array_map(bucket_count, hash) + { + insert(init); + } +#endif + + + +#ifdef TSL_AH_HAS_STRING_VIEW + array_map& operator=(std::initializer_list, T>> ilist) { + clear(); + + reserve(ilist.size()); + insert(ilist); + + return *this; + } +#else + array_map& operator=(std::initializer_list> ilist) { + clear(); + + reserve(ilist.size()); + insert(ilist); + + return *this; + } +#endif + + + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + size_type max_key_size() const noexcept { return m_ht.max_key_size(); } + void shrink_to_fit() { m_ht.shrink_to_fit(); } + + + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + std::pair insert(const std::basic_string_view& key, const T& value) { + return m_ht.emplace(key.data(), key.size(), value); + } +#else + std::pair insert(const CharT* key, const T& value) { + return m_ht.emplace(key, std::char_traits::length(key), value); + } + + std::pair insert(const std::basic_string& key, const T& value) { + return m_ht.emplace(key.data(), key.size(), value); + } +#endif + std::pair insert_ks(const CharT* key, size_type key_size, const T& value) { + return m_ht.emplace(key, key_size, value); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + std::pair insert(const std::basic_string_view& key, T&& value) { + return m_ht.emplace(key.data(), key.size(), std::move(value)); + } +#else + std::pair insert(const CharT* key, T&& value) { + return m_ht.emplace(key, std::char_traits::length(key), std::move(value)); + } + + std::pair insert(const std::basic_string& key, T&& value) { + return m_ht.emplace(key.data(), key.size(), std::move(value)); + } +#endif + std::pair insert_ks(const CharT* key, size_type key_size, T&& value) { + return m_ht.emplace(key, key_size, std::move(value)); + } + + + + template::value>::type* = nullptr> + void insert(InputIt first, InputIt last) { + if(std::is_base_of::iterator_category>::value) + { + const auto nb_elements_insert = std::distance(first, last); + const std::size_t nb_free_buckets = std::size_t(float(bucket_count())*max_load_factor()) - size(); + + if(nb_elements_insert > 0 && nb_free_buckets < std::size_t(nb_elements_insert)) { + reserve(size() + std::size_t(nb_elements_insert)); + } + } + + for(auto it = first; it != last; ++it) { + insert_pair(*it); + } + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + void insert(std::initializer_list, T>> ilist) { + insert(ilist.begin(), ilist.end()); + } +#else + void insert(std::initializer_list> ilist) { + insert(ilist.begin(), ilist.end()); + } +#endif + + + +#ifdef TSL_AH_HAS_STRING_VIEW + template + std::pair insert_or_assign(const std::basic_string_view& key, M&& obj) { + return m_ht.insert_or_assign(key.data(), key.size(), std::forward(obj)); + } +#else + template + std::pair insert_or_assign(const CharT* key, M&& obj) { + return m_ht.insert_or_assign(key, std::char_traits::length(key), std::forward(obj)); + } + + template + std::pair insert_or_assign(const std::basic_string& key, M&& obj) { + return m_ht.insert_or_assign(key.data(), key.size(), std::forward(obj)); + } +#endif + template + std::pair insert_or_assign_ks(const CharT* key, size_type key_size, M&& obj) { + return m_ht.insert_or_assign(key, key_size, std::forward(obj)); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + template + std::pair emplace(const std::basic_string_view& key, Args&&... args) { + return m_ht.emplace(key.data(), key.size(), std::forward(args)...); + } +#else + template + std::pair emplace(const CharT* key, Args&&... args) { + return m_ht.emplace(key, std::char_traits::length(key), std::forward(args)...); + } + + template + std::pair emplace(const std::basic_string& key, Args&&... args) { + return m_ht.emplace(key.data(), key.size(), std::forward(args)...); + } +#endif + template + std::pair emplace_ks(const CharT* key, size_type key_size, Args&&... args) { + return m_ht.emplace(key, key_size, std::forward(args)...); + } + + + + /** + * Erase has an amortized O(1) runtime complexity, but even if it removes the key immediately, + * it doesn't do the same for the associated value T. + * + * T will only be removed when the ratio between the size of the map and + * the size of the map + the number of deleted values still stored is low enough. + * + * To force the deletion you can call shrink_to_fit. + */ + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + + /** + * @copydoc erase(const_iterator pos) + */ + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc erase(const_iterator pos) + */ + size_type erase(const std::basic_string_view& key) { + return m_ht.erase(key.data(), key.size()); + } +#else + /** + * @copydoc erase(const_iterator pos) + */ + size_type erase(const CharT* key) { + return m_ht.erase(key, std::char_traits::length(key)); + } + + /** + * @copydoc erase(const_iterator pos) + */ + size_type erase(const std::basic_string& key) { + return m_ht.erase(key.data(), key.size()); + } +#endif + /** + * @copydoc erase(const_iterator pos) + */ + size_type erase_ks(const CharT* key, size_type key_size) { + return m_ht.erase(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + size_type erase(const std::basic_string_view& key, std::size_t precalculated_hash) { + return m_ht.erase(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + size_type erase(const CharT* key, std::size_t precalculated_hash) { + return m_ht.erase(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + size_type erase(const std::basic_string& key, std::size_t precalculated_hash) { + return m_ht.erase(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * @copydoc erase(const_iterator pos) + * + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + size_type erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) { + return m_ht.erase(key, key_size, precalculated_hash); + } + + + + void swap(array_map& other) { other.m_ht.swap(m_ht); } + + + + /* + * Lookup + */ +#ifdef TSL_AH_HAS_STRING_VIEW + T& at(const std::basic_string_view& key) { + return m_ht.at(key.data(), key.size()); + } + + const T& at(const std::basic_string_view& key) const { + return m_ht.at(key.data(), key.size()); + } +#else + T& at(const CharT* key) { + return m_ht.at(key, std::char_traits::length(key)); + } + + const T& at(const CharT* key) const { + return m_ht.at(key, std::char_traits::length(key)); + } + + T& at(const std::basic_string& key) { + return m_ht.at(key.data(), key.size()); + } + + const T& at(const std::basic_string& key) const { + return m_ht.at(key.data(), key.size()); + } +#endif + T& at_ks(const CharT* key, size_type key_size) { + return m_ht.at(key, key_size); + } + + const T& at_ks(const CharT* key, size_type key_size) const { + return m_ht.at(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + T& at(const std::basic_string_view& key, std::size_t precalculated_hash) { + return m_ht.at(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const T& at(const std::basic_string_view& key, std::size_t precalculated_hash) const { + return m_ht.at(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + T& at(const CharT* key, std::size_t precalculated_hash) { + return m_ht.at(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const T& at(const CharT* key, std::size_t precalculated_hash) const { + return m_ht.at(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + T& at(const std::basic_string& key, std::size_t precalculated_hash) { + return m_ht.at(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const T& at(const std::basic_string& key, std::size_t precalculated_hash) const { + return m_ht.at(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + T& at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) { + return m_ht.at(key, key_size, precalculated_hash); + } + + /** + * @copydoc at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const T& at_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const { + return m_ht.at(key, key_size, precalculated_hash); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + T& operator[](const std::basic_string_view& key) { return m_ht.access_operator(key.data(), key.size()); } +#else + T& operator[](const CharT* key) { return m_ht.access_operator(key, std::char_traits::length(key)); } + T& operator[](const std::basic_string& key) { return m_ht.access_operator(key.data(), key.size()); } +#endif + + + +#ifdef TSL_AH_HAS_STRING_VIEW + size_type count(const std::basic_string_view& key) const { + return m_ht.count(key.data(), key.size()); + } +#else + size_type count(const CharT* key) const { + return m_ht.count(key, std::char_traits::length(key)); + } + + size_type count(const std::basic_string& key) const { + return m_ht.count(key.data(), key.size()); + } +#endif + size_type count_ks(const CharT* key, size_type key_size) const { + return m_ht.count(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const + */ + size_type count(const std::basic_string_view& key, std::size_t precalculated_hash) const { + return m_ht.count(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const + */ + size_type count(const CharT* key, std::size_t precalculated_hash) const { + return m_ht.count(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const + */ + size_type count(const std::basic_string& key, std::size_t precalculated_hash) const { + return m_ht.count(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + size_type count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const { + return m_ht.count(key, key_size, precalculated_hash); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + iterator find(const std::basic_string_view& key) { + return m_ht.find(key.data(), key.size()); + } + + const_iterator find(const std::basic_string_view& key) const { + return m_ht.find(key.data(), key.size()); + } +#else + iterator find(const CharT* key) { + return m_ht.find(key, std::char_traits::length(key)); + } + + const_iterator find(const CharT* key) const { + return m_ht.find(key, std::char_traits::length(key)); + } + + iterator find(const std::basic_string& key) { + return m_ht.find(key.data(), key.size()); + } + + const_iterator find(const std::basic_string& key) const { + return m_ht.find(key.data(), key.size()); + } +#endif + iterator find_ks(const CharT* key, size_type key_size) { + return m_ht.find(key, key_size); + } + + const_iterator find_ks(const CharT* key, size_type key_size) const { + return m_ht.find(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + iterator find(const std::basic_string_view& key, std::size_t precalculated_hash) { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find(const std::basic_string_view& key, std::size_t precalculated_hash) const { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + iterator find(const CharT* key, std::size_t precalculated_hash) { + return m_ht.find(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find(const CharT* key, std::size_t precalculated_hash) const { + return m_ht.find(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + iterator find(const std::basic_string& key, std::size_t precalculated_hash) { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find(const std::basic_string& key, std::size_t precalculated_hash) const { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + iterator find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) { + return m_ht.find(key, key_size, precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const { + return m_ht.find(key, key_size, precalculated_hash); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + std::pair equal_range(const std::basic_string_view& key) { + return m_ht.equal_range(key.data(), key.size()); + } + + std::pair equal_range(const std::basic_string_view& key) const { + return m_ht.equal_range(key.data(), key.size()); + } +#else + std::pair equal_range(const CharT* key) { + return m_ht.equal_range(key, std::char_traits::length(key)); + } + + std::pair equal_range(const CharT* key) const { + return m_ht.equal_range(key, std::char_traits::length(key)); + } + + std::pair equal_range(const std::basic_string& key) { + return m_ht.equal_range(key.data(), key.size()); + } + + std::pair equal_range(const std::basic_string& key) const { + return m_ht.equal_range(key.data(), key.size()); + } +#endif + std::pair equal_range_ks(const CharT* key, size_type key_size) { + return m_ht.equal_range(key, key_size); + } + + std::pair equal_range_ks(const CharT* key, size_type key_size) const { + return m_ht.equal_range(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string_view& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string_view& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const CharT* key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const CharT* key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + std::pair equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) { + return m_ht.equal_range(key, key_size, precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, key_size, precalculated_hash); + } + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + /* + * Other + */ + /** + * Return the `const_iterator it` as an `iterator`. + */ + iterator mutable_iterator(const_iterator it) noexcept { return m_ht.mutable_iterator(it); } + + /** + * Serialize the map through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the following calls: + * - `template void operator()(const U& value);` where the types `std::uint64_t`, `float` and `T` must be supported for U. + * - `void operator()(const CharT* value, std::size_t value_size);` + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for floats, ...) of the types it serializes + * in the hands of the `Serializer` function object if compatibility is required. + */ + template + void serialize(Serializer& serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previously serialized map through the `deserializer` parameter. + * + * The `deserializer` parameter must be a function object that supports the following calls: + * - `template U operator()();` where the types `std::uint64_t`, `float` and `T` must be supported for U. + * - `void operator()(CharT* value_out, std::size_t value_size);` + * + * If the deserialized hash map type is hash compatible with the serialized map, the deserialization process can be + * sped up by setting `hash_compatible` to true. To be hash compatible, the Hash (take care of the 32-bits vs 64 bits), + * KeyEqual, GrowthPolicy, StoreNullTerminator, KeySizeT and IndexSizeT must behave the same than the ones used on the + * serialized map. Otherwise the behaviour is undefined with `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `CharT` and `T` of the `array_map` are not the same as the + * types used during serialization. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for floats, size of int, ...) of the types it + * deserializes in the hands of the `Deserializer` function object if compatibility is required. + */ + template + static array_map deserialize(Deserializer& deserializer, bool hash_compatible = false) { + array_map map(0); + map.m_ht.deserialize(deserializer, hash_compatible); + + return map; + } + + friend bool operator==(const array_map& lhs, const array_map& rhs) { + if(lhs.size() != rhs.size()) { + return false; + } + + for(auto it = lhs.cbegin(); it != lhs.cend(); ++it) { + const auto it_element_rhs = rhs.find_ks(it.key(), it.key_size()); + if(it_element_rhs == rhs.cend() || it.value() != it_element_rhs.value()) { + return false; + } + } + + return true; + } + + friend bool operator!=(const array_map& lhs, const array_map& rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(array_map& lhs, array_map& rhs) { + lhs.swap(rhs); + } + +private: + template + void insert_pair(const std::pair& value) { + insert(value.first, value.second); + } + + template + void insert_pair(std::pair&& value) { + insert(value.first, std::move(value.second)); + } + +public: + static const size_type MAX_KEY_SIZE = ht::MAX_KEY_SIZE; + +private: + ht m_ht; +}; + + +/** + * Same as + * `tsl::array_map`. + */ +template, + class KeyEqual = tsl::ah::str_equal, + bool StoreNullTerminator = true, + class KeySizeT = std::uint16_t, + class IndexSizeT = std::uint32_t> +using array_pg_map = array_map; + +} //end namespace tsl + +#endif diff --git a/ios/include/trie/array-hash/array_set.h b/ios/include/trie/array-hash/array_set.h new file mode 100644 index 00000000..0322bcd0 --- /dev/null +++ b/ios/include/trie/array-hash/array_set.h @@ -0,0 +1,664 @@ +/** + * MIT License + * + * Copyright (c) 2017 Thibaut Goetghebuer-Planchon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +#ifndef TSL_ARRAY_SET_H +#define TSL_ARRAY_SET_H + +#include +#include +#include +#include +#include +#include +#include +#include "array_hash.h" + +namespace tsl { + +/** + * Implementation of a cache-conscious string hash set. + * + * The set stores the strings as `const CharT*`. If `StoreNullTerminator` is true, + * the strings are stored with the a null-terminator (the `key()` method of the iterators + * will return a pointer to this null-terminated string). Otherwise the null character + * is not stored (which allow an economy of 1 byte per string). + * + * The size of a key string is limited to `std::numeric_limits::max() - 1`. + * That is 65 535 characters by default, but can be raised with the `KeySizeT` template parameter. + * See `max_key_size()` for an easy access to this limit. + * + * The number of elements in the set is limited to `std::numeric_limits::max()`. + * That is 4 294 967 296 elements, but can be raised with the `IndexSizeT` template parameter. + * See `max_size()` for an easy access to this limit. + * + * Iterators invalidation: + * - clear, operator=: always invalidate the iterators. + * - insert, emplace, operator[]: always invalidate the iterators. + * - erase: always invalidate the iterators. + * - shrink_to_fit: always invalidate the iterators. + */ +template, + class KeyEqual = tsl::ah::str_equal, + bool StoreNullTerminator = true, + class KeySizeT = std::uint16_t, + class IndexSizeT = std::uint32_t, + class GrowthPolicy = tsl::ah::power_of_two_growth_policy<2>> +class array_set { +private: + template + using is_iterator = tsl::detail_array_hash::is_iterator; + + using ht = tsl::detail_array_hash::array_hash; + +public: + using char_type = typename ht::char_type; + using key_size_type = typename ht::key_size_type; + using index_size_type = typename ht::index_size_type; + using size_type = typename ht::size_type; + using hasher = typename ht::hasher; + using key_equal = typename ht::key_equal; + using iterator = typename ht::iterator; + using const_iterator = typename ht::const_iterator; + + array_set(): array_set(ht::DEFAULT_INIT_BUCKET_COUNT) { + } + + explicit array_set(size_type bucket_count, + const Hash& hash = Hash()): m_ht(bucket_count, hash, ht::DEFAULT_MAX_LOAD_FACTOR) + { + } + + template::value>::type* = nullptr> + array_set(InputIt first, InputIt last, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash& hash = Hash()): array_set(bucket_count, hash) + { + insert(first, last); + } + + +#ifdef TSL_AH_HAS_STRING_VIEW + array_set(std::initializer_list> init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash& hash = Hash()): array_set(bucket_count, hash) + { + insert(init); + } +#else + array_set(std::initializer_list init, + size_type bucket_count = ht::DEFAULT_INIT_BUCKET_COUNT, + const Hash& hash = Hash()): array_set(bucket_count, hash) + { + insert(init); + } +#endif + + + +#ifdef TSL_AH_HAS_STRING_VIEW + array_set& operator=(std::initializer_list> ilist) { + clear(); + + reserve(ilist.size()); + insert(ilist); + + return *this; + } +#else + array_set& operator=(std::initializer_list ilist) { + clear(); + + reserve(ilist.size()); + insert(ilist); + + return *this; + } +#endif + + /* + * Iterators + */ + iterator begin() noexcept { return m_ht.begin(); } + const_iterator begin() const noexcept { return m_ht.begin(); } + const_iterator cbegin() const noexcept { return m_ht.cbegin(); } + + iterator end() noexcept { return m_ht.end(); } + const_iterator end() const noexcept { return m_ht.end(); } + const_iterator cend() const noexcept { return m_ht.cend(); } + + + /* + * Capacity + */ + bool empty() const noexcept { return m_ht.empty(); } + size_type size() const noexcept { return m_ht.size(); } + size_type max_size() const noexcept { return m_ht.max_size(); } + size_type max_key_size() const noexcept { return m_ht.max_key_size(); } + void shrink_to_fit() { m_ht.shrink_to_fit(); } + + + /* + * Modifiers + */ + void clear() noexcept { m_ht.clear(); } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + std::pair insert(const std::basic_string_view& key) { + return m_ht.emplace(key.data(), key.size()); + } +#else + std::pair insert(const CharT* key) { + return m_ht.emplace(key, std::char_traits::length(key)); + } + + std::pair insert(const std::basic_string& key) { + return m_ht.emplace(key.data(), key.size()); + } +#endif + std::pair insert_ks(const CharT* key, size_type key_size) { + return m_ht.emplace(key, key_size); + } + + + + template::value>::type* = nullptr> + void insert(InputIt first, InputIt last) { + if(std::is_base_of::iterator_category>::value) + { + const auto nb_elements_insert = std::distance(first, last); + const std::size_t nb_free_buckets = std::size_t(float(bucket_count())*max_load_factor()) - size(); + + if(nb_elements_insert > 0 && nb_free_buckets < std::size_t(nb_elements_insert)) { + reserve(size() + std::size_t(nb_elements_insert)); + } + } + + for(auto it = first; it != last; ++it) { + insert(*it); + } + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + void insert(std::initializer_list> ilist) { + insert(ilist.begin(), ilist.end()); + } +#else + void insert(std::initializer_list ilist) { + insert(ilist.begin(), ilist.end()); + } +#endif + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc emplace_ks(const CharT* key, size_type key_size) + */ + std::pair emplace(const std::basic_string_view& key) { + return m_ht.emplace(key.data(), key.size()); + } +#else + /** + * @copydoc emplace_ks(const CharT* key, size_type key_size) + */ + std::pair emplace(const CharT* key) { + return m_ht.emplace(key, std::char_traits::length(key)); + } + + /** + * @copydoc emplace_ks(const CharT* key, size_type key_size) + */ + std::pair emplace(const std::basic_string& key) { + return m_ht.emplace(key.data(), key.size()); + } +#endif + /** + * No difference compared to the insert method. Mainly here for coherence with array_map. + */ + std::pair emplace_ks(const CharT* key, size_type key_size) { + return m_ht.emplace(key, key_size); + } + + + + iterator erase(const_iterator pos) { return m_ht.erase(pos); } + iterator erase(const_iterator first, const_iterator last) { return m_ht.erase(first, last); } + +#ifdef TSL_AH_HAS_STRING_VIEW + size_type erase(const std::basic_string_view& key) { + return m_ht.erase(key.data(), key.size()); + } +#else + size_type erase(const CharT* key) { + return m_ht.erase(key, std::char_traits::length(key)); + } + + size_type erase(const std::basic_string& key) { + return m_ht.erase(key.data(), key.size()); + } +#endif + size_type erase_ks(const CharT* key, size_type key_size) { + return m_ht.erase(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + size_type erase(const std::basic_string_view& key, std::size_t precalculated_hash) { + return m_ht.erase(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + size_type erase(const CharT* key, std::size_t precalculated_hash) { + return m_ht.erase(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + size_type erase(const std::basic_string& key, std::size_t precalculated_hash) { + return m_ht.erase(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + size_type erase_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) { + return m_ht.erase(key, key_size, precalculated_hash); + } + + + + void swap(array_set& other) { other.m_ht.swap(m_ht); } + + + + /* + * Lookup + */ +#ifdef TSL_AH_HAS_STRING_VIEW + size_type count(const std::basic_string_view& key) const { return m_ht.count(key.data(), key.size()); } +#else + size_type count(const CharT* key) const { return m_ht.count(key, std::char_traits::length(key)); } + size_type count(const std::basic_string& key) const { return m_ht.count(key.data(), key.size()); } +#endif + size_type count_ks(const CharT* key, size_type key_size) const { return m_ht.count(key, key_size); } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const + */ + size_type count(const std::basic_string_view& key, std::size_t precalculated_hash) const { + return m_ht.count(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const + */ + size_type count(const CharT* key, std::size_t precalculated_hash) const { + return m_ht.count(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const + */ + size_type count(const std::basic_string& key, std::size_t precalculated_hash) const { + return m_ht.count(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + size_type count_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const { + return m_ht.count(key, key_size, precalculated_hash); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + iterator find(const std::basic_string_view& key) { + return m_ht.find(key.data(), key.size()); + } + + const_iterator find(const std::basic_string_view& key) const { + return m_ht.find(key.data(), key.size()); + } +#else + iterator find(const CharT* key) { + return m_ht.find(key, std::char_traits::length(key)); + } + + const_iterator find(const CharT* key) const { + return m_ht.find(key, std::char_traits::length(key)); + } + + iterator find(const std::basic_string& key) { + return m_ht.find(key.data(), key.size()); + } + + const_iterator find(const std::basic_string& key) const { + return m_ht.find(key.data(), key.size()); + } +#endif + iterator find_ks(const CharT* key, size_type key_size) { + return m_ht.find(key, key_size); + } + + const_iterator find_ks(const CharT* key, size_type key_size) const { + return m_ht.find(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + iterator find(const std::basic_string_view& key, std::size_t precalculated_hash) { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find(const std::basic_string_view& key, std::size_t precalculated_hash) const { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + iterator find(const CharT* key, std::size_t precalculated_hash) { + return m_ht.find(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find(const CharT* key, std::size_t precalculated_hash) const { + return m_ht.find(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + iterator find(const std::basic_string& key, std::size_t precalculated_hash) { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find(const std::basic_string& key, std::size_t precalculated_hash) const { + return m_ht.find(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + iterator find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) { + return m_ht.find(key, key_size, precalculated_hash); + } + + /** + * @copydoc find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + const_iterator find_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const { + return m_ht.find(key, key_size, precalculated_hash); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + std::pair equal_range(const std::basic_string_view& key) { + return m_ht.equal_range(key.data(), key.size()); + } + + std::pair equal_range(const std::basic_string_view& key) const { + return m_ht.equal_range(key.data(), key.size()); + } +#else + std::pair equal_range(const CharT* key) { + return m_ht.equal_range(key, std::char_traits::length(key)); + } + + std::pair equal_range(const CharT* key) const { + return m_ht.equal_range(key, std::char_traits::length(key)); + } + + std::pair equal_range(const std::basic_string& key) { + return m_ht.equal_range(key.data(), key.size()); + } + + std::pair equal_range(const std::basic_string& key) const { + return m_ht.equal_range(key.data(), key.size()); + } +#endif + std::pair equal_range_ks(const CharT* key, size_type key_size) { + return m_ht.equal_range(key, key_size); + } + + std::pair equal_range_ks(const CharT* key, size_type key_size) const { + return m_ht.equal_range(key, key_size); + } + + + +#ifdef TSL_AH_HAS_STRING_VIEW + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string_view& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string_view& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } +#else + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const CharT* key, std::size_t precalculated_hash) { + return m_ht.equal_range(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const CharT* key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, std::char_traits::length(key), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string& key, std::size_t precalculated_hash) { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range(const std::basic_string& key, std::size_t precalculated_hash) const { + return m_ht.equal_range(key.data(), key.size(), precalculated_hash); + } +#endif + /** + * Use the hash value 'precalculated_hash' instead of hashing the key. The hash value should be the same + * as hash_function()(key). Useful to speed-up the lookup to the value if you already have the hash. + */ + std::pair equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) { + return m_ht.equal_range(key, key_size, precalculated_hash); + } + + /** + * @copydoc equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) + */ + std::pair equal_range_ks(const CharT* key, size_type key_size, std::size_t precalculated_hash) const { + return m_ht.equal_range(key, key_size, precalculated_hash); + } + + + + /* + * Bucket interface + */ + size_type bucket_count() const { return m_ht.bucket_count(); } + size_type max_bucket_count() const { return m_ht.max_bucket_count(); } + + + /* + * Hash policy + */ + float load_factor() const { return m_ht.load_factor(); } + float max_load_factor() const { return m_ht.max_load_factor(); } + void max_load_factor(float ml) { m_ht.max_load_factor(ml); } + + void rehash(size_type count) { m_ht.rehash(count); } + void reserve(size_type count) { m_ht.reserve(count); } + + + /* + * Observers + */ + hasher hash_function() const { return m_ht.hash_function(); } + key_equal key_eq() const { return m_ht.key_eq(); } + + + /* + * Other + */ + /** + * Return the `const_iterator it` as an `iterator`. + */ + iterator mutable_iterator(const_iterator it) noexcept { return m_ht.mutable_iterator(it); } + + /** + * Serialize the set through the `serializer` parameter. + * + * The `serializer` parameter must be a function object that supports the following calls: + * - `template void operator()(const U& value);` where the types `std::uint64_t` and `float` must be supported for U. + * - `void operator()(const CharT* value, std::size_t value_size);` + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for floats, ...) of the types it serializes + * in the hands of the `Serializer` function object if compatibility is required. + */ + template + void serialize(Serializer& serializer) const { + m_ht.serialize(serializer); + } + + /** + * Deserialize a previously serialized set through the `deserializer` parameter. + * + * The `deserializer` parameter must be a function object that supports the following calls: + * - `template U operator()();` where the types `std::uint64_t` and `float` must be supported for U. + * - `void operator()(CharT* value_out, std::size_t value_size);` + * + * If the deserialized hash set type is hash compatible with the serialized set, the deserialization process can be + * sped up by setting `hash_compatible` to true. To be hash compatible, the Hash (take care of the 32-bits vs 64 bits), + * KeyEqual, GrowthPolicy, StoreNullTerminator, KeySizeT and IndexSizeT must behave the same than the ones used on the + * serialized set. Otherwise the behaviour is undefined with `hash_compatible` sets to true. + * + * The behaviour is undefined if the type `CharT` of the `array_set` is not the same as the + * type used during serialization. + * + * The implementation leaves binary compatibility (endianness, IEEE 754 for floats, size of int, ...) of the types it + * deserializes in the hands of the `Deserializer` function object if compatibility is required. + */ + template + static array_set deserialize(Deserializer& deserializer, bool hash_compatible = false) { + array_set set(0); + set.m_ht.deserialize(deserializer, hash_compatible); + + return set; + } + + friend bool operator==(const array_set& lhs, const array_set& rhs) { + if(lhs.size() != rhs.size()) { + return false; + } + + for(auto it = lhs.cbegin(); it != lhs.cend(); ++it) { + const auto it_element_rhs = rhs.find_ks(it.key(), it.key_size()); + if(it_element_rhs == rhs.cend()) { + return false; + } + } + + return true; + } + + friend bool operator!=(const array_set& lhs, const array_set& rhs) { + return !operator==(lhs, rhs); + } + + friend void swap(array_set& lhs, array_set& rhs) { + lhs.swap(rhs); + } + +public: + static const size_type MAX_KEY_SIZE = ht::MAX_KEY_SIZE; + +private: + ht m_ht; +}; + + +/** + * Same as + * `tsl::array_set`. + */ +template, + class KeyEqual = tsl::ah::str_equal, + bool StoreNullTerminator = true, + class KeySizeT = std::uint16_t, + class IndexSizeT = std::uint32_t> +using array_pg_set = array_set; + +} //end namespace tsl + +#endif diff --git a/ios/mimetic_filament.podspec b/ios/mimetic_filament.podspec index 560819d9..fc7f9197 100644 --- a/ios/mimetic_filament.podspec +++ b/ios/mimetic_filament.podspec @@ -13,7 +13,7 @@ A new flutter plugin project. s.license = { :file => '../LICENSE' } s.author = { 'Your Company' => 'email@example.com' } s.source = { :path => '.' } - s.source_files = 'Classes/**/*', 'src/*.*' + s.source_files = 'Classes/**/*', 'src/*.*', 'src/morph/*' s.dependency 'Filament', '~> 1.12.3' s.dependency 'Flutter' s.platform = :ios, '12.1' @@ -23,13 +23,18 @@ A new flutter plugin project. s.xcconfig = { 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES', 'ALWAYS_SEARCH_USER_PATHS' => 'YES', - 'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/mimetic_filament/ios/include" "${PODS_ROOT}/../.symlinks/plugins/mimetic_filament/ios/src"', - + 'USER_HEADER_SEARCH_PATHS' => '"${PODS_ROOT}/../.symlinks/plugins/mimetic_filament/ios/include" "${PODS_ROOT}/../.symlinks/plugins/mimetic_filament/ios/src", "${PODS_ROOT}/../.symlinks/plugins/mimetic_filament/ios/morph"', + 'OTHER_CXXFLAGS' => '--std=c++17', + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + #"CLANG_CXX_LIBRARY" => "libc++" } s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', - 'OTHER_CFLAGS' => '-fmodules -fcxx-modules' + 'OTHER_CFLAGS' => '-fmodules -fcxx-modules', + #'OTHER_CXXFLAGS' => '--std=c++17', + #"CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + #"CLANG_CXX_LIBRARY" => "libc++" } s.swift_version = '5.0' end diff --git a/ios/src/FilamentViewer.cpp b/ios/src/FilamentViewer.cpp index f7e13948..4a2aab23 100644 --- a/ios/src/FilamentViewer.cpp +++ b/ios/src/FilamentViewer.cpp @@ -14,9 +14,8 @@ * limitations under the License. */ - - #include "FilamentViewer.hpp" + #include #include #include @@ -37,6 +36,7 @@ #include #include #include +#include #include @@ -50,13 +50,9 @@ #include -#include - #include #include - - using namespace filament; using namespace filament::math; using namespace gltfio; @@ -67,6 +63,10 @@ namespace filament { class LightManager; } +namespace gltfio { + MaterialProvider* createGPUMorphShaderLoader(const void* data, uint64_t size, Engine* engine); +} + namespace mimetic { const double kNearPlane = 0.05; // 5 cm @@ -76,10 +76,9 @@ const float kAperture = 16.0f; const float kShutterSpeed = 1.0f / 125.0f; const float kSensitivity = 100.0f; -MaterialProvider* createGPUShaderLoader(Engine* engine); - FilamentViewer::FilamentViewer( void* layer, + const char* shaderPath, LoadResource loadResource, FreeResource freeResource) : _layer(layer), _loadResource(loadResource), @@ -99,8 +98,13 @@ FilamentViewer::FilamentViewer( _swapChain = _engine->createSwapChain(_layer); -// _materialProvider = createGPUShaderLoader(_engine); - _materialProvider = createUbershaderLoader(_engine); + if(shaderPath) { + ResourceBuffer rb = _loadResource(shaderPath); + _materialProvider = createGPUMorphShaderLoader(rb.data, rb.size, _engine); +// _freeResource((void*)rb.data, rb.size, nullptr); + } else { + _materialProvider = createUbershaderLoader(_engine); + } EntityManager& em = EntityManager::get(); _ncm = new NameComponentManager(em); _assetLoader = AssetLoader::create({_engine, _materialProvider, _ncm, &em}); @@ -111,6 +115,7 @@ FilamentViewer::FilamentViewer( Manipulator::Builder().orbitHomePosition(0.0f, 0.0f, 0.0f).targetPosition(0.0f, 0.0f, 0).build(Mode::ORBIT); //Manipulator::Builder().orbitHomePosition(0.0f, 0.0f, 0.0f).targetPosition(0.0f, 0.0f, 0).build(Mode::ORBIT); _asset = nullptr; + } FilamentViewer::~FilamentViewer() { @@ -138,23 +143,26 @@ void FilamentViewer::loadResources(string relativeResourcePath) { } _animator = _asset->getAnimator(); - _asset->releaseSourceData(); +// _asset->releaseSourceData(); _scene->addEntities(_asset->getEntities(), _asset->getEntityCount()); }; -void FilamentViewer::loadGltf(const char* const uri, const char* const relativeResourcePath) { - _resourceLoader->asyncCancelLoad(); - _resourceLoader->evictResourceData(); +void FilamentViewer::loadGltf(const char* const uri, const char* const relativeResourcePath, const char* materialInstanceName) { if(_asset) { + _resourceLoader->evictResourceData(); + _scene->removeEntities(_asset->getEntities(), _asset->getEntityCount()); _assetLoader->destroyAsset(_asset); } + _asset = nullptr; + _animator = nullptr; ResourceBuffer rbuf = _loadResource(uri); // Parse the glTF file and create Filament entities. _asset = _assetLoader->createAssetFromJson((uint8_t*)rbuf.data, rbuf.size); - + + if (!_asset) { std::cerr << "Unable to parse asset" << std::endl; exit(1); @@ -167,6 +175,11 @@ void FilamentViewer::loadGltf(const char* const uri, const char* const relativeR transformToUnitCube(); startTime = std::chrono::high_resolution_clock::now(); + +} + +void FilamentViewer::createMorpher(const char* meshName, const char* entityName, const char* materialInstanceName) { + morphHelper = new gltfio::GPUMorphHelper((FFilamentAsset*)_asset, meshName, entityName, materialInstanceName); } @@ -241,13 +254,12 @@ void FilamentViewer::render() { _mainCamera->lookAt(eye, target, upward); if(_animator) { - // typedef std::chrono::high_resolution_clock clock; - typedef std::chrono::duration duration; + /*typedef std::chrono::duration duration; duration dur = std::chrono::high_resolution_clock::now() - startTime; if (_animator->getAnimationCount() > 0) { _animator->applyAnimation(0, dur.count() / 1000); } - _animator->updateBoneMatrices(); + _animator->updateBoneMatrices(); */ } // Render the scene, unless the renderer wants to skip the frame. diff --git a/ios/src/FilamentViewer.hpp b/ios/src/FilamentViewer.hpp index 810f2c27..7dc7e483 100644 --- a/ios/src/FilamentViewer.hpp +++ b/ios/src/FilamentViewer.hpp @@ -30,6 +30,8 @@ #include #include +#include "GPUMorphHelper.h" + using namespace std; using namespace filament; using namespace filament::math; @@ -51,13 +53,16 @@ namespace mimetic { class FilamentViewer { public: - FilamentViewer(void* layer, LoadResource loadResource, FreeResource freeResource); + FilamentViewer(void* layer, const char* shaderPath, LoadResource loadResource, FreeResource freeResource); ~FilamentViewer(); - void loadGltf(const char* const uri, const char* relativeResourcePath); + void loadGltf(const char* const uri, const char* relativeResourcePath, const char* materialInstanceName); void loadSkybox(const char* const skyboxUri, const char* const iblUri); void updateViewportAndCameraProjection(int height, int width, float scaleFactor); void render(); + void createMorpher(const char* meshName, const char* entityName, const char* materialInstanceName); Manipulator* manipulator; + GPUMorphHelper* morphHelper; + private: void loadResources(std::string relativeResourcePath); void transformToUnitCube(); @@ -83,6 +88,7 @@ namespace mimetic { FilamentAsset* _asset = nullptr; NameComponentManager* _ncm; + Entity _sun; Texture* _skyboxTexture; Skybox* _skybox; diff --git a/ios/src/morph/DependencyGraph.h b/ios/src/morph/DependencyGraph.h new file mode 100644 index 00000000..854571c2 --- /dev/null +++ b/ios/src/morph/DependencyGraph.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLTFIO_DEPENDENCY_GRAPH_H +#define GLTFIO_DEPENDENCY_GRAPH_H + +#include + +#include +#include + +#include +#include + +namespace filament { + class MaterialInstance; + class Texture; +} + +namespace gltfio { + +/** + * Internal graph that enables FilamentAsset to discover "ready-to-render" entities by tracking + * the loading status of Texture objects that each entity depends on. + * + * Renderables connect to a set of material instances, which in turn connect to a set of parameter + * names, which in turn connect to a set of texture objects. These relationships are not easily + * inspectable using the Filament API or ECS. + * + * One graph corresponds to a single glTF asset. The graph only contains weak references, it does + * not have ownership over any Filament objects. Here's an example: + * + * Entity Entity Entity Entity + * | / \ | / + * | / \ | / + * Material Material Material + * / | \ | + * / | \ | + * Param Param Param Param + * \ / | | + * \ / | | + * Texture Texture Texture + * + * Note that the left-most entity in the above graph has no textures, so it becomes ready as soon as + * finalize is called. + */ +class DependencyGraph { +public: + using Material = filament::MaterialInstance; + using Entity = utils::Entity; + + // Pops up to "count" ready-to-render entities off the queue. + // If "result" is non-null, returns the number of written items. + // If "result" is null, returns the number of available entities. + size_t popRenderables(Entity* result, size_t count) noexcept; + + // These are called during the initial asset loader phase. + void addEdge(Entity entity, Material* material); + void addEdge(Material* material, const char* parameter); + + // This is called at the end of the initial asset loading phase. + // Makes a guarantee that no new material nodes or parameter nodes will be added to the graph. + void finalize(); + + // This can be called after finalization to allow for dynamic addition of entities. + // It is slower than finalize() because it checks the readiness of existing materials. + void refinalize(); + + // These are called after textures have created and decoded. + void addEdge(filament::Texture* texture, Material* material, const char* parameter); + void markAsReady(filament::Texture* texture); + +private: + struct TextureNode { + filament::Texture* texture; + bool ready; + }; + + struct MaterialNode { + tsl::robin_map params; + }; + + struct EntityNode { + tsl::robin_set materials; + size_t numReadyMaterials = 0; + }; + + void checkReadiness(Material* material); + void markAsReady(Material* material); + TextureNode* getStatus(filament::Texture* texture); + + // The following maps contain the directed edges in the graph. + tsl::robin_map mEntityToMaterial; + tsl::robin_map> mMaterialToEntity; + tsl::robin_map mMaterialToTexture; + tsl::robin_map> mTextureToMaterial; + + // Each texture (and its readiness flag) can be referenced from multiple nodes, so we own + // a collection of wrapper objects in the following map. This uses std::unique_ptr to allow + // nodes to refer to a texture wrapper using a stable weak pointer. + tsl::robin_map> mTextureNodes; + + std::queue mReadyRenderables; + bool mFinalized = false; +}; + +} // namespace gltfio + +#endif // GLTFIO_DEPENDENCY_GRAPH_H diff --git a/ios/src/morph/DracoCache.h b/ios/src/morph/DracoCache.h new file mode 100644 index 00000000..88963da8 --- /dev/null +++ b/ios/src/morph/DracoCache.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLTFIO_DRACO_CACHE_H +#define GLTFIO_DRACO_CACHE_H + +#include + +#include + +#include + +#ifndef GLTFIO_DRACO_SUPPORTED +#define GLTFIO_DRACO_SUPPORTED 0 +#endif + +namespace gltfio { + +class DracoMesh; + +// Manages a set of Draco meshes that can be looked up using cgltf_buffer_view. +// +// The cache key is the buffer view that holds the compressed data. This allows the loader to +// avoid duplicated work when a single Draco mesh is referenced from multiple primitives. +class DracoCache { +public: + DracoMesh* findOrCreateMesh(const cgltf_buffer_view* key); +private: + tsl::robin_map> mCache; +}; + +// Decodes a Draco mesh upon construction and retains the results. +// +// The DracoMesh API leverages cgltf accessor structs in a way that bears explanation. These are +// read / write parameters that tell the decoder where to write the decoded data, and what format +// is desired. The buffer_view in the accessor should be null unless decompressed data is already +// loaded. This tells the decoder that it should create a buffer_view and a buffer. The buffer +// view, the buffer, and the buffer's data are all automatically freed when DracoMesh is destroyed. +// +// Note that in the gltfio architecture, the AssetLoader has the job of constructing VertexBuffer +// objects while the ResourceLoader has the job of populating them asychronously. This means that +// our Draco decoder relies on the accessor fields being 100% correct. If we had to be robust +// against faulty accessor information, we would need to replace the VertexBuffer object that was +// created in the AssetLoader, which would be a messy process. +class DracoMesh { +public: + static DracoMesh* decode(const uint8_t* compressedData, size_t compressedSize); + bool getFaceIndices(cgltf_accessor* destination) const; + bool getVertexAttributes(uint32_t attributeId, cgltf_accessor* destination) const; + ~DracoMesh(); +private: + DracoMesh(struct DracoMeshDetails* details); + std::unique_ptr mDetails; +}; + +} // namespace gltfio + +#endif // GLTFIO_DRACO_CACHE_H diff --git a/ios/src/morph/FFilamentAsset.h b/ios/src/morph/FFilamentAsset.h new file mode 100644 index 00000000..7c7dc798 --- /dev/null +++ b/ios/src/morph/FFilamentAsset.h @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLTFIO_FFILAMENTASSET_H +#define GLTFIO_FFILAMENTASSET_H + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include + +#include "upcast.h" +#include "DependencyGraph.h" +#include "DracoCache.h" +#include "FFilamentInstance.h" + +#include +#include + +#include + +#ifdef NDEBUG +#define GLTFIO_VERBOSE 0 +#define GLTFIO_WARN(msg) +#else +#define GLTFIO_VERBOSE 1 +#define GLTFIO_WARN(msg) slog.w << msg << io::endl +#endif + +namespace utils { + class NameComponentManager; + class EntityManager; +} + +namespace gltfio { + +class Animator; +class Wireframe; + +// Encapsulates VertexBuffer::setBufferAt() or IndexBuffer::setBuffer(). +struct BufferSlot { + const cgltf_accessor* accessor; + cgltf_attribute_type attribute; + int bufferIndex; // for vertex buffers only + int morphTarget; // 0 if no morphing, otherwise 1-based index + filament::VertexBuffer* vertexBuffer; + filament::IndexBuffer* indexBuffer; +}; + +// Encapsulates a connection between Texture and MaterialInstance. +struct TextureSlot { + const cgltf_texture* texture; + filament::MaterialInstance* materialInstance; + const char* materialParameter; + filament::TextureSampler sampler; + bool srgb; +}; + +// MeshCache +// --------- +// If a given glTF mesh is referenced by multiple glTF nodes, then it generates a separate Filament +// renderable for each of those nodes. All renderables generated by a given mesh share a common set +// of VertexBuffer and IndexBuffer objects. To achieve the sharing behavior, the loader maintains a +// small cache. The cache keys are glTF mesh definitions and the cache entries are lists of +// primitives, where a "primitive" is a reference to a Filament VertexBuffer and IndexBuffer. +struct Primitive { + filament::VertexBuffer* vertices = nullptr; + filament::IndexBuffer* indices = nullptr; + filament::Aabb aabb; // object-space bounding box + UvMap uvmap; // mapping from each glTF UV set to either UV0 or UV1 (8 bytes) + uint8_t morphPositions[4] = {}; // Buffer indices for MORPH_POSITION_0, MORPH_POSITION_1 etc. + uint8_t morphTangents[4] = {}; // Buffer indices for MORPH_TANGENTS_0, MORPH_TANGENTS_1, etc. +}; +using MeshCache = tsl::robin_map>; + +// MatInstanceCache +// ---------------- +// Each glTF material definition corresponds to a single filament::MaterialInstance, which are +// temporarily cached during loading. The filament::Material objects that are used to create instances are +// cached in MaterialProvider. If a given glTF material is referenced by multiple glTF meshes, then +// their corresponding filament primitives will share the same Filament MaterialInstance and UvMap. +// The UvMap is a mapping from each texcoord slot in glTF to one of Filament's 2 texcoord sets. +struct MaterialEntry { + filament::MaterialInstance* instance; + UvMap uvmap; +}; +using MatInstanceCache = tsl::robin_map; + +struct FFilamentAsset : public FilamentAsset { + FFilamentAsset(filament::Engine* engine, utils::NameComponentManager* names, + utils::EntityManager* entityManager, const cgltf_data* srcAsset) : + mEngine(engine), mNameManager(names), mEntityManager(entityManager) { + mSourceAsset.reset(new SourceAsset {(cgltf_data*)srcAsset}); + } + + ~FFilamentAsset(); + + size_t getEntityCount() const noexcept { + return mEntities.size(); + } + + const utils::Entity* getEntities() const noexcept { + return mEntities.empty() ? nullptr : mEntities.data(); + } + + const utils::Entity* getLightEntities() const noexcept { + return mLightEntities.empty() ? nullptr : mLightEntities.data(); + } + + size_t getLightEntityCount() const noexcept { + return mLightEntities.size(); + } + + const utils::Entity* getCameraEntities() const noexcept { + return mCameraEntities.empty() ? nullptr : mCameraEntities.data(); + } + + size_t getCameraEntityCount() const noexcept { + return mCameraEntities.size(); + } + + utils::Entity getRoot() const noexcept { + return mRoot; + } + + size_t popRenderables(utils::Entity* entities, size_t count) noexcept { + return mDependencyGraph.popRenderables(entities, count); + } + + size_t getMaterialInstanceCount() const noexcept { + return mMaterialInstances.size(); + } + + const filament::MaterialInstance* const* getMaterialInstances() const noexcept { + return mMaterialInstances.data(); + } + + filament::MaterialInstance* const* getMaterialInstances() noexcept { + return mMaterialInstances.data(); + } + + size_t getResourceUriCount() const noexcept { + return mResourceUris.size(); + } + + const char* const* getResourceUris() const noexcept { + return mResourceUris.data(); + } + + filament::Aabb getBoundingBox() const noexcept { + return mBoundingBox; + } + + const char* getName(utils::Entity entity) const noexcept; + + const char* getExtras(utils::Entity entity) const noexcept; + + utils::Entity getFirstEntityByName(const char* name) noexcept; + + size_t getEntitiesByName(const char* name, utils::Entity* entities, + size_t maxCount) const noexcept; + + size_t getEntitiesByPrefix(const char* prefix, utils::Entity* entities, + size_t maxCount) const noexcept; + + Animator* getAnimator() noexcept; + + utils::Entity getWireframe() noexcept; + + filament::Engine* getEngine() const noexcept { + return mEngine; + } + + void releaseSourceData() noexcept; + + const void* getSourceAsset() const noexcept { + return mSourceAsset.get() ? mSourceAsset->hierarchy : nullptr; + } + + FilamentInstance** getAssetInstances() noexcept { + return (FilamentInstance**) mInstances.data(); + } + + size_t getAssetInstanceCount() const noexcept { + return mInstances.size(); + } + + void takeOwnership(filament::Texture* texture) { + mTextures.push_back(texture); + } + + void bindTexture(const TextureSlot& tb, filament::Texture* texture) { + tb.materialInstance->setParameter(tb.materialParameter, texture, tb.sampler); + mDependencyGraph.addEdge(texture, tb.materialInstance, tb.materialParameter); + } + + bool isInstanced() const { + return mInstances.size() > 0; + } + + filament::Engine* mEngine; + utils::NameComponentManager* mNameManager; + utils::EntityManager* mEntityManager; + std::vector mEntities; + std::vector mLightEntities; + std::vector mCameraEntities; + std::vector mMaterialInstances; + std::vector mVertexBuffers; + std::vector mBufferObjects; + std::vector mIndexBuffers; + std::vector mTextures; + filament::Aabb mBoundingBox; + utils::Entity mRoot; + std::vector mInstances; + SkinVector mSkins; // unused for instanced assets + Animator* mAnimator = nullptr; + Wireframe* mWireframe = nullptr; + bool mResourcesLoaded = false; + DependencyGraph mDependencyGraph; + tsl::htrie_map> mNameToEntity; + tsl::robin_map mNodeExtras; + utils::CString mAssetExtras; + + // Sentinels for situations where ResourceLoader needs to generate data. + const cgltf_accessor mGenerateNormals = {}; + const cgltf_accessor mGenerateTangents = {}; + + // Encapsulates reference-counted source data, which includes the cgltf hierachy + // and potentially also includes buffer data that can be uploaded to the GPU. + struct SourceAsset { + ~SourceAsset() { cgltf_free(hierarchy); } + cgltf_data* hierarchy; + DracoCache dracoCache; + utils::FixedCapacityVector glbData; + }; + + // We used shared ownership for the raw cgltf data in order to permit ResourceLoader to + // complete various asynchronous work (e.g. uploading buffers to the GPU) even after the asset + // or ResourceLoader have been destroyed. + using SourceHandle = std::shared_ptr; + SourceHandle mSourceAsset; + + // Transient source data that can freed via releaseSourceData: + std::vector mBufferSlots; + std::vector mTextureSlots; + std::vector mResourceUris; + NodeMap mNodeMap; // unused for instanced assets + std::vector > mPrimitives; + MatInstanceCache mMatInstanceCache; + MeshCache mMeshCache; +}; + +FILAMENT_UPCAST(FilamentAsset) + +} // namespace gltfio + +#endif // GLTFIO_FFILAMENTASSET_H diff --git a/ios/src/morph/FFilamentInstance.h b/ios/src/morph/FFilamentInstance.h new file mode 100644 index 00000000..6487f6d1 --- /dev/null +++ b/ios/src/morph/FFilamentInstance.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLTFIO_FFILAMENTINSTANCE_H +#define GLTFIO_FFILAMENTINSTANCE_H + +#include + +#include + +#include + +#include + +#include +#include + +#include "upcast.h" + +struct cgltf_node; + +namespace gltfio { + +struct FFilamentAsset; +class Animator; + +struct Skin { + std::string name; + std::vector inverseBindMatrices; + std::vector joints; + std::vector targets; +}; + +using SkinVector = std::vector; +using NodeMap = tsl::robin_map; + +struct FFilamentInstance : public FilamentInstance { + std::vector entities; + utils::Entity root; + Animator* animator; + FFilamentAsset* owner; + SkinVector skins; + NodeMap nodeMap; + Animator* getAnimator() noexcept; +}; + +FILAMENT_UPCAST(FilamentInstance) + +} // namespace gltfio + +#endif // GLTFIO_FFILAMENTINSTANCE_H diff --git a/ios/src/morph/GPUMorphHelper.cpp b/ios/src/morph/GPUMorphHelper.cpp new file mode 100644 index 00000000..ef5c65bd --- /dev/null +++ b/ios/src/morph/GPUMorphHelper.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "GPUMorphHelper.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "GltfEnums.h" +#include "TangentsJob.h" + +#include + +#include + +using namespace filament; +using namespace filamat; +using namespace filament::math; +using namespace utils; +namespace gltfio { + + static constexpr uint8_t kUnused = 0xff; + + uint32_t computeBindingSize(const cgltf_accessor *accessor); + + uint32_t computeBindingSize(const cgltf_accessor *accessor); + + uint32_t computeBindingOffset(const cgltf_accessor *accessor); + + static const auto FREE_CALLBACK = [](void *mem, size_t s, void *) { + free(mem); + }; + + GPUMorphHelper::GPUMorphHelper(FFilamentAsset *asset, const char* meshName, const char* entityName, const char* materialInstanceName) : mAsset(asset) { + + cgltf_size num_primitives = 0; + NodeMap &sourceNodes = asset->isInstanced() ? asset->mInstances[0]->nodeMap + : asset->mNodeMap; + + for (auto pair : sourceNodes) { + cgltf_node const *node = pair.first; + cgltf_mesh const *mesh = node->mesh; + + if (mesh) { + std::cout << "Mesh " << mesh->name <name) == 0) { + targetMesh = mesh; + num_primitives = mesh->primitives_count; + for (cgltf_size pi = 0, count = mesh->primitives_count; pi < count; ++pi) { + addPrimitive(mesh, pi, &mMorphTable[pair.second]); + } + } + } + } + + createTextures(); + + + } + + GPUMorphHelper::~GPUMorphHelper() { + + } + + /// + /// Creates the texture that will store the morphable attributes. The texture will be sized according to the total number of vertices in the mesh, meaning all primitives share the same texture. + /// + void GPUMorphHelper::createTextures() { + auto materialInstances = mAsset->getMaterialInstances(); + auto &engine = *(mAsset->mEngine); + + for (auto &entry : mMorphTable) { + for (auto prim : entry.second.primitives) { + // for a single morph target, each vertex will be assigned 2 pixels, corresponding to a position vec3 and a normal vec3 + // these two vectors will be laid out adjacent in memory + // the total texture "width" is the total number of these pixels + // morph targets are then assigned to the depth channel + auto textureWidth = prim.numVertices * 2; + + // the total size of the texture in bytes + // equal to (numVertices * numAttributes * vectorSize (3) * sizeof(float) * numMorphTargets) + auto textureSize = textureWidth * 3 * sizeof(float) * prim.numTargets; + auto textureBuffer = (float *const) malloc(textureSize); + + if(!textureBuffer) { + std::cout << "Error allocating texture buffer" << std::endl; + exit(-1); + } + + uint32_t offset = 0; + + // assume the primitive morph target source buffer is laid out like: + // |target0_v0_pos * 3|target0_v0_norm * 3|target0_v1_pos * 3|target0_v1_norm * 3|...|target1_v0_pos * 3|target1_v0_norm * 3|target1_v1_pos * 3|target1_v1_norm * 3|... + // where: + // - target0/target1/etc is the first/second/etc morph target + // - v0/v1/etc is the first/second/etc vertex + // - pos/norm are each 3-float vectors + for (auto &target : prim.targets) { + if(target.type == cgltf_attribute_type_position + || target.type == cgltf_attribute_type_normal + ) { + memcpy(textureBuffer+offset, target.bufferObject, target.bufferSize); + offset += int(target.bufferSize / sizeof(float)); + } + } + + prim.texture = Texture::Builder() + .width(textureWidth) // + .height(1) + .depth(prim.numTargets) + .sampler(Texture::Sampler::SAMPLER_2D_ARRAY) + .format(Texture::InternalFormat::RGB32F) + .levels(0x01) + .build(engine); + + Texture::PixelBufferDescriptor descriptor( + textureBuffer, + textureSize, + Texture::Format::RGB, + Texture::Type::FLOAT, + FREE_CALLBACK, + nullptr); + prim.texture->setImage(engine, 0, 0,0, 0, textureWidth, 1, prim.numTargets, std::move(descriptor)); + + for(int i = 0; i < mAsset->getMaterialInstanceCount(); i++) { + const char* name = materialInstances[i]->getName(); + std::cout << name << std::endl; + if(strcmp(name, prim.materialName) == 0) { + prim.materialInstance = materialInstances[i]; + break; + } + } + + if(!prim.materialInstance) { + exit(-1); + } + + // this won't work if material instance is shared between primitives? + prim.materialInstance->setParameter("dimensions", filament::math::int3 { prim.numVertices * 2, numAttributes, prim.numTargets }); + prim.materialInstance->setParameter("morphTargets", prim.texture, TextureSampler()); + float weights[prim.numTargets]; + memset(weights, 0, prim.numTargets * sizeof(float)); + prim.materialInstance->setParameter("morphTargetWeights", weights, prim.numTargets); + } + } + } + + void GPUMorphHelper::applyWeights(float const *weights, size_t count, int primitiveIndex) noexcept { + auto materialInstance = mAsset->getMaterialInstances()[primitiveIndex]; + materialInstance->setParameter("morphTargetWeights", weights, count); +// assert(count <= numTargets); + } + + void + GPUMorphHelper::addPrimitive(cgltf_mesh const *mesh, int primitiveIndex, TableEntry *entry) { + auto &engine = *mAsset->mEngine; + const cgltf_primitive &prim = mesh->primitives[primitiveIndex]; + + const auto &gltfioPrim = mAsset->mMeshCache.at(mesh)[primitiveIndex]; + VertexBuffer *vertexBuffer = gltfioPrim.vertices; + + entry->primitives.push_back({vertexBuffer}); + + auto &morphHelperPrim = entry->primitives.back(); + morphHelperPrim.materialName = prim.material->name; + morphHelperPrim.numTargets = prim.targets_count; + morphHelperPrim.numVertices = vertexBuffer->getVertexCount(); + cgltf_size maxIndex = 0; + for(int i = 0; i < prim.indices->count; i++) { + maxIndex = std::max(cgltf_accessor_read_index(prim.indices, i), maxIndex); + } + + std::cout << "Max index for primitive index " << primitiveIndex << " is " << maxIndex << " and numVertices was " << morphHelperPrim.numVertices << std::endl; + + + const cgltf_accessor *previous = nullptr; + + // for this primitive, iterate over every target + for (int targetIndex = 0; targetIndex < prim.targets_count; targetIndex++) { + const cgltf_morph_target &morphTarget = prim.targets[targetIndex]; + for (cgltf_size aindex = 0; aindex < morphTarget.attributes_count; aindex++) { + const cgltf_attribute &attribute = morphTarget.attributes[aindex]; + const cgltf_accessor *accessor = attribute.data; + const cgltf_attribute_type atype = attribute.type; + if (atype == cgltf_attribute_type_tangent) { + continue; + } + if ( + atype == cgltf_attribute_type_normal || + atype == cgltf_attribute_type_position + ) { + + // + // the texture needs to be sized according to the total number of vertices in the mesh + // this is identified by the highest vertex index of all primitives in the mesh + // +// if(numVertices == 0) + // numVertices = accessor->count; +//assert(numVertices == accessor->count); + + // All position & normal attributes must have the same data type. + assert_invariant( + !previous || previous->component_type == accessor->component_type); + assert_invariant(!previous || previous->type == accessor->type); + previous = accessor; + + // This should always be non-null, but don't crash if the glTF is malformed. + if (accessor->buffer_view) { + auto bufferData = (const uint8_t *) accessor->buffer_view->buffer->data; + assert_invariant(bufferData); + const uint8_t *data = computeBindingOffset(accessor) + bufferData; + const uint32_t size = computeBindingSize(accessor); + morphHelperPrim.targets.push_back({data, size, targetIndex, atype}); + } + } + } + } + } +} + + +//VertexBuffer* vBuf = VertexBuffer::Builder() +// .vertexCount(numVertices) +// .bufferCount(numPrimitives) +// .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT4, 0) +// .build(*engine); + +//numIndices = maxIndex+1; */ +//numIndices = prim.indices->count; + +/*indicesBuffer = (uint32_t*)malloc(sizeof(unsigned int) * prim.indices->count); + +//materialInstance->setParameter("vertexIndices", indicesBuffer, numIndices); + +//.require(VertexAttribute::UV0) +//.require(MaterialBuilder.VertexAttribute.CUSTOM0) +//MaterialBuilder::init(); +//MaterialBuilder builder = MaterialBuilder() +// .name("DefaultMaterial") +// .platform(MaterialBuilder::Platform::MOBILE) +// .targetApi(MaterialBuilder::TargetApi::ALL) +// .optimization(MaterialBuilderBase::Optimization::NONE) +// .shading(MaterialBuilder::Shading::LIT) +// .parameter(MaterialBuilder::UniformType::FLOAT3, "baseColor") +// .parameter(MaterialBuilder::UniformType::INT3, "dimensions") +// .parameter(MaterialBuilder::UniformType::FLOAT, numTargets, MaterialBuilder::ParameterPrecision::DEFAULT, "morphTargetWeights") +// .parameter(MaterialBuilder::SamplerType::SAMPLER_2D_ARRAY, MaterialBuilder::SamplerFormat::FLOAT, MaterialBuilder::ParameterPrecision::DEFAULT, "morphTargets") +// .vertexDomain(VertexDomain::WORLD) +// .material(R"SHADER(void material(inout MaterialInputs material) { +// prepareMaterial(material); +// material.baseColor.rgb = materialParams.baseColor; +// })SHADER") +// .materialVertex(R"SHADER( +// vec3 getMorphTarget(int vertexIndex, int morphTargetIndex) { +// // our texture is laid out as (x,y,z) where y is 1, z is the number of morph targets, and x is the number of vertices * 2 (multiplication accounts for position + normal) +// // UV coordinates are normalized to (-1,1), so we divide the current vertex index by the total number of vertices to find the correct coordinate for this vertex +// vec3 uv = vec3( +// (float(vertexIndex) + 0.5) / float(materialParams.dimensions.x), +// 0.0f, +// //(float(morphTargetIndex) + 0.5f) / float(materialParams.dimensions.z)); +// float(morphTargetIndex)); +// return texture(materialParams_morphTargets, uv).xyz; +// } +// +// void materialVertex(inout MaterialVertexInputs material) { +// return; +// // for every morph target +// for(int morphTargetIndex = 0; morphTargetIndex < materialParams.dimensions.z; morphTargetIndex++) { +// +// // get the weight to apply +// float weight = materialParams.morphTargetWeights[morphTargetIndex]; +// +// // get the ID of this vertex, which will be the x-offset of the position attribute in the texture sampler +// int vertexId = getVertexIndex(); +// +// // get the position of the target for this vertex +// vec3 morphTargetPosition = getMorphTarget(vertexId, morphTargetIndex); +// // update the world position of this vertex +// material.worldPosition.xyz += (weight * morphTargetPosition); +// +// // increment the vertexID by half the size of the texture to get the x-offset of the normal (all positions stored in the first half, all normals stored in the second half) +// +// vertexId += (materialParams.dimensions.x / 2); +// +// // get the normal of this target for this vertex +// vec3 morphTargetNormal = getMorphTarget(vertexId, morphTargetIndex); +// material.worldNormal += (weight * morphTargetNormal); +// } +// mat4 transform = getWorldFromModelMatrix(); +// material.worldPosition = mulMat4x4Float3(transform, material.worldPosition.xyz); +// })SHADER"); +// +//Package pkg = builder.build(mAsset->mEngine->getJobSystem()); +//Material* material = Material::Builder().package(pkg.getData(), pkg.getSize()) +// .build(*mAsset->mEngine); + +//size_t normal_size = sizeof(short4); +//assert(textureWidth * (position_size + normal_size) == textureSize); +//assert(textureWidth * position_size == textureSize); +/*__android_log_print(ANDROID_LOG_INFO, "MyTag", "Expected size %d width at level 0 %d height", Texture::PixelBufferDescriptor::computeDataSize(Texture::Format::RGB, + Texture::Type::FLOAT, 24, 1, 4), texture->getWidth(0),texture->getHeight(0)); */ + +/* Texture::PixelBufferDescriptor descriptor( + textureBuffer, + textureSize, + Texture::Format::RGB, + Texture::Type::FLOAT, + 4, 0,0, 24, + FREE_CALLBACK, + nullptr); */ + +/*for(int i = 0; i < int(textureSize / sizeof(float)); i++) { + __android_log_print(ANDROID_LOG_INFO, "MyTag", "offset %d %f", i, *(textureBuffer+i)); +//}*/ +//std::cout << "Checking for " << materialInstanceName << std::endl; +//if(materialInstanceName) { +// for(int i = 0; i < asset->getMaterialInstanceCount(); i++) { +// const char* name = instances[i]->getName(); +// std::cout << name << std::endl; +// if(strcmp(name, materialInstanceName) == 0) { +// materialInstance = instances[i]; +// break; +// } +// } +//} else { +// materialInstance = instances[0]; +//} +// +//if(!materialInstance) { +// exit(-1); +//} + +// std::cout << std::endl; + /* for (int i = 0; i < 4; i++) { + morphHelperPrim.positions[i] = gltfioPrim.morphPositions[i]; + morphHelperPrim.tangents[i] = gltfioPrim.morphTangents[i]; + } */ + +// applyTextures(materialInstance); + +/* const Entity* entities = mAsset->getEntities(); + for(int i=0; i < mAsset->getEntityCount();i++) { + std::cout << mAsset->getName(entities[i]); + } */ + +// Entity entity = mAsset->getFirstEntityByName(entityName); +// RenderableManager::Instance rInst = mAsset->mEngine->getRenderableManager().getInstance(entity); +// for(int i = 0; imEngine->getRenderableManager().setMaterialInstanceAt(rInst, i, materialInstance); +// } diff --git a/ios/src/morph/GPUMorphHelper.h b/ios/src/morph/GPUMorphHelper.h new file mode 100644 index 00000000..5c0fb586 --- /dev/null +++ b/ios/src/morph/GPUMorphHelper.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "FFilamentAsset.h" +#include "FFilamentInstance.h" +#include "filament/Texture.h" +#include "filament/Engine.h" +#include + +#include + +#include + +using namespace filament; + +struct cgltf_node; +struct cgltf_mesh; +struct cgltf_primitive; + +namespace gltfio { + + /// + /// A GPUMorphHelper instance can be created per mesh (this avoids creating textures for meshes that do not require animation). + /// For each primitive in the mesh, a texture is created to store the target positions and normals. + /// The texture is laid out as x * 1 * z, where z is the number of morph targets and x is the number of vertices for the primitive. + /// A MaterialInstance is created for each primitive, then applied to the entity identified by entityName. + /// + class GPUMorphHelper { + public: + using Entity = utils::Entity; + + GPUMorphHelper(FFilamentAsset *asset, const char* meshName, const char* entityName, const char* materialInstanceName); + + ~GPUMorphHelper(); + + void applyWeights(float const *weights, size_t count, int primitiveIndex) noexcept; + + private: + struct GltfTarget { + const void *bufferObject; + uint32_t bufferSize; + int morphTargetIndex; + cgltf_attribute_type type; + }; + + struct GltfPrimitive { + filament::VertexBuffer *vertexBuffer; + Texture* texture; + std::vector targets; // TODO: flatten this? + const char* materialName; + cgltf_size numTargets = 0; + cgltf_size numVertices = 0; + MaterialInstance* materialInstance = nullptr; + }; + + struct TableEntry { + std::vector primitives; // TODO: flatten this? + }; + + int numAttributes = 2; // position & normal + + uint32_t* indicesBuffer = nullptr; + + void addPrimitive(cgltf_mesh const *mesh, int primitiveIndex, TableEntry *entry); + + void createTextures(); + + + cgltf_mesh const* targetMesh; + + tsl::robin_map mMorphTable; + FFilamentAsset *mAsset; + }; +} diff --git a/ios/src/morph/GPUMorphShaderLoader.cpp b/ios/src/morph/GPUMorphShaderLoader.cpp new file mode 100644 index 00000000..e7ec448a --- /dev/null +++ b/ios/src/morph/GPUMorphShaderLoader.cpp @@ -0,0 +1,235 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +#include "gltfio/resources/gltfresources_lite.h" + +using namespace filament; +using namespace filament::math; +using namespace gltfio; +using namespace utils; + +namespace { + + using CullingMode = MaterialInstance::CullingMode; + + class GPUMorphShaderLoader : public MaterialProvider { + public: + GPUMorphShaderLoader(const void* mData, uint64_t size, filament::Engine* engine); + ~GPUMorphShaderLoader() {} + + MaterialInstance* createMaterialInstance(MaterialKey* config, UvMap* uvmap, + const char* label) override; + + size_t getMaterialsCount() const noexcept override; + const Material* const* getMaterials() const noexcept override; + void destroyMaterials() override; + + bool needsDummyData(VertexAttribute attrib) const noexcept override { + switch (attrib) { + case VertexAttribute::UV0: + case VertexAttribute::UV1: + case VertexAttribute::COLOR: + return true; + default: + return false; + } + } + const void* mData; + const size_t mSize; + + Material* getMaterial(const MaterialKey& config) const; + + enum ShadingMode { + UNLIT = 0, + LIT = 1, + SPECULAR_GLOSSINESS = 2, + }; + + mutable Material* mMaterials[12] = {}; + Texture* mDummyTexture = nullptr; + + Engine* mEngine; + }; + + GPUMorphShaderLoader::GPUMorphShaderLoader(const void* data, uint64_t size, Engine* engine) : mData(data), mSize(size), mEngine(engine) { + + unsigned char texels[4] = {}; + mDummyTexture = Texture::Builder() + .width(1).height(1) + .format(Texture::InternalFormat::RGBA8) + .build(*mEngine); + Texture::PixelBufferDescriptor pbd(texels, sizeof(texels), Texture::Format::RGBA, + Texture::Type::UBYTE); + mDummyTexture->setImage(*mEngine, 0, std::move(pbd)); + } + + size_t GPUMorphShaderLoader::getMaterialsCount() const noexcept { + return sizeof(mMaterials) / sizeof(mMaterials[0]); + } + + const Material* const* GPUMorphShaderLoader::getMaterials() const noexcept { + return &mMaterials[0]; + } + + void GPUMorphShaderLoader::destroyMaterials() { + for (auto& material : mMaterials) { + mEngine->destroy(material); + material = nullptr; + } + mEngine->destroy(mDummyTexture); + } + + Material* GPUMorphShaderLoader::getMaterial(const MaterialKey& config) const { + const ShadingMode shading = config.unlit ? UNLIT : + (config.useSpecularGlossiness ? SPECULAR_GLOSSINESS : LIT); + const int matindex = 0; + if (mMaterials[matindex] != nullptr) { + return mMaterials[matindex]; + } + + filamat::Package pkg = filamat::Package(mData, mSize); + return Material::Builder().package(pkg.getData(), pkg.getSize()).build(*mEngine); + } + + MaterialInstance* GPUMorphShaderLoader::createMaterialInstance(MaterialKey* config, UvMap* uvmap, + const char* label) { + // Diagnostics are not supported with LOAD_UBERSHADERS, please use GENERATE_SHADERS instead. + if (config->enableDiagnostics) { + return nullptr; + } + + if (config->hasVolume && config->hasSheen) { + slog.w << "Volume and sheen are not supported together in ubershader mode," + " removing sheen (" << label << ")." << io::endl; + config->hasSheen = false; + } + + if (config->hasTransmission && config->hasSheen) { + slog.w << "Transmission and sheen are not supported together in ubershader mode," + " removing sheen (" << label << ")." << io::endl; + config->hasSheen = false; + } + + const bool clearCoatConflict = config->hasVolume || config->hasTransmission || config->hasSheen; + + // Due to sampler overload, disable transmission if necessary and print a friendly warning. + if (config->hasClearCoat && clearCoatConflict) { + slog.w << "Volume, transmission and sheen are not supported in ubershader mode for clearcoat" + " materials (" << label << ")." << io::endl; + config->hasVolume = false; + config->hasTransmission = false; + config->hasSheen = false; + } + + constrainMaterial(config, uvmap); + auto getUvIndex = [uvmap](uint8_t srcIndex, bool hasTexture) -> int { + return hasTexture ? int(uvmap->at(srcIndex)) - 1 : -1; + }; + Material* material = getMaterial(*config); + MaterialInstance* mi = material->createInstance(label); + mi->setParameter("baseColorIndex", + getUvIndex(config->baseColorUV, config->hasBaseColorTexture)); + mi->setParameter("normalIndex", getUvIndex(config->normalUV, config->hasNormalTexture)); + mi->setParameter("metallicRoughnessIndex", + getUvIndex(config->metallicRoughnessUV, config->hasMetallicRoughnessTexture)); + mi->setParameter("aoIndex", getUvIndex(config->aoUV, config->hasOcclusionTexture)); + mi->setParameter("emissiveIndex", getUvIndex(config->emissiveUV, config->hasEmissiveTexture)); + + mi->setDoubleSided(config->doubleSided); + mi->setCullingMode(config->doubleSided ? CullingMode::NONE : CullingMode::BACK); + + // Initially, assume that the clear coat texture can be honored. This is changed to false when + // running into a sampler count limitation. TODO: check if these constraints can now be relaxed. + bool clearCoatNeedsTexture = true; + + mat3f identity; + mi->setParameter("baseColorUvMatrix", identity); + mi->setParameter("metallicRoughnessUvMatrix", identity); + mi->setParameter("normalUvMatrix", identity); + mi->setParameter("occlusionUvMatrix", identity); + mi->setParameter("emissiveUvMatrix", identity); + + if (config->hasClearCoat) { + mi->setParameter("clearCoatIndex", + getUvIndex(config->clearCoatUV, config->hasClearCoatTexture)); + mi->setParameter("clearCoatRoughnessIndex", + getUvIndex(config->clearCoatRoughnessUV, config->hasClearCoatRoughnessTexture)); + mi->setParameter("clearCoatNormalIndex", + getUvIndex(config->clearCoatNormalUV, config->hasClearCoatNormalTexture)); + mi->setParameter("clearCoatUvMatrix", identity); + mi->setParameter("clearCoatRoughnessUvMatrix", identity); + mi->setParameter("clearCoatNormalUvMatrix", identity); + } else { + if (config->hasSheen) { + clearCoatNeedsTexture = false; + mi->setParameter("sheenColorIndex", + getUvIndex(config->sheenColorUV, config->hasSheenColorTexture)); + mi->setParameter("sheenRoughnessIndex", + getUvIndex(config->sheenRoughnessUV, config->hasSheenRoughnessTexture)); + mi->setParameter("sheenColorUvMatrix", identity); + mi->setParameter("sheenRoughnessUvMatrix", identity); + + } + if (config->hasVolume) { + clearCoatNeedsTexture = false; + mi->setParameter("volumeThicknessUvMatrix", identity); + mi->setParameter("volumeThicknessIndex", + getUvIndex(config->transmissionUV, config->hasVolumeThicknessTexture)); + } + if (config->hasTransmission) { + clearCoatNeedsTexture = false; + mi->setParameter("transmissionUvMatrix", identity); + mi->setParameter("transmissionIndex", + getUvIndex(config->transmissionUV, config->hasTransmissionTexture)); + } + } + + TextureSampler sampler; + mi->setParameter("normalMap", mDummyTexture, sampler); + mi->setParameter("baseColorMap", mDummyTexture, sampler); + mi->setParameter("metallicRoughnessMap", mDummyTexture, sampler); + mi->setParameter("occlusionMap", mDummyTexture, sampler); + mi->setParameter("emissiveMap", mDummyTexture, sampler); + if (clearCoatNeedsTexture) { + mi->setParameter("clearCoatMap", mDummyTexture, sampler); + mi->setParameter("clearCoatRoughnessMap", mDummyTexture, sampler); + mi->setParameter("clearCoatNormalMap", mDummyTexture, sampler); + } + if (!config->hasClearCoat) { + if (config->hasTransmission) { + mi->setParameter("transmissionMap", mDummyTexture, sampler); + } + if (config->hasSheen) { + mi->setParameter("sheenColorMap", mDummyTexture, sampler); + mi->setParameter("sheenRoughnessMap", mDummyTexture, sampler); + } + } + + if (mi->getMaterial()->hasParameter("ior")) { + mi->setParameter("ior", 1.5f); + } + if (mi->getMaterial()->hasParameter("reflectance")) { + mi->setParameter("reflectance", 0.5f); + } + + return mi; + } + +} // anonymous namespace + +namespace gltfio { + + MaterialProvider* createGPUMorphShaderLoader(const void* data, uint64_t size, filament::Engine* engine) { + return new GPUMorphShaderLoader(data, size,engine); + } + +} // namespace gltfio + diff --git a/ios/src/morph/GltfEnums.h b/ios/src/morph/GltfEnums.h new file mode 100644 index 00000000..ad6a34aa --- /dev/null +++ b/ios/src/morph/GltfEnums.h @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLTFIO_GLTFENUMS_H +#define GLTFIO_GLTFENUMS_H + +#include +#include +#include +#include + +#include + +#define GL_NEAREST 0x2600 +#define GL_LINEAR 0x2601 +#define GL_NEAREST_MIPMAP_NEAREST 0x2700 +#define GL_LINEAR_MIPMAP_NEAREST 0x2701 +#define GL_NEAREST_MIPMAP_LINEAR 0x2702 +#define GL_LINEAR_MIPMAP_LINEAR 0x2703 +#define GL_REPEAT 0x2901 +#define GL_MIRRORED_REPEAT 0x8370 +#define GL_CLAMP_TO_EDGE 0x812F + +inline filament::TextureSampler::WrapMode getWrapMode(cgltf_int wrap) { + switch (wrap) { + case GL_REPEAT: + return filament::TextureSampler::WrapMode::REPEAT; + case GL_MIRRORED_REPEAT: + return filament::TextureSampler::WrapMode::MIRRORED_REPEAT; + case GL_CLAMP_TO_EDGE: + return filament::TextureSampler::WrapMode::CLAMP_TO_EDGE; + } + return filament::TextureSampler::WrapMode::REPEAT; +} + +inline filament::TextureSampler::MinFilter getMinFilter(cgltf_int minFilter) { + switch (minFilter) { + case GL_NEAREST: + return filament::TextureSampler::MinFilter::NEAREST; + case GL_LINEAR: + return filament::TextureSampler::MinFilter::LINEAR; + case GL_NEAREST_MIPMAP_NEAREST: + return filament::TextureSampler::MinFilter::NEAREST_MIPMAP_NEAREST; + case GL_LINEAR_MIPMAP_NEAREST: + return filament::TextureSampler::MinFilter::LINEAR_MIPMAP_NEAREST; + case GL_NEAREST_MIPMAP_LINEAR: + return filament::TextureSampler::MinFilter::NEAREST_MIPMAP_LINEAR; + case GL_LINEAR_MIPMAP_LINEAR: + return filament::TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR; + } + return filament::TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR; +} + +inline filament::TextureSampler::MagFilter getMagFilter(cgltf_int magFilter) { + switch (magFilter) { + case GL_NEAREST: + return filament::TextureSampler::MagFilter::NEAREST; + case GL_LINEAR: + return filament::TextureSampler::MagFilter::LINEAR; + } + return filament::TextureSampler::MagFilter::LINEAR; +} + +inline bool getVertexAttrType(cgltf_attribute_type atype, filament::VertexAttribute* attrType) { + switch (atype) { + case cgltf_attribute_type_position: + *attrType = filament::VertexAttribute::POSITION; + return true; + case cgltf_attribute_type_texcoord: + *attrType = filament::VertexAttribute::UV0; + return true; + case cgltf_attribute_type_color: + *attrType = filament::VertexAttribute::COLOR; + return true; + case cgltf_attribute_type_joints: + *attrType = filament::VertexAttribute::BONE_INDICES; + return true; + case cgltf_attribute_type_weights: + *attrType = filament::VertexAttribute::BONE_WEIGHTS; + return true; + case cgltf_attribute_type_normal: + case cgltf_attribute_type_tangent: + default: + return false; + } +} + +inline bool getIndexType(cgltf_component_type ctype, filament::IndexBuffer::IndexType* itype) { + switch (ctype) { + case cgltf_component_type_r_8u: + case cgltf_component_type_r_16u: + *itype = filament::IndexBuffer::IndexType::USHORT; + return true; + case cgltf_component_type_r_32u: + *itype = filament::IndexBuffer::IndexType::UINT; + return true; + default: + break; + } + return false; +} + +inline bool getPrimitiveType(cgltf_primitive_type in, + filament::RenderableManager::PrimitiveType* out) { + switch (in) { + case cgltf_primitive_type_points: + *out = filament::RenderableManager::PrimitiveType::POINTS; + return true; + case cgltf_primitive_type_lines: + *out = filament::RenderableManager::PrimitiveType::LINES; + return true; + case cgltf_primitive_type_triangles: + *out = filament::RenderableManager::PrimitiveType::TRIANGLES; + return true; + case cgltf_primitive_type_line_loop: + case cgltf_primitive_type_line_strip: + case cgltf_primitive_type_triangle_strip: + case cgltf_primitive_type_triangle_fan: + return false; + } + return false; +} + +// This converts a cgltf component type into a Filament Attribute type. +// +// This function has two out parameters. One result is a safe "permitted type" which we know is +// universally accepted across GPU's and backends, but may require conversion (see Transcoder). The +// other result is the "actual type" which requires no conversion. +// +// Returns false if the given component type is invalid. +inline bool getElementType(cgltf_type type, cgltf_component_type ctype, + filament::VertexBuffer::AttributeType* permitType, + filament::VertexBuffer::AttributeType* actualType) { + switch (type) { + case cgltf_type_scalar: + switch (ctype) { + case cgltf_component_type_r_8: + *permitType = filament::VertexBuffer::AttributeType::BYTE; + *actualType = filament::VertexBuffer::AttributeType::BYTE; + return true; + case cgltf_component_type_r_8u: + *permitType = filament::VertexBuffer::AttributeType::UBYTE; + *actualType = filament::VertexBuffer::AttributeType::UBYTE; + return true; + case cgltf_component_type_r_16: + *permitType = filament::VertexBuffer::AttributeType::SHORT; + *actualType = filament::VertexBuffer::AttributeType::SHORT; + return true; + case cgltf_component_type_r_16u: + *permitType = filament::VertexBuffer::AttributeType::USHORT; + *actualType = filament::VertexBuffer::AttributeType::USHORT; + return true; + case cgltf_component_type_r_32u: + *permitType = filament::VertexBuffer::AttributeType::UINT; + *actualType = filament::VertexBuffer::AttributeType::UINT; + return true; + case cgltf_component_type_r_32f: + *permitType = filament::VertexBuffer::AttributeType::FLOAT; + *actualType = filament::VertexBuffer::AttributeType::FLOAT; + return true; + default: + return false; + } + break; + case cgltf_type_vec2: + switch (ctype) { + case cgltf_component_type_r_8: + *permitType = filament::VertexBuffer::AttributeType::BYTE2; + *actualType = filament::VertexBuffer::AttributeType::BYTE2; + return true; + case cgltf_component_type_r_8u: + *permitType = filament::VertexBuffer::AttributeType::UBYTE2; + *actualType = filament::VertexBuffer::AttributeType::UBYTE2; + return true; + case cgltf_component_type_r_16: + *permitType = filament::VertexBuffer::AttributeType::SHORT2; + *actualType = filament::VertexBuffer::AttributeType::SHORT2; + return true; + case cgltf_component_type_r_16u: + *permitType = filament::VertexBuffer::AttributeType::USHORT2; + *actualType = filament::VertexBuffer::AttributeType::USHORT2; + return true; + case cgltf_component_type_r_32f: + *permitType = filament::VertexBuffer::AttributeType::FLOAT2; + *actualType = filament::VertexBuffer::AttributeType::FLOAT2; + return true; + default: + return false; + } + break; + case cgltf_type_vec3: + switch (ctype) { + case cgltf_component_type_r_8: + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::BYTE3; + return true; + case cgltf_component_type_r_8u: + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::UBYTE3; + return true; + case cgltf_component_type_r_16: + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::SHORT3; + return true; + case cgltf_component_type_r_16u: + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::USHORT3; + return true; + case cgltf_component_type_r_32f: + *permitType = filament::VertexBuffer::AttributeType::FLOAT3; + *actualType = filament::VertexBuffer::AttributeType::FLOAT3; + return true; + default: + return false; + } + break; + case cgltf_type_vec4: + switch (ctype) { + case cgltf_component_type_r_8: + *permitType = filament::VertexBuffer::AttributeType::BYTE4; + *actualType = filament::VertexBuffer::AttributeType::BYTE4; + return true; + case cgltf_component_type_r_8u: + *permitType = filament::VertexBuffer::AttributeType::UBYTE4; + *actualType = filament::VertexBuffer::AttributeType::UBYTE4; + return true; + case cgltf_component_type_r_16: + *permitType = filament::VertexBuffer::AttributeType::SHORT4; + *actualType = filament::VertexBuffer::AttributeType::SHORT4; + return true; + case cgltf_component_type_r_16u: + *permitType = filament::VertexBuffer::AttributeType::USHORT4; + *actualType = filament::VertexBuffer::AttributeType::USHORT4; + return true; + case cgltf_component_type_r_32f: + *permitType = filament::VertexBuffer::AttributeType::FLOAT4; + *actualType = filament::VertexBuffer::AttributeType::FLOAT4; + return true; + default: + return false; + } + break; + default: + return false; + } + return false; +} + +inline bool requiresConversion(cgltf_type type, cgltf_component_type ctype) { + filament::VertexBuffer::AttributeType permitted; + filament::VertexBuffer::AttributeType actual; + bool supported = getElementType(type, ctype, &permitted, &actual); + return supported && permitted != actual; +} + +#endif // GLTFIO_GLTFENUMS_H diff --git a/ios/src/morph/GltfHelpers.h b/ios/src/morph/GltfHelpers.h new file mode 100644 index 00000000..6a684233 --- /dev/null +++ b/ios/src/morph/GltfHelpers.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef GLTFIO_GLTFHELPERS_H +#define GLTFIO_GLTFHELPERS_H + +#include + +static const uint8_t* cgltf_buffer_view_data(const cgltf_buffer_view* view) { + if (view->data) + return (const uint8_t*)view->data; + + if (!view->buffer->data) + return NULL; + + const uint8_t* result = (const uint8_t*)view->buffer->data; + result += view->offset; + return result; +} + +static cgltf_size cgltf_component_size(cgltf_component_type component_type) { + switch (component_type) + { + case cgltf_component_type_r_8: + case cgltf_component_type_r_8u: + return 1; + case cgltf_component_type_r_16: + case cgltf_component_type_r_16u: + return 2; + case cgltf_component_type_r_32u: + case cgltf_component_type_r_32f: + return 4; + case cgltf_component_type_invalid: + default: + return 0; + } +} + +static cgltf_size cgltf_component_read_index(const void* in, cgltf_component_type component_type) { + switch (component_type) + { + case cgltf_component_type_r_16: + return *((const int16_t*) in); + case cgltf_component_type_r_16u: + return *((const uint16_t*) in); + case cgltf_component_type_r_32u: + return *((const uint32_t*) in); + case cgltf_component_type_r_32f: + return (cgltf_size)*((const float*) in); + case cgltf_component_type_r_8: + return *((const int8_t*) in); + case cgltf_component_type_r_8u: + return *((const uint8_t*) in); + default: + return 0; + } +} + +static cgltf_float cgltf_component_read_float(const void* in, cgltf_component_type component_type, + cgltf_bool normalized) { + if (component_type == cgltf_component_type_r_32f) + { + return *((const float*) in); + } + + if (normalized) + { + switch (component_type) + { + // note: glTF spec doesn't currently define normalized conversions for 32-bit integers + case cgltf_component_type_r_16: + return *((const int16_t*) in) / (cgltf_float)32767; + case cgltf_component_type_r_16u: + return *((const uint16_t*) in) / (cgltf_float)65535; + case cgltf_component_type_r_8: + return *((const int8_t*) in) / (cgltf_float)127; + case cgltf_component_type_r_8u: + return *((const uint8_t*) in) / (cgltf_float)255; + default: + return 0; + } + } + + return (cgltf_float)cgltf_component_read_index(in, component_type); +} + +static cgltf_bool cgltf_element_read_float(const uint8_t* element, cgltf_type type, + cgltf_component_type component_type, cgltf_bool normalized, cgltf_float* out, + cgltf_size element_size) { + cgltf_size num_components = cgltf_num_components(type); + + if (element_size < num_components) { + return 0; + } + + // There are three special cases for component extraction, see #data-alignment in the 2.0 spec. + + cgltf_size component_size = cgltf_component_size(component_type); + + for (cgltf_size i = 0; i < num_components; ++i) + { + out[i] = cgltf_component_read_float(element + component_size * i, component_type, normalized); + } + return 1; +} + +#endif // GLTFIO_GLTFHELPERS_H diff --git a/ios/src/morph/Log.h b/ios/src/morph/Log.h new file mode 100644 index 00000000..dcf7a1cb --- /dev/null +++ b/ios/src/morph/Log.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_UTILS_LOG_H +#define TNT_UTILS_LOG_H + +#include +#include + +namespace utils { + +struct UTILS_PUBLIC Loggers { + // DEBUG level logging stream + io::ostream& d; + + // ERROR level logging stream + io::ostream& e; + + // WARNING level logging stream + io::ostream& w; + + // INFORMATION level logging stream + io::ostream& i; +}; + +extern UTILS_PUBLIC Loggers const slog; + +} // namespace utils + +#endif // TNT_UTILS_LOG_H diff --git a/ios/src/morph/TangentsJob.h b/ios/src/morph/TangentsJob.h new file mode 100644 index 00000000..06e8f67f --- /dev/null +++ b/ios/src/morph/TangentsJob.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include + +#include + +namespace filament { class VertexBuffer; } + +namespace gltfio { + +/** + * Internal helper that examines a cgltf primitive and generates data suitable for Filament's + * TANGENTS attribute. This has been designed to be run as a JobSystem job, but clients are not + * required to do so. + */ +struct TangentsJob { + static constexpr int kMorphTargetUnused = -1; + + // The inputs to the procedure. The prim is owned by the client, which should ensure that it + // stays alive for the duration of the procedure. + struct InputParams { + const cgltf_primitive* prim; + const int morphTargetIndex = kMorphTargetUnused; + }; + + // The context of the procedure. These fields are not used by the procedure but are provided as + // a convenience to clients. You can think of this as a scratch space for clients. + struct Context { + filament::VertexBuffer* const vb; + const uint8_t slot; + }; + + // The outputs of the procedure. The results array gets malloc'd by the procedure, so clients + // should remember to free it. + struct OutputParams { + cgltf_size vertexCount; + filament::math::short4* results; + }; + + // Clients might want to track the jobs in an array, so the arguments are bundled into a struct. + struct Params { + InputParams in; + Context context; + OutputParams out; + }; + + // Performs tangents generation synchronously. This can be invoked from inside a job if desired. + // The parameters structure is owned by the client. + static void run(Params* params); +}; + +} // namespace gltfio diff --git a/ios/src/morph/upcast.h b/ios/src/morph/upcast.h new file mode 100644 index 00000000..dd454086 --- /dev/null +++ b/ios/src/morph/upcast.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TNT_FILAMENT_UPCAST_H +#define TNT_FILAMENT_UPCAST_H + +/* + * Generates functions to safely upcast a pointer Bar* to FBar* + * FILAMENT_UPCAST() should be included in the header file + * declaring FBar, e.g.: + * + * #include + * + * class FBar : public Bar { + * }; + * + * FILAMENT_UPCAST(Bar) + * + */ + +#define FILAMENT_UPCAST(CLASS) \ + inline F##CLASS& upcast(CLASS& that) noexcept { \ + return static_cast(that); \ + } \ + inline const F##CLASS& upcast(const CLASS& that) noexcept { \ + return static_cast(that); \ + } \ + inline F##CLASS* upcast(CLASS* that) noexcept { \ + return static_cast(that); \ + } \ + inline F##CLASS const* upcast(CLASS const* that) noexcept { \ + return static_cast(that); \ + } + +#endif // TNT_FILAMENT_UPCAST_H diff --git a/lib/filament_controller.dart b/lib/filament_controller.dart index 50763c6a..7bd6b7df 100644 --- a/lib/filament_controller.dart +++ b/lib/filament_controller.dart @@ -2,13 +2,18 @@ import 'package:flutter/services.dart'; abstract class FilamentController { void onFilamentViewCreated(int id); - Future initialize(); + Future initialize({String? materialPath}); Future loadSkybox(String skyboxPath, String lightingPath); Future loadGlb(String path); - Future loadGltf(String path, String relativeResourcePath); + Future loadGltf( + String path, String relativeResourcePath, String materialInstanceName); Future panStart(double x, double y); Future panUpdate(double x, double y); Future panEnd(); + Future applyWeights(List weights, int primitiveIndex); + Future createMorpher(String meshName, String entityName, + {String? materialName}); + Future zoom(double z); } class MimeticFilamentController extends FilamentController { @@ -22,8 +27,8 @@ class MimeticFilamentController extends FilamentController { } @override - Future initialize() async { - await _channel.invokeMethod("initialize"); + Future initialize({String? materialPath}) async { + await _channel.invokeMethod("initialize", materialPath); } @override @@ -35,8 +40,10 @@ class MimeticFilamentController extends FilamentController { throw Exception(); } - Future loadGltf(String path, String relativeResourcePath) async { - await _channel.invokeMethod("loadGltf", [path, relativeResourcePath]); + Future loadGltf(String path, String relativeResourcePath, + String materialInstanceName) async { + await _channel.invokeMethod( + "loadGltf", [path, relativeResourcePath, materialInstanceName]); } Future panStart(double x, double y) async { @@ -50,4 +57,18 @@ class MimeticFilamentController extends FilamentController { Future panEnd() async { await _channel.invokeMethod("panEnd"); } + + Future applyWeights(List weights, int primitiveIndex) async { + await _channel.invokeMethod("applyWeights", [weights, primitiveIndex]); + } + + Future zoom(double z) async { + await _channel.invokeMethod("zoom", z); + } + + Future createMorpher(String meshName, String entityName, + {String? materialName}) async { + await _channel + .invokeMethod("createMorpher", [meshName, entityName, materialName]); + } }