diff --git a/Cargo.toml b/Cargo.toml index a38db3beae0c0..4ada3bed138bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -111,8 +111,8 @@ name = "texture_atlas" path = "examples/2d/texture_atlas.rs" [[example]] -name = "load_model" -path = "examples/3d/load_model.rs" +name = "load_gltf" +path = "examples/3d/load_gltf.rs" [[example]] name = "msaa" @@ -171,8 +171,8 @@ name = "asset_loading" path = "examples/asset/asset_loading.rs" [[example]] -name = "custom_loader" -path = "examples/asset/custom_asset_loading.rs" +name = "custom_asset" +path = "examples/asset/custom_asset.rs" [[example]] name = "audio" diff --git a/assets/data/asset.custom b/assets/data/asset.custom new file mode 100644 index 0000000000000..0a53b2d09a586 --- /dev/null +++ b/assets/data/asset.custom @@ -0,0 +1,3 @@ +CustomAsset ( + value: 42 +) \ No newline at end of file diff --git a/assets/data/test_data.data1 b/assets/data/test_data.data1 deleted file mode 100644 index e2d4dc7121d72..0000000000000 --- a/assets/data/test_data.data1 +++ /dev/null @@ -1,3 +0,0 @@ - MyCustomData ( - num: 42 -) \ No newline at end of file diff --git a/assets/data/test_data.data2 b/assets/data/test_data.data2 deleted file mode 100644 index 885d7fa253d3f..0000000000000 --- a/assets/data/test_data.data2 +++ /dev/null @@ -1,3 +0,0 @@ - MySecondCustomData ( - is_set: true -) \ No newline at end of file diff --git a/assets/models/FlightHelmet/FlightHelmet.bin b/assets/models/FlightHelmet/FlightHelmet.bin new file mode 100644 index 0000000000000..3a878be10d32e Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet.bin differ diff --git a/assets/models/FlightHelmet/FlightHelmet.gltf b/assets/models/FlightHelmet/FlightHelmet.gltf new file mode 100644 index 0000000000000..37687d8e9a3ab --- /dev/null +++ b/assets/models/FlightHelmet/FlightHelmet.gltf @@ -0,0 +1,705 @@ +{ + "asset": { + "version": "2.0", + "generator": "babylon.js glTF exporter for Maya 2018 v20200228.3 (with minor hand modifications)" + }, + "scene": 0, + "scenes": [ + { + "nodes": [ + 0, + 1, + 2, + 3, + 4, + 5 + ] + } + ], + "nodes": [ + { + "mesh": 0, + "name": "Hose_low" + }, + { + "mesh": 1, + "name": "RubberWood_low" + }, + { + "mesh": 2, + "name": "GlassPlastic_low" + }, + { + "mesh": 3, + "name": "MetalParts_low" + }, + { + "mesh": 4, + "name": "LeatherParts_low" + }, + { + "mesh": 5, + "name": "Lenses_low" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "POSITION": 1, + "TANGENT": 2, + "NORMAL": 3, + "TEXCOORD_0": 4 + }, + "indices": 0, + "material": 0 + } + ], + "name": "Hose_low" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 6, + "TANGENT": 7, + "NORMAL": 8, + "TEXCOORD_0": 9 + }, + "indices": 5, + "material": 1 + } + ], + "name": "RubberWood_low" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 11, + "TANGENT": 12, + "NORMAL": 13, + "TEXCOORD_0": 14 + }, + "indices": 10, + "material": 2 + } + ], + "name": "GlassPlastic_low" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 16, + "TANGENT": 17, + "NORMAL": 18, + "TEXCOORD_0": 19 + }, + "indices": 15, + "material": 3 + } + ], + "name": "MetalParts_low" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 21, + "TANGENT": 22, + "NORMAL": 23, + "TEXCOORD_0": 24 + }, + "indices": 20, + "material": 4 + } + ], + "name": "LeatherParts_low" + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 26, + "TANGENT": 27, + "NORMAL": 28, + "TEXCOORD_0": 29 + }, + "indices": 25, + "material": 5 + } + ], + "name": "Lenses_low" + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5123, + "count": 59040, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 10472, + "max": [ + 0.10810829, + 0.356580257, + 0.190409869 + ], + "min": [ + -0.07221258, + 0.104120225, + -0.088394776 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 2, + "componentType": 5126, + "count": 10472, + "type": "VEC4", + "name": "accessorTangents" + }, + { + "bufferView": 1, + "byteOffset": 125664, + "componentType": 5126, + "count": 10472, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 10472, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 118080, + "componentType": 5123, + "count": 72534, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 251328, + "componentType": 5126, + "count": 13638, + "max": [ + 0.162940636, + 0.7025226, + 0.200029165 + ], + "min": [ + -0.158857465, + -2.14242937E-05, + -0.171545789 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 2, + "byteOffset": 167552, + "componentType": 5126, + "count": 13638, + "type": "VEC4", + "name": "accessorTangents" + }, + { + "bufferView": 1, + "byteOffset": 414984, + "componentType": 5126, + "count": 13638, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 3, + "byteOffset": 83776, + "componentType": 5126, + "count": 13638, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 263148, + "componentType": 5123, + "count": 24408, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 578640, + "componentType": 5126, + "count": 4676, + "max": [ + 0.140494063, + 0.61828655, + 0.147373646 + ], + "min": [ + -0.140846014, + 0.440957, + -0.107818365 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 2, + "byteOffset": 385760, + "componentType": 5126, + "count": 4676, + "type": "VEC4", + "name": "accessorTangents" + }, + { + "bufferView": 1, + "byteOffset": 634752, + "componentType": 5126, + "count": 4676, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 3, + "byteOffset": 192880, + "componentType": 5126, + "count": 4676, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 311964, + "componentType": 5123, + "count": 60288, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 690864, + "componentType": 5126, + "count": 13636, + "max": [ + 0.132708371, + 0.6024364, + 0.199477077 + ], + "min": [ + -0.203642711, + 0.02116075, + -0.147512689 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 2, + "byteOffset": 460576, + "componentType": 5126, + "count": 13636, + "type": "VEC4", + "name": "accessorTangents" + }, + { + "bufferView": 1, + "byteOffset": 854496, + "componentType": 5126, + "count": 13636, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 3, + "byteOffset": 230288, + "componentType": 5126, + "count": 13636, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 432540, + "componentType": 5123, + "count": 65688, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 1018128, + "componentType": 5126, + "count": 12534, + "max": [ + 0.124933377, + 0.716000438, + 0.129168555 + ], + "min": [ + -0.125863016, + 0.2958266, + -0.1541516 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 2, + "byteOffset": 678752, + "componentType": 5126, + "count": 12534, + "type": "VEC4", + "name": "accessorTangents" + }, + { + "bufferView": 1, + "byteOffset": 1168536, + "componentType": 5126, + "count": 12534, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 3, + "byteOffset": 339376, + "componentType": 5126, + "count": 12534, + "type": "VEC2", + "name": "accessorUVs" + }, + { + "bufferView": 0, + "byteOffset": 563916, + "componentType": 5123, + "count": 2208, + "type": "SCALAR", + "name": "accessorIndices" + }, + { + "bufferView": 1, + "byteOffset": 1318944, + "componentType": 5126, + "count": 436, + "max": [ + 0.101920746, + 0.5936986, + 0.152926728 + ], + "min": [ + -0.101920947, + 0.5300429, + 0.090174824 + ], + "type": "VEC3", + "name": "accessorPositions" + }, + { + "bufferView": 2, + "byteOffset": 879296, + "componentType": 5126, + "count": 436, + "type": "VEC4", + "name": "accessorTangents" + }, + { + "bufferView": 1, + "byteOffset": 1324176, + "componentType": 5126, + "count": 436, + "type": "VEC3", + "name": "accessorNormals" + }, + { + "bufferView": 3, + "byteOffset": 439648, + "componentType": 5126, + "count": 436, + "type": "VEC2", + "name": "accessorUVs" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 568332, + "name": "bufferViewScalar" + }, + { + "buffer": 0, + "byteOffset": 568332, + "byteLength": 1329408, + "byteStride": 12, + "name": "bufferViewFloatVec3" + }, + { + "buffer": 0, + "byteOffset": 1897740, + "byteLength": 886272, + "byteStride": 16, + "name": "bufferViewFloatVec4" + }, + { + "buffer": 0, + "byteOffset": 2784012, + "byteLength": 443136, + "byteStride": 8, + "name": "bufferViewFloatVec2" + } + ], + "buffers": [ + { + "uri": "FlightHelmet.bin", + "byteLength": 3227148 + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 2 + }, + "metallicRoughnessTexture": { + "index": 1 + } + }, + "normalTexture": { + "index": 0 + }, + "occlusionTexture": { + "index": 1 + }, + "doubleSided": true, + "name": "HoseMat" + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 2 + }, + "metallicRoughnessTexture": { + "index": 1 + } + }, + "normalTexture": { + "index": 0 + }, + "occlusionTexture": { + "index": 1 + }, + "name": "RubberWoodMat" + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 5 + }, + "metallicRoughnessTexture": { + "index": 4 + } + }, + "normalTexture": { + "index": 3 + }, + "occlusionTexture": { + "index": 4 + }, + "name": "GlassPlasticMat" + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 8 + }, + "metallicRoughnessTexture": { + "index": 7 + } + }, + "normalTexture": { + "index": 6 + }, + "occlusionTexture": { + "index": 7 + }, + "name": "MetalPartsMat" + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 11 + }, + "metallicRoughnessTexture": { + "index": 10 + } + }, + "normalTexture": { + "index": 9 + }, + "occlusionTexture": { + "index": 10 + }, + "name": "LeatherPartsMat" + }, + { + "pbrMetallicRoughness": { + "baseColorTexture": { + "index": 14 + }, + "metallicRoughnessTexture": { + "index": 13 + } + }, + "normalTexture": { + "index": 12 + }, + "occlusionTexture": { + "index": 13 + }, + "alphaMode": "BLEND", + "name": "LensesMat" + } + ], + "textures": [ + { + "sampler": 0, + "source": 0, + "name": "FlightHelmet_Materials_RubberWoodMat_Normal.png" + }, + { + "sampler": 0, + "source": 1, + "name": "FlightHelmet_Materials_RubberWoodMat_OcclusionRoughMetal.png" + }, + { + "sampler": 0, + "source": 2, + "name": "FlightHelmet_Materials_RubberWoodMat_BaseColor.png" + }, + { + "sampler": 0, + "source": 3, + "name": "FlightHelmet_Materials_GlassPlasticMat_Normal.png" + }, + { + "sampler": 0, + "source": 4, + "name": "FlightHelmet_Materials_GlassPlasticMat_OcclusionRoughMetal.png" + }, + { + "sampler": 0, + "source": 5, + "name": "FlightHelmet_Materials_GlassPlasticMat_BaseColor.png" + }, + { + "sampler": 0, + "source": 6, + "name": "FlightHelmet_Materials_MetalPartsMat_Normal.png" + }, + { + "sampler": 0, + "source": 7, + "name": "FlightHelmet_Materials_MetalPartsMat_OcclusionRoughMetal.png" + }, + { + "sampler": 0, + "source": 8, + "name": "FlightHelmet_Materials_MetalPartsMat_BaseColor.png" + }, + { + "sampler": 0, + "source": 9, + "name": "FlightHelmet_Materials_LeatherPartsMat_Normal.png" + }, + { + "sampler": 0, + "source": 10, + "name": "FlightHelmet_Materials_LeatherPartsMat_OcclusionRoughMetal.png" + }, + { + "sampler": 0, + "source": 11, + "name": "FlightHelmet_Materials_LeatherPartsMat_BaseColor.png" + }, + { + "sampler": 0, + "source": 12, + "name": "FlightHelmet_Materials_LensesMat_Normal.png" + }, + { + "sampler": 0, + "source": 13, + "name": "FlightHelmet_Materials_LensesMat_OcclusionRoughMetal.png" + }, + { + "sampler": 0, + "source": 14, + "name": "FlightHelmet_Materials_LensesMat_BaseColor.png" + } + ], + "images": [ + { + "uri": "FlightHelmet_Materials_RubberWoodMat_Normal.png" + }, + { + "uri": "FlightHelmet_Materials_RubberWoodMat_OcclusionRoughMetal.png" + }, + { + "uri": "FlightHelmet_Materials_RubberWoodMat_BaseColor.png" + }, + { + "uri": "FlightHelmet_Materials_GlassPlasticMat_Normal.png" + }, + { + "uri": "FlightHelmet_Materials_GlassPlasticMat_OcclusionRoughMetal.png" + }, + { + "uri": "FlightHelmet_Materials_GlassPlasticMat_BaseColor.png" + }, + { + "uri": "FlightHelmet_Materials_MetalPartsMat_Normal.png" + }, + { + "uri": "FlightHelmet_Materials_MetalPartsMat_OcclusionRoughMetal.png" + }, + { + "uri": "FlightHelmet_Materials_MetalPartsMat_BaseColor.png" + }, + { + "uri": "FlightHelmet_Materials_LeatherPartsMat_Normal.png" + }, + { + "uri": "FlightHelmet_Materials_LeatherPartsMat_OcclusionRoughMetal.png" + }, + { + "uri": "FlightHelmet_Materials_LeatherPartsMat_BaseColor.png" + }, + { + "uri": "FlightHelmet_Materials_LensesMat_Normal.png" + }, + { + "uri": "FlightHelmet_Materials_LensesMat_OcclusionRoughMetal.png" + }, + { + "uri": "FlightHelmet_Materials_LensesMat_BaseColor.png" + } + ], + "samplers": [ + { + "magFilter": 9729, + "minFilter": 9987 + } + ] +} diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_BaseColor.png b/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_BaseColor.png new file mode 100644 index 0000000000000..f79eafe421608 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_BaseColor.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_Normal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_Normal.png new file mode 100644 index 0000000000000..06e70ef10b61c Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_Normal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_OcclusionRoughMetal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_OcclusionRoughMetal.png new file mode 100644 index 0000000000000..3a03d7bb2ad40 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_GlassPlasticMat_OcclusionRoughMetal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_BaseColor.png b/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_BaseColor.png new file mode 100644 index 0000000000000..dbee2d41accc0 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_BaseColor.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_Normal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_Normal.png new file mode 100644 index 0000000000000..467e2ca9187fc Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_Normal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_OcclusionRoughMetal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_OcclusionRoughMetal.png new file mode 100644 index 0000000000000..24058ff8fc90d Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_LeatherPartsMat_OcclusionRoughMetal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_BaseColor.png b/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_BaseColor.png new file mode 100644 index 0000000000000..81b29d8e5eab4 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_BaseColor.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_Normal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_Normal.png new file mode 100644 index 0000000000000..ed6502e341fcd Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_Normal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_OcclusionRoughMetal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_OcclusionRoughMetal.png new file mode 100644 index 0000000000000..bc7dd395a17c2 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_LensesMat_OcclusionRoughMetal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_BaseColor.png b/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_BaseColor.png new file mode 100644 index 0000000000000..33b9159225d7d Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_BaseColor.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_Normal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_Normal.png new file mode 100644 index 0000000000000..b977bac8ca176 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_Normal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_OcclusionRoughMetal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_OcclusionRoughMetal.png new file mode 100644 index 0000000000000..9cde90c3254b7 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_MetalPartsMat_OcclusionRoughMetal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_BaseColor.png b/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_BaseColor.png new file mode 100644 index 0000000000000..c60cc95646c6f Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_BaseColor.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_Normal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_Normal.png new file mode 100644 index 0000000000000..bc5669cf8a3a5 Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_Normal.png differ diff --git a/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_OcclusionRoughMetal.png b/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_OcclusionRoughMetal.png new file mode 100644 index 0000000000000..f846b0551638e Binary files /dev/null and b/assets/models/FlightHelmet/FlightHelmet_Materials_RubberWoodMat_OcclusionRoughMetal.png differ diff --git a/assets/models/monkey/Monkey.bin b/assets/models/monkey/Monkey.bin deleted file mode 100644 index 472d25376e6fb..0000000000000 Binary files a/assets/models/monkey/Monkey.bin and /dev/null differ diff --git a/assets/models/monkey/Monkey.glb b/assets/models/monkey/Monkey.glb deleted file mode 100644 index 1998f1dbe1cf3..0000000000000 Binary files a/assets/models/monkey/Monkey.glb and /dev/null differ diff --git a/assets/models/monkey/Monkey.gltf b/assets/models/monkey/Monkey.gltf index 7a3836529d683..f7b34ed3f0c40 100644 --- a/assets/models/monkey/Monkey.gltf +++ b/assets/models/monkey/Monkey.gltf @@ -1,6 +1,6 @@ { "asset" : { - "generator" : "Khronos glTF Blender I/O v1.2.75", + "generator" : "Khronos glTF Blender I/O v1.3.48", "version" : "2.0" }, "scene" : 0, @@ -18,6 +18,27 @@ "name" : "Suzanne" } ], + "materials" : [ + { + "doubleSided" : true, + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Material", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.7612724900245667, + 0.5813313126564026, + 0.41983750462532043, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4000000059604645 + } + } + ], "meshes" : [ { "name" : "Suzanne", @@ -28,7 +49,8 @@ "NORMAL" : 1, "TEXCOORD_0" : 2 }, - "indices" : 3 + "indices" : 3, + "material" : 0 } ] } @@ -37,7 +59,7 @@ { "bufferView" : 0, "componentType" : 5126, - "count" : 3321, + "count" : 2012, "max" : [ 1.325934886932373, 0.9392361640930176, @@ -53,13 +75,13 @@ { "bufferView" : 1, "componentType" : 5126, - "count" : 3321, + "count" : 2012, "type" : "VEC3" }, { "bufferView" : 2, "componentType" : 5126, - "count" : 3321, + "count" : 2012, "type" : "VEC2" }, { @@ -72,29 +94,29 @@ "bufferViews" : [ { "buffer" : 0, - "byteLength" : 39852, + "byteLength" : 24144, "byteOffset" : 0 }, { "buffer" : 0, - "byteLength" : 39852, - "byteOffset" : 39852 + "byteLength" : 24144, + "byteOffset" : 24144 }, { "buffer" : 0, - "byteLength" : 26568, - "byteOffset" : 79704 + "byteLength" : 16096, + "byteOffset" : 48288 }, { "buffer" : 0, "byteLength" : 23616, - "byteOffset" : 106272 + "byteOffset" : 64384 } ], "buffers" : [ { - "byteLength" : 129888, - "uri" : "Monkey.bin" + "byteLength" : 88000, + "uri" : "data:application/octet-stream;base64,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NgA3AC0AMwA3AC0ANwAgADgAOQA6ADgAOgA7ADkAPAA9ADkAPQA6ADoAPQAwADoAMAAuADsAOgAuADsALgAoACAANwA+ACAAPgAhADcANgA/ADcAPwA+AD4APwBAAD4AQABBACEAPgBBACEAQQAiAEIAQwBEAEIARABFAEMAOAA7AEMAOwBEAEQAOwAoAEQAKAAlAEUARAAlAEUAJQAkADYARgBHADYARwA/AEYASABJAEYASQBHAEcASQBKAEcASgBLAD8ARwBLAD8ASwBAAEwATQBOAEwATgBPAE0AUABRAE0AUQBOAE4AUQA4AE4AOABDAE8ATgBDAE8AQwBCADQAUgBTADQAUwA1AFIAVABVAFIAVQBTAFMAVQBIAFMASABGADUAUwBGADUARgA2AFAAVgBXAFAAVwBRAFYAWABZAFYAWQBXAFcAWQA8AFcAPAA5AFEAVwA5AFEAOQA4AFQAWgBbAFQAWwBVAFoAXABdAFoAXQBbAFsAXQBeAFsAXgBfAFUAWwBfAFUAXwBIAGAAYQBiAGAAYgBjAGEAZABlAGEAZQBiAGIAZQBYAGIAWABWAGMAYgBWAGMAVgBQAEgAXwBmAEgAZgBJAF8AXgBnAF8AZwBmAGYAZwBoAGYAaABpAEkAZgBpAEkAaQBKAGoAawBsAGoAbABtAGsAYABjAGsAYwBsAGwAYwBQAGwAUABNAG0AbABNAG0ATQBMAF4AbgBvAF4AbwBnAG4AcABxAG4AcQBvAG8AcQByAG8AcgBzAGcAbwBzAGcAcwBoAHQAdQB2AHQAdgB3AHUAeAB5AHUAeQB2AHYAeQBgAHYAYABrAHcAdgBrAHcAawBqAFwAegB7AFwAewBdAHoAfAB9AHoAfQB7AHsAfQBwAHsAcABuAF0AewBuAF0AbgBeAHgAfgB/AHgAfwB5AH4AgACBAH4AgQB/AH8AgQBkAH8AZABhAHkAfwBhAHkAYQBgAHwAggCDAHwAgwB9AIIAhACFAIIAhQCDAIMAhQCGAIMAhgCHAH0AgwCHAH0AhwBwAIgAiQCKAIgAigCLAIkAjACNAIkAjQCKAIoAjQCAAIoAgAB+AIsAigB+AIsAfgB4AHAAhwCOAHAAjgBxAIcAhgCPAIcAjwCOAI4AjwCQAI4AkACRAHEAjgCRAHEAkQByAJIAkwCUAJIAlACVAJMAiACLAJMAiwCUAJQAiwB4AJQAeAB1AJUAlAB1AJUAdQB0AIYAlgCXAIYAlwCPAJYACAATAJYAEwCXAJcAEwAXAJcAFwCYAI8AlwCYAI8AmACQAB0AHACZAB0AmQCaABwAEQCbABwAmwCZAJkAmwCIAJkAiACTAJoAmQCTAJoAkwCSAIQAnACdAIQAnQCFAJwAAAADAJwAAwCdAJ0AAwAIAJ0ACACWAIUAnQCWAIUAlgCGABEAEACeABEAngCbABAADwCfABAAnwCeAJ4AnwCMAJ4AjACJAJsAngCJAJsAiQCIAAAAnACgAAAAoAChAJwAhACiAJwAogCgAKAAogCjAKAAowCkAKEAoACkAKEApAClAKYApwCoAKYAqACpAKcAjACfAKcAnwCoAKgAnwAPAKgADwCqAKkAqACqAKkAqgCrAIQAggCsAIQArACiAIIAfACtAIIArQCsAKwArQCuAKwArgCvAKIArACvAKIArwCjALAAsQCyALAAsgCzALEAgACNALEAjQCyALIAjQCMALIAjACnALMAsgCnALMApwCmAHwAegC0AHwAtACtAHoAXAC1AHoAtQC0ALQAtQC2ALQAtgC3AK0AtAC3AK0AtwCuALgAuQC6ALgAugC7ALkAZACBALkAgQC6ALoAgQCAALoAgACxALsAugCxALsAsQCwAFwAWgC8AFwAvAC1AFoAVAC9AFoAvQC8ALwAvQC+ALwAvgC/ALUAvAC/ALUAvwC2AMAAwQDCAMAAwgDDAMEAWABlAMEAZQDCAMIAZQBkAMIAZAC5AMMAwgC5AMMAuQC4AFQAUgDEAFQAxAC9AFIANADFAFIAxQDEAMQAxQDGAMQAxgDHAL0AxADHAL0AxwC+AMgAyQDKAMgAygDLAMkAPABZAMkAWQDKAMoAWQBYAMoAWADBAMsAygDBAMsAwQDAADQAMgDMADQAzADFADIALADNADIAzQDMAMwAzQDOAMwAzgDPAMUAzADPAMUAzwDGANAA0QDSANAA0gDTANEAMAA9ANEAPQDSANIAPQA8ANIAPADJANMA0gDJANMAyQDIACwAKgDUACwA1ADNACoABADVACoA1QDUANQA1QDWANQA1gDXAM0A1ADXAM0A1wDOANgA2QDaANgA2gDbANkADQAxANkAMQDaANoAMQAwANoAMADRANsA2gDRANsA0QDQAAQAAQDcAAQA3ADVAAEAAAChAAEAoQDcANwAoQClANwApQDdANUA3ADdANUA3QDWAKsAqgDeAKsA3gDfAKoADwAOAKoADgDeAN4ADgANAN4ADQDZAN8A3gDZAN8A2QDYAOAA4QDiAOAA4gDjANYA3QDiANYA4gDhAKUA4wDiAKUA4gDdAKsA3wDkAKsA5ADlANgA5gDkANgA5ADfAOcA5QDkAOcA5ADmAM4A1wDoAM4A6ADpANYA4QDoANYA6ADXAOAA6QDoAOAA6ADhAOcA5gDqAOcA6gDrANgA2wDqANgA6gDmANAA6wDqANAA6gDbAOAA7ADtAOAA7QDpAMYAzwDtAMYA7QDsAM4A6QDtAM4A7QDPANAA0wDuANAA7gDrAMgA7wDuAMgA7gDTAOcA6wDuAOcA7gDvAOAA8ADxAOAA8QDsAL4AxwDxAL4A8QDwAMYA7ADxAMYA8QDHAMgAywDyAMgA8gDvAMAA8wDyAMAA8gDLAOcA7wDyAOcA8gDzAOAA9AD1AOAA9QDwALYAvwD1ALYA9QD0AL4A8AD1AL4A9QC/AMAAwwD2AMAA9gDzALgA9wD2ALgA9gDDAOcA8wD2AOcA9gD3AOAA+AD5AOAA+QD0AK4AtwD5AK4A+QD4ALYA9AD5ALYA+QC3ALgAuwD6ALgA+gD3ALAA+wD6ALAA+gC7AOcA9wD6AOcA+gD7AOAA/AD9AOAA/QD4AKMArwD9AKMA/QD8AK4A+AD9AK4A/QCvALAAswD+ALAA/gD7AKYA/wD+AKYA/gCzAOcA+wD+AOcA/gD/AOAA4wAAAeAAAAH8AKUApAAAAaUAAAHjAKMA/AAAAaMAAAGkAKYAqQABAaYAAQH/AKsA5QABAasAAQGpAOcA/wABAecAAQHlAAIBAwEEAQIBBAEFAQMBBgEHAQMBBwEEAQQBBwEIAQQBCAEJAQUBBAEJAQUBCQEKAQgBCwEMAQgBDAEJAQsBDQEOAQsBDgEMAQwBDgEPAQwBDwEQAQkBDAEQAQkBEAEKAREBEgETAREBEwEUARIBFQEWARIBFgETARMBFgEGARMBBgEDARQBEwEDARQBAwECAQ0BFwEYAQ0BGAEOARcBGQEaARcBGgEYARgBGgEbARgBGwEcAQ4BGAEcAQ4BHAEPAR0BHgEfAR0BHwEgAR4BIQEiAR4BIgEfAR8BIgEVAR8BFQESASABHwESASABEgERARkBIwEkARkBJAEaASMBJQEmASMBJgEkASQBJgEnASQBJwEoARoBJAEoARoBKAEbASkBKgErASkBKwEsASoBLQEuASoBLgErASsBLgEhASsBIQEeASwBKwEeASwBHgEdASUBLwEwASUBMAEmAS8BMQEyAS8BMgEwATABMgEzATABMwE0ASYBMAE0ASYBNAEnATUBNgE3ATUBNwE4ATYBOQE6ATYBOgE3ATcBOgEtATcBLQEqATgBNwEqATgBKgEpATEBOwE8ATEBPAEyATsBPQE+ATsBPgE8ATwBPgE/ATwBPwFAATIBPAFAATIBQAEzAUEBQgFDAUEBQwFEAUIBRQFGAUIBRgFDAUMBRgFHAUMBRwFIAUQBQwFIAUQBSAFJAUoBSwFMAUoBTAFNAUsBTgFPAUsBTwFMAUwBTwFQAUwBUAFRAU0BTAFRAU0BUQFSAUUBUwFUAUUBVAFGAVMBVQFWAVMBVgFUAVQBVgFXAVQBVwFYAUYBVAFYAUYBWAFHAVkBWgFbAVkBWwFcAVoBXQFeAVoBXgFbAVsBXgFOAVsBTgFLAVwBWwFLAVwBSwFKAVUBXwFgAVUBYAFWAV8BYQFiAV8BYgFgAWABYgFjAWABYwFkAVYBYAFkAVYBZAFXAWUBZgFnAWUBZwFoAWYBaQFqAWYBagFnAWcBagFdAWcBXQFaAWgBZwFaAWgBWgFZAWEBawFsAWEBbAFiAWsBbQFuAWsBbgFsAWwBbgFvAWwBbwFwAWIBbAFwAWIBcAFjAXEBcgFzAXEBcwF0AXIBdQF2AXIBdgFzAXMBdgFpAXMBaQFmAXQBcwFmAXQBZgFlAW0BdwF4AW0BeAFuAXcBeQF6AXcBegF4AXgBegF7AXgBewF8AW4BeAF8AW4BfAFvAX0BfgF/AX0BfwGAAX4BgQGCAX4BggF/AX8BggF1AX8BdQFyAYABfwFyAYABcgFxAXkBgwGEAXkBhAF6AYMBhQGGAYMBhgGEAYQBhgGHAYQBhwGIAXoBhAGIAXoBiAF7AYkBigGLAYkBiwGMAYoBjQGOAYoBjgGLAYsBjgGBAYsBgQF+AYwBiwF+AYwBfgF9AYUBjwGQAYUBkAGGAY8BkQGSAY8BkgGQAZABkgGTAZABkwGUAYYBkAGUAYYBlAGHAZUBlgGXAZUBlwGYAZYBmQGaAZYBmgGXAZcBmgGNAZcBjQGKAZgBlwGKAZgBigGJAZEBmwGcAZEBnAGSAZsBnQGeAZsBngGcAZwBngGfAZwBnwGgAZIBnAGgAZIBoAGTAaEBogGjAaEBowGkAaIBpQGmAaIBpgGjAaMBpgGZAaMBmQGWAaQBowGWAaQBlgGVAZ0BpwGoAZ0BqAGeAacBqQGqAacBqgGoAagBqgGrAagBqwGsAZ4BqAGsAZ4BrAGfAa0BrgGvAa0BrwGwAa4BsQGyAa4BsgGvAa8BsgGlAa8BpQGiAbABrwGiAbABogGhAakBswG0AakBtAGqAbMBtQG2AbMBtgG0AbQBtgG3AbQBtwG4AaoBtAG4AaoBuAGrAbcBtgG5AbcBuQG6AbYBtQG7AbYBuwG5AbkBuwGxAbkBsQGuAboBuQGuAboBrgGtAbwBvQG+AbwBvgG/Ab0BwAHBAb0BwQG+Ab4BwQGfAb4BnwGsAb8BvgGsAb8BrAGrAaEBwgHDAaEBwwGwAcIBxAHFAcIBxQHDAcMBxQHGAcMBxgHHAbABwwHHAbABxwGtAcAByAHJAcAByQHBAcgBygHLAcgBywHJAckBywGTAckBkwGgAcEByQGgAcEBoAGfAZUBzAHNAZUBzQGkAcwBzgHPAcwBzwHNAc0BzwHEAc0BxAHCAaQBzQHCAaQBwgGhAdAB0QHSAdAB0gHTAdEBhwGUAdEBlAHSAdIBlAGTAdIBkwHLAdMB0gHLAdMBywHKAZUBmAHUAZUB1AHMAZgBiQHVAZgB1QHUAdQB1QHWAdQB1gHXAcwB1AHXAcwB1wHOAdgB2QHaAdgB2gHbAdkBewGIAdkBiAHaAdoBiAGHAdoBhwHRAdsB2gHRAdsB0QHQAYkBjAHcAYkB3AHVAYwBfQHdAYwB3QHcAdwB3QHeAdwB3gHfAdUB3AHfAdUB3wHWAeAB4QHiAeAB4gHjAeEBbwF8AeEBfAHiAeIBfAF7AeIBewHZAeMB4gHZAeMB2QHYAX0BgAHkAX0B5AHdAYABcQHlAYAB5QHkAeQB5QHmAeQB5gHnAd0B5AHnAd0B5wHeAegB6QHqAegB6gHrAekBYwFwAekBcAHqAeoBcAFvAeoBbwHhAesB6gHhAesB4QHgAXEBdAHsAXEB7AHlAXQBZQHtAXQB7QHsAewB7QHuAewB7gHvAeUB7AHvAeUB7wHmAfAB8QHyAfAB8gHzAfEBVwFkAfEBZAHyAfIBZAFjAfIBYwHpAfMB8gHpAfMB6QHoAWUBaAH0AWUB9AHtAWgBWQH1AWgB9QH0AfQB9QH2AfQB9gH3Ae0B9AH3Ae0B9wHuAfgB+QH6AfgB+gH7AfkBRwFYAfkBWAH6AfoBWAFXAfoBVwHxAfsB+gHxAfsB8QHwAVkBXAH8AVkB/AH1AVwBSgH9AVwB/QH8AfwB/QH+AfwB/gH/AfUB/AH/AfUB/wH2AQACAQICAgACAgIDAgECSQFIAQECSAECAgICSAFHAQICRwH5AQMCAgL5AQMC+QH4AUoBTQEEAkoBBAL9AU0BUgEFAk0BBQIEAgQCBQIGAgQCBgIHAv0BBAIHAv0BBwL+AQACCAIJAgACCQIBAggCCgILAggCCwIJAgkCCwIMAgkCDAINAgECCQINAgECDQJJAQwCDgIPAgwCDwIQAg4CEQISAg4CEgIPAg8CEgIGAg8CBgIFAhACDwIFAhACBQJSAbwBvwETArwBEwIUAr8BqwG4Ab8BuAETAhMCuAG3ARMCtwEVAhQCEwIVAhQCFQIWArcBugEXArcBFwIYAroBrQHHAboBxwEXAhcCxwHGARcCxgEZAhgCFwIZAhgCGQIaAhYCFQIbAhYCGwIcAhUCtwEdAhUCHQIbAhsCHQIeAhsCHgIfAhwCGwIfAhwCHwIgAh4CHQIhAh4CIQIiAh0CtwEYAh0CGAIhAiECGAIaAiECGgIjAiICIQIjAiICIwIkAgwCCwIlAgwCJQImAgsCCgInAgsCJwIlAiUCJwIgAiUCIAIfAiYCJQIfAiYCHwIeAiQCKAIpAiQCKQIiAigCEQIOAigCDgIpAikCDgIMAikCDAImAiICKQImAiICJgIeAioCKwIsAioCLAItAisCLgIvAisCLwIsAiwCLwIIASwCCAEHAS0CLAIHAS0CBwEGAQgBLwIwAggBMAILAS8CLgIxAi8CMQIwAjACMQIyAjACMgIzAgsBMAIzAgsBMwINATQCNQI2AjQCNgI3AjUCKgItAjUCLQI2AjYCLQIGATYCBgEWATcCNgIWATcCFgEVAQ0BMwI4Ag0BOAIXATMCMgI5AjMCOQI4AjgCOQI6AjgCOgI7AhcBOAI7AhcBOwIZATwCPQI+AjwCPgI/Aj0CNAI3Aj0CNwI+Aj4CNwIVAT4CFQEiAT8CPgIiAT8CIgEhARkBOwJAAhkBQAIjATsCOgJBAjsCQQJAAkACQQJCAkACQgJDAiMBQAJDAiMBQwIlATkBRAJFAjkBRQI6AUQCRgJHAkQCRwJFAkUCRwJIAkUCSAJJAjoBRQJJAjoBSQItAUoCSwJMAkoCTAJNAksCTgJPAksCTwJMAkwCTwI9AUwCPQE7AU0CTAI7AU0COwExATwCPwJQAjwCUAJRAj8CIQEuAT8CLgFQAlACLgEtAVACLQFJAlECUAJJAlECSQJIAjEBLwFSAjEBUgJNAi8BJQFDAi8BQwJSAlICQwJCAlICQgJTAk0CUgJTAk0CUwJKAlQCVQJWAlQCVgJXAlUCWAJZAlUCWQJWAlYCWQJaAlYCWgJbAlcCVgJbAlcCWwJGAloCWQJcAloCXAJdAlkCWAJeAlkCXgJcAlwCXgJfAlwCXwJgAl0CXAJgAl0CYAJOAkgCRwJhAkgCYQJiAkcCRgJbAkcCWwJhAmECWwJaAmECWgJjAmICYQJjAmICYwJkAloCXQJlAloCZQJjAl0CTgJLAl0CSwJlAmUCSwJKAmUCSgJmAmMCZQJmAmMCZgJkAmcCaAJpAmcCaQJqAmgCPAJRAmgCUQJpAmkCUQJIAmkCSAJiAmoCaQJiAmoCYgJkAkoCUwJrAkoCawJmAlMCQgJsAlMCbAJrAmsCbAJnAmsCZwJqAmYCawJqAmYCagJkAm0CbgJvAm0CbwJwAm4CcQJyAm4CcgJvAm8CcgJzAm8CcwJ0AnACbwJ0AnACdAJUAnUCdgJ3AnUCdwJ4AnYCeQJ6AnYCegJ3AncCegJ7AncCewJ8AngCdwJ8AngCfAJfAn0CfgJ/An0CfwKAAn4CgQKCAn4CggJ/An8CggJxAn8CcQJuAoACfwJuAoACbgJtAnkCgwKEAnkChAJ6AoMChQKGAoMChgKEAoQChgKHAoQChwKIAnoChAKIAnoCiAJ7AokCigKLAokCiwKMAooCjQKOAooCjgKLAosCjgKBAosCgQJ+AowCiwJ+AowCfgJ9AoUCjwKQAoUCkAKGAo8CkQKSAo8CkgKQApACkgKJApACiQKTAoYCkAKTAoYCkwKHApQClQKWApQClgKXApUCmAKZApUCmQKWApYCmQKNApYCjQKKApcClgKKApcCigKJApECmgKbApECmwKSApoCmAKVApoClQKbApsClQKUApsClAKXApICmwKXApIClwKJAlgCVQKcAlgCnAKdAlUCVAJ0AlUCdAKcApwCdAJzApwCcwKeAp0CnAKeAp0CngKfAnUCeAKgAnUCoAKhAngCXwJeAngCXgKgAqACXgJYAqACWAKdAqECoAKdAqECnQKfAp8CngKiAp8CogKjAp4CcwKkAp4CpAKiAqICpAKlAqICpQKmAqMCogKmAqMCpgKnAqgCqQKqAqgCqgKrAqkCdQKhAqkCoQKqAqoCoQKfAqoCnwKjAqsCqgKjAqsCowKnApgCrAKtApgCrQKZAqwCrgKvAqwCrwKtAq0CrwKwAq0CsAKxApkCrQKxApkCsQKNArICswK0ArICtAK1ArMCrgKsArMCrAK0ArQCrAKYArQCmAKaArUCtAKaArUCmgKRAo0CsQK2Ao0CtgKOArECsAK3ArECtwK2ArYCtwK4ArYCuAK5Ao4CtgK5Ao4CuQKBAroCuwK8AroCvAK9ArsCsgK1ArsCtQK8ArwCtQKRArwCkQKPAr0CvAKPAr0CjwKFAoECuQK+AoECvgKCArkCuAK/ArkCvwK+Ar4CvwLAAr4CwALBAoICvgLBAoICwQJxAsICwwLEAsICxALFAsMCugK9AsMCvQLEAsQCvQKFAsQChQKDAsUCxAKDAsUCgwJ5AnECwQLGAnECxgJyAsECwALHAsECxwLGAsYCxwKlAsYCpQKkAnICxgKkAnICpAJzAqgCyALJAqgCyQKpAsgCwgLFAsgCxQLJAskCxQJ5AskCeQJ2AqkCyQJ2AqkCdgJ1Aq4CygLLAq4CywKvAsoCwAK/AsoCvwLLAssCvwK4AssCuAK3Aq8CywK3Aq8CtwKwAroCwwLMAroCzAK7AsMCwgLNAsMCzQLMAswCzQKuAswCrgKzArsCzAKzArsCswKyAq4CzgLPAq4CzwLKAs4CpwKmAs4CpgLPAs8CpgKlAs8CpQLHAsoCzwLHAsoCxwLAAqgCqwLQAqgC0ALIAqsCpwLOAqsCzgLQAtACzgKuAtACrgLNAsgC0ALNAsgCzQLCAokCjALRAokC0QLSAowCfQLTAowC0wLRAtEC0wJJAdECSQENAtIC0QINAtICDQIMAlIB1ALVAlIB1QIQAtQChwKTAtQCkwLVAtUCkwKJAtUCiQLSAhAC1QLSAhAC0gIMAn0CgALWAn0C1gLTAoACbQLXAoAC1wLWAtYC1wLYAtYC2ALZAtMC1gLZAtMC2QJJAdoC2wLcAtoC3ALdAtsCewKIAtsCiALcAtwCiAKHAtwChwLUAt0C3ALUAt0C1AJSAW0CcALeAm0C3gLXAnACVALfAnAC3wLeAt4C3wLgAt4C4ALhAtcC3gLhAtcC4QLYAuIC4wLkAuIC5ALlAuMCXwJ8AuMCfALkAuQCfAJ7AuQCewLbAuUC5ALbAuUC2wLaAlQCVwLmAlQC5gLfAlcCRgJEAlcCRALmAuYCRAI5AeYCOQHnAt8C5gLnAt8C5wLgAj0BTwLoAj0B6ALpAk8CTgJgAk8CYALoAugCYAJfAugCXwLjAukC6ALjAukC4wLiAjUB6gLrAjUB6wI2AeoC7ALtAuoC7QLrAusC7QLgAusC4ALnAjYB6wLnAjYB5wI5AeIC7gLvAuIC7wLpAu4C8ALxAu4C8QLvAu8C8QI/Ae8CPwE+AekC7wI+AekCPgE9AewC8gLzAuwC8wLtAvIC9AL1AvIC9QLzAvMC9QLYAvMC2ALhAu0C8wLhAu0C4QLgAtoC9gL3AtoC9wLlAvYC+AL5AvYC+QL3AvcC+QLwAvcC8ALuAuUC9wLuAuUC7gLiAkEBRAH6AkEB+gL7AkQBSQHZAkQB2QL6AvoC2QLYAvoC2AL1AvsC+gL1AvsC9QL0AtoC3QL8AtoC/AL2At0CUgFRAd0CUQH8AvwCUQFQAfwCUAH9AvYC/AL9AvYC/QL4AjwCaAL+AjwC/gL/AmgCZwIAA2gCAAP+Av4CAAMBA/4CAQMCA/8C/gICA/8CAgMDAwEDAAMEAwEDBAMFAwADZwJsAgADbAIEAwQDbAJCAgQDQgIGAwUDBAMGAwUDBgMHAzQCPQIIAzQCCAMJAz0CPAL/Aj0C/wIIAwgD/wIDAwgDAwMKAwkDCAMKAwkDCgMLAwcDBgMMAwcDDAMNAwYDQgJBAgYDQQIMAwwDQQI6AgwDOgIOAw0DDAMOAw0DDgMPAyoCNQIQAyoCEAMRAzUCNAIJAzUCCQMQAxADCQMLAxADCwMSAxEDEAMSAxEDEgMTAw8DDgMUAw8DFAMVAw4DOgI5Ag4DOQIUAxQDOQIyAhQDMgIWAxUDFAMWAxUDFgMXAy4CKwIYAy4CGAMZAysCKgIRAysCEQMYAxgDEQMTAxgDEwMaAxkDGAMaAxkDGgMbAxcDFgMcAxcDHAMdAxYDMgIxAhYDMQIcAxwDMQIuAhwDLgIZAx0DHAMZAx0DGQMbAxsDGgMeAxsDHgMfAxoDEwMgAxoDIAMeAx4DIAMhAx4DIQMiAx8DHgMiAx8DIgMjAyQDJQMmAyQDJgMnAyUDFwMdAyUDHQMmAyYDHQMbAyYDGwMfAycDJgMfAycDHwMjAxMDEgMoAxMDKAMgAxIDCwMpAxIDKQMoAygDKQMqAygDKgMrAyADKAMrAyADKwMhAywDLQMuAywDLgMvAy0DDwMVAy0DFQMuAy4DFQMXAy4DFwMlAy8DLgMlAy8DJQMkAwsDCgMwAwsDMAMpAwoDAwMxAwoDMQMwAzADMQMyAzADMgMzAykDMAMzAykDMwMqAzQDNQM2AzQDNgM3AzUDBwMNAzUDDQM2AzYDDQMPAzYDDwMtAzcDNgMtAzcDLQMsAwMDAgM4AwMDOAMxAwIDAQM5AwIDOQM4AzgDOQM6AzgDOgM7AzEDOAM7AzEDOwMyAzoDOQM8AzoDPAM9AzkDAQMFAzkDBQM8AzwDBQMHAzwDBwM1Az0DPAM1Az0DNQM0AzoDPgM/AzoDPwM7Az4DIwMiAz4DIgM/Az8DIgMhAz8DIQNAAzsDPwNAAzsDQAMyAyQDJwNBAyQDQQNCAycDIwM+AycDPgNBA0EDPgM6A0EDOgM9A0IDQQM9A0IDPQM0AzIDQANDAzIDQwMzAyEDKwNDAyEDQwNAAyoDMwNDAyoDQwMrAywDLwNEAywDRAM3AyQDQgNEAyQDRAMvAzQDNwNEAzQDRANCAyACJwJFAyACRQNGAycCCgJHAycCRwNFA0UDRwNIA0UDSANJA0YDRQNJA0YDSQNKA0sDTANNA0sDTQNOA0wDEQIoAkwDKAJNA00DKAIkAk0DJAJPA04DTQNPA04DTwNQAxYCHAJRAxYCUQNSAxwCIAJGAxwCRgNRA1EDRgNKA1EDSgNTA1IDUQNTA1IDUwNUA1ADTwNVA1ADVQNWA08DJAIjAk8DIwJVA1UDIwIaAlUDGgJXA1YDVQNXA1YDVwNYA7wBFAJZA7wBWQNaAxQCFgJSAxQCUgNZA1kDUgNUA1kDVANbA1oDWQNbA1oDWwNcA1gDVwNdA1gDXQNeA1cDGgIZAlcDGQJdA10DGQLGAV0DxgFfA14DXQNfA14DXwNgAwoCCAJhAwoCYQNHAwgCAAJiAwgCYgNhA2EDYgNjA2EDYwNkA0cDYQNkA0cDZANIA2UDZgNnA2UDZwNoA2YDBgISAmYDEgJnA2cDEgIRAmcDEQJMA2gDZwNMA2gDTANLAwACAwJpAwACaQNiAwMC+AFqAwMCagNpA2kDagNrA2kDawNsA2IDaQNsA2IDbANjA20DbgNvA20DbwNwA24D/gEHAm4DBwJvA28DBwIGAm8DBgJmA3ADbwNmA3ADZgNlA/gB+wFxA/gBcQNqA/sB8AFyA/sBcgNxA3EDcgNzA3EDcwN0A2oDcQN0A2oDdANrA3UDdgN3A3UDdwN4A3YD9gH/AXYD/wF3A3cD/wH+AXcD/gFuA3gDdwNuA3gDbgNtA/AB8wF5A/ABeQNyA/MB6AF6A/MBegN5A3kDegN7A3kDewN8A3IDeQN8A3IDfANzA30DfgN/A30DfwOAA34D7gH3AX4D9wF/A38D9wH2AX8D9gF2A4ADfwN2A4ADdgN1A+gB6wGBA+gBgQN6A+sB4AGCA+sBggOBA4EDggODA4EDgwOEA3oDgQOEA3oDhAN7A4UDhgOHA4UDhwOIA4YD5gHvAYYD7wGHA4cD7wHuAYcD7gF+A4gDhwN+A4gDfgN9A+AB4wGJA+ABiQOCA+MB2AGKA+MBigOJA4kDigOLA4kDiwOMA4IDiQOMA4IDjAODA40DjgOPA40DjwOQA44D3gHnAY4D5wGPA48D5wHmAY8D5gGGA5ADjwOGA5ADhgOFA9gB2wGRA9gBkQOKA9sB0AGSA9sBkgORA5EDkgOTA5EDkwOUA4oDkQOUA4oDlAOLA5UDlgOXA5UDlwOYA5YD1gHfAZYD3wGXA5cD3wHeAZcD3gGOA5gDlwOOA5gDjgONA9AB0wGZA9ABmQOSA9MBygGaA9MBmgOZA5kDmgObA5kDmwOcA5IDmQOcA5IDnAOTA50DngOfA50DnwOgA54DzgHXAZ4D1wGfA58D1wHWAZ8D1gGWA6ADnwOWA6ADlgOVA8oByAGhA8oBoQOaA8gBwAGiA8gBogOhA6EDogOjA6EDowOkA5oDoQOkA5oDpAObA6UDpgOnA6UDpwOoA6YDxAHPAaYDzwGnA6cDzwHOAacDzgGeA6gDpwOeA6gDngOdA8ABvQGpA8ABqQOiA70BvAFaA70BWgOpA6kDWgNcA6kDXAOqA6IDqQOqA6IDqgOjA2ADXwOrA2ADqwOsA18DxgHFAV8DxQGrA6sDxQHEAasDxAGmA6wDqwOmA6wDpgOlA6MDqgOtA6MDrQOuA6oDXAOvA6oDrwOtA60DrwOwA60DsAOxA64DrQOxA64DsQOyA7MDtAO1A7MDtQO2A7QDYAOsA7QDrAO1A7UDrAOlA7UDpQO3A7YDtQO3A7YDtwO4A5sDpAO5A5sDuQO6A6QDowOuA6QDrgO5A7kDrgOyA7kDsgO7A7oDuQO7A7oDuwO8A7gDtwO9A7gDvQO+A7cDpQOoA7cDqAO9A70DqAOdA70DnQO/A74DvQO/A74DvwPAA5MDnAPBA5MDwQPCA5wDmwO6A5wDugPBA8EDugO8A8EDvAPDA8IDwQPDA8IDwwPEA8ADvwPFA8ADxQPGA78DnQOgA78DoAPFA8UDoAOVA8UDlQPHA8YDxQPHA8YDxwPIA4sDlAPJA4sDyQPKA5QDkwPCA5QDwgPJA8kDwgPEA8kDxAPLA8oDyQPLA8oDywPMA8gDxwPNA8gDzQPOA8cDlQOYA8cDmAPNA80DmAONA80DjQPPA84DzQPPA84DzwPQA4MDjAPRA4MD0QPSA4wDiwPKA4wDygPRA9EDygPMA9EDzAPTA9ID0QPTA9ID0wPUA9ADzwPVA9AD1QPWA88DjQOQA88DkAPVA9UDkAOFA9UDhQPXA9YD1QPXA9YD1wPYA3sDhAPZA3sD2QPaA4QDgwPSA4QD0gPZA9kD0gPUA9kD1APbA9oD2QPbA9oD2wPcA9gD1wPdA9gD3QPeA9cDhQOIA9cDiAPdA90DiAN9A90DfQPfA94D3QPfA94D3wPgA3MDfAPhA3MD4QPiA3wDewPaA3wD2gPhA+ED2gPcA+ED3APjA+ID4QPjA+ID4wPkA+AD3wPlA+AD5QPmA98DfQOAA98DgAPlA+UDgAN1A+UDdQPnA+YD5QPnA+YD5wPoA2sDdAPpA2sD6QPqA3QDcwPiA3QD4gPpA+kD4gPkA+kD5APrA+oD6QPrA+oD6wPsA+gD5wPtA+gD7QPuA+cDdQN4A+cDeAPtA+0DeANtA+0DbQPvA+4D7QPvA+4D7wPwA2MDbAPxA2MD8QPyA2wDawPqA2wD6gPxA/ED6gPsA/ED7APzA/ID8QPzA/ID8wP0A/AD7wP1A/AD9QP2A+8DbQNwA+8DcAP1A/UDcANlA/UDZQP3A/YD9QP3A/YD9wP4A0gDZAP5A0gD+QP6A2QDYwPyA2QD8gP5A/kD8gP0A/kD9AP7A/oD+QP7A/oD+wP8A/gD9wP9A/gD/QP+A/cDZQNoA/cDaAP9A/0DaANLA/0DSwP/A/4D/QP/A/4D/wMABFwDWwMBBFwDAQSvA1sDVAMCBFsDAgQBBAEEAgQDBAEEAwQEBK8DAQQEBK8DBASwAwUEBgQHBAUEBwQIBAYEWANeAwYEXgMHBAcEXgNgAwcEYAO0AwgEBwS0AwgEtAOzA1QDUwMJBFQDCQQCBFMDSgMKBFMDCgQJBAkECgQLBAkECwQMBAIECQQMBAIEDAQDBA0EDgQPBA0EDwQQBA4EUANWAw4EVgMPBA8EVgNYAw8EWAMGBBAEDwQGBBAEBgQFBEoDSQMRBEoDEQQKBEkDSAP6A0kD+gMRBBEE+gP8AxEE/AMSBAoEEQQSBAoEEgQLBAAE/wMTBAAEEwQUBP8DSwNOA/8DTgMTBBMETgNQAxMEUAMOBBQEEwQOBBQEDgQNBLUBswEVBLUBFQQWBLMBqQEXBLMBFwQVBBUEFwQYBBUEGAQZBBYEFQQZBBYEGQQaBBsEHAQdBBsEHQQeBBwEsQG7ARwEuwEdBB0EuwG1AR0EtQEWBB4EHQQWBB4EFgQaBKkBpwEfBKkBHwQXBKcBnQEgBKcBIAQfBB8EIAQhBB8EIQQiBBcEHwQiBBcEIgQYBCMEJAQlBCMEJQQmBCQEpQGyASQEsgElBCUEsgGxASUEsQEcBCYEJQQcBCYEHAQbBJ0BmwEnBJ0BJwQgBJsBkQEoBJsBKAQnBCcEKAQpBCcEKQQqBCAEJwQqBCAEKgQhBCsELAQtBCsELQQuBCwEmQGmASwEpgEtBC0EpgGlAS0EpQEkBC4ELQQkBC4EJAQjBJEBjwEvBJEBLwQoBI8BhQEwBI8BMAQvBC8EMAQxBC8EMQQyBCgELwQyBCgEMgQpBDMENAQ1BDMENQQ2BDQEjQGaATQEmgE1BDUEmgGZATUEmQEsBDYENQQsBDYELAQrBIUBgwE3BIUBNwQwBIMBeQE4BIMBOAQ3BDcEOAQ5BDcEOQQ6BDAENwQ6BDAEOgQxBDsEPAQ9BDsEPQQ+BDwEgQGOATwEjgE9BD0EjgGNAT0EjQE0BD4EPQQ0BD4ENAQzBHkBdwE/BHkBPwQ4BHcBbQFABHcBQAQ/BD8EQARBBD8EQQRCBDgEPwRCBDgEQgQ5BEMERARFBEMERQRGBEQEdQGCAUQEggFFBEUEggGBAUUEgQE8BEYERQQ8BEYEPAQ7BG0BawFHBG0BRwRABGsBYQFIBGsBSARHBEcESARJBEcESQRKBEAERwRKBEAESgRBBEsETARNBEsETQROBEwEaQF2AUwEdgFNBE0EdgF1AU0EdQFEBE4ETQREBE4ERARDBGEBXwFPBGEBTwRIBF8BVQFQBF8BUARPBE8EUARRBE8EUQRSBEgETwRSBEgEUgRJBFMEVARVBFMEVQRWBFQEXQFqAVQEagFVBFUEagFpAVUEaQFMBFYEVQRMBFYETARLBFUBUwFXBFUBVwRQBFMBRQFYBFMBWARXBFcEWARZBFcEWQRaBFAEVwRaBFAEWgRRBFsEXARdBFsEXQReBFwETgFeAVwEXgFdBF0EXgFdAV0EXQFUBF4EXQRUBF4EVARTBF8EYARhBF8EYQRiBGAEYwRkBGAEZARhBGEEZARlBGEEZQRmBGIEYQRmBGIEZgRnBGgEaQRqBGgEagRrBGkEYwRgBGkEYARqBGoEYARfBGoEXwRsBGsEagRsBGsEbARtBG4EbwRwBG4EcARxBG8EXwRiBG8EYgRwBHAEYgRnBHAEZwRyBHEEcARyBHEEcgRzBG0EbAR0BG0EdAR1BGwEXwRvBGwEbwR0BHQEbwRuBHQEbgR2BHUEdAR2BHUEdgR3BHgEeQR6BHgEegR7BHkEbgRxBHkEcQR6BHoEcQRzBHoEcwR8BHsEegR8BHsEfAR9BHcEdgR+BHcEfgR/BHYEbgR5BHYEeQR+BH4EeQR4BH4EeASABH8EfgSABH8EgASBBAIBBQGCBAIBggSDBAUBCgGEBAUBhASCBIIEhAR4BIIEeAR7BIMEggR7BIMEewR9BHgEhASFBHgEhQSABIQECgEQAYQEEAGFBIUEEAEPAYUEDwGGBIAEhQSGBIAEhgSBBBEBFAGHBBEBhwSIBBQBAgGDBBQBgwSHBIcEgwR9BIcEfQSJBIgEhwSJBIgEiQSKBIEEhgSLBIEEiwSMBIYEDwEcAYYEHAGLBIsEHAEbAYsEGwGNBIwEiwSNBIwEjQSOBB0BIAGPBB0BjwSQBCABEQGIBCABiASPBI8EiASKBI8EigSRBJAEjwSRBJAEkQSSBI4EjQSTBI4EkwSUBI0EGwEoAY0EKAGTBJMEKAEnAZMEJwGVBJQEkwSVBJQElQSWBCkBLAGXBCkBlwSYBCwBHQGQBCwBkASXBJcEkASSBJcEkgSZBJgElwSZBJgEmQSaBJYElQSbBJYEmwScBJUEJwE0AZUENAGbBJsENAEzAZsEMwGdBJwEmwSdBJwEnQSeBJIEnwSgBJIEoASZBJ8EcwRyBJ8EcgSgBKAEcgRnBKAEZwShBJkEoAShBJkEoQSaBG0EdQSiBG0EogSjBHUEdwSkBHUEpASiBKIEpASWBKIElgScBKMEogScBKMEnASeBJIEkQSlBJIEpQSfBJEEigSJBJEEiQSlBKUEiQR9BKUEfQR8BJ8EpQR8BJ8EfARzBIEEjASmBIEEpgR/BIwEjgSUBIwElASmBKYElASWBKYElgSkBH8EpgSkBH8EpAR3BKcEqASpBKcEqQSqBKgEmgShBKgEoQSpBKkEoQRnBKkEZwRmBKoEqQRmBKoEZgRlBG0EowSrBG0EqwRrBKMEngSsBKMErASrBKsErAStBKsErQSuBGsEqwSuBGsErgRoBDUBOAGvBDUBrwSwBDgBKQGYBDgBmASvBK8EmASaBK8EmgSoBLAErwSoBLAEqASnBJ4EnQSxBJ4EsQSsBJ0EMwFAAZ0EQAGxBLEEQAE/AbEEPwGyBKwEsQSyBKwEsgStBOwCswS0BOwCtATyArMEtQS2BLMEtgS0BLQEtgS3BLQEtwS4BPICtAS4BPICuAT0ArkEugS7BLkEuwS8BLoEvQS+BLoEvgS7BLsEvgTwArsE8AL5ArwEuwT5ArwE+QL4AjUBsAS/BDUBvwTqArAEpwTABLAEwAS/BL8EwAS1BL8EtQSzBOoCvwSzBOoCswTsAr0EwQTCBL0EwgS+BMEErQSyBMEEsgTCBMIEsgQ/AcIEPwHxAr4EwgTxAr4E8QLwAkEB+wLDBEEBwwTEBPsC9AK4BPsCuATDBMMEuAS3BMMEtwTFBMQEwwTFBMQExQTGBLkEvATHBLkExwTIBLwE+AL9ArwE/QLHBMcE/QJQAccEUAHJBMgExwTJBMgEyQTKBEEBxATLBEEBywRCAcQExgTMBMQEzATLBMsEzARZBMsEWQRYBEIBywRYBEIBWARFAVsEzQTOBFsEzgRcBM0EygTJBM0EyQTOBM4EyQRQAc4EUAFPAVwEzgRPAVwETwFOAc8E0ATRBM8E0QTSBNAE0wTUBNAE1ATRBNEE1ATVBNEE1QTWBNIE0QTWBNIE1gTXBNgE2QTaBNgE2gTbBNkE3ATdBNkE3QTaBNoE3QTPBNoEzwTSBNsE2gTSBNsE0gTXBNcE1gTeBNcE3gTfBNYE1QTgBNYE4ATeBN4E4AThBN4E4QTiBN8E3gTiBN8E4gTjBOQE5QTmBOQE5gTnBOUE2ATbBOUE2wTmBOYE2wTXBOYE1wTfBOcE5gTfBOcE3wTjBOME4gToBOME6ATpBOIE4QTqBOIE6gToBOgE6gTrBOgE6wTsBOkE6ATsBOkE7ATtBO4E7wTwBO4E8ATxBO8E5ATnBO8E5wTwBPAE5wTjBPAE4wTpBPEE8ATpBPEE6QTtBO0E7ATyBO0E8gTzBOwE6wT0BOwE9ATyBPIE9ARlBPIEZQRkBPME8gRkBPMEZARjBGgE9QT2BGgE9gRpBPUE7gTxBPUE8QT2BPYE8QTtBPYE7QTzBGkE9gTzBGkE8wRjBKcEqgT3BKcE9wTABKoEZQT0BKoE9AT3BPcE9ATrBPcE6wT4BMAE9wT4BMAE+AS1BO4E9QT5BO4E+QT6BPUEaASuBPUErgT5BPkErgStBPkErQTBBPoE+QTBBPoEwQS9BEkEUgT7BEkE+wT8BFIEUQT9BFIE/QT7BPsE/QT+BPsE/gT/BPwE+wT/BPwE/wQABQEFAgUDBQEFAwUEBQIFUwRWBAIFVgQDBQMFVgRLBAMFSwQFBQQFAwUFBQQFBQUGBQcFCAUJBQcFCQUKBQgFCwUMBQgFDAUJBQkFDAXTBAkF0wTQBAoFCQXQBAoF0ATPBNwEDQUOBdwEDgXdBA0FDwUQBQ0FEAUOBQ4FEAUHBQ4FBwUKBd0EDgUKBd0ECgXPBBEFEgUTBREFEwUUBRIFFQUWBRIFFgUTBRMFFgUXBRMFFwUYBRQFEwUYBRQFGAUZBRoFGwUcBRoFHAUdBRsFHgUfBRsFHwUcBRwFHwURBRwFEQUUBR0FHAUUBR0FFAUZBRkFGAUgBRkFIAUhBRgFFwUiBRgFIgUgBSAFIgUjBSAFIwUkBSEFIAUkBSEFJAUlBSYFJwUoBSYFKAUpBScFGgUdBScFHQUoBSgFHQUZBSgFGQUhBSkFKAUhBSkFIQUlBSUFJAUqBSUFKgUrBSQFIwUsBSQFLAUqBSoFLAULBSoFCwUIBSsFKgUIBSsFCAUHBQ8FLQUuBQ8FLgUQBS0FJgUpBS0FKQUuBS4FKQUlBS4FJQUrBRAFLgUrBRAFKwUHBTkEQgQvBTkELwUwBUIEQQQxBUIEMQUvBS8FMQUyBS8FMgUzBTAFLwUzBTAFMwU0BTUFNgU3BTUFNwU4BTYFQwRGBDYFRgQ3BTcFRgQ7BDcFOwQ5BTgFNwU5BTgFOQU6BTQFMwU7BTQFOwU8BTMFMgU9BTMFPQU7BTsFPQU+BTsFPgU/BTwFOwU/BTwFPwVABUEFQgVDBUEFQwVEBUIFNQU4BUIFOAVDBUMFOAU6BUMFOgVFBUQFQwVFBUQFRQVGBUAFPwVHBUAFRwVIBT8FPgVJBT8FSQVHBUcFSQVKBUcFSgVLBUgFRwVLBUgFSwVMBU0FTgVPBU0FTwVQBU4FQQVEBU4FRAVPBU8FRAVGBU8FRgVRBVAFTwVRBVAFUQVSBUwFSwVTBUwFUwVUBUsFSgVVBUsFVQVTBVMFVQVWBVMFVgVXBVQFUwVXBVQFVwVYBVkFWgVbBVkFWwVcBVoFTQVQBVoFUAVbBVsFUAVSBVsFUgVdBVwFWwVdBVwFXQVeBV8FYAVhBV8FYQViBWAFYwVkBWAFZAVhBWEFZAVYBWEFWAVXBWIFYQVXBWIFVwVWBV4FZQVmBV4FZgVcBWUFZwVoBWUFaAVmBWYFaAVpBWYFaQVqBVwFZgVqBVwFagVZBQsFLAVrBQsFawVsBSwFIwVtBSwFbQVrBWsFbQVYBWsFWAVkBWwFawVkBWwFZAVjBV4FbgVvBV4FbwVlBW4FJgUtBW4FLQVvBW8FLQUPBW8FDwVwBWUFbwVwBWUFcAVnBSMFIgVxBSMFcQVtBSIFFwVyBSIFcgVxBXEFcgVMBXEFTAVUBW0FcQVUBW0FVAVYBVIFcwV0BVIFdAVdBXMFGgUnBXMFJwV0BXQFJwUmBXQFJgVuBV0FdAVuBV0FbgVeBRcFFgV1BRcFdQVyBRYFFQV2BRYFdgV1BXUFdgVABXUFQAVIBXIFdQVIBXIFSAVMBUYFdwV4BUYFeAVRBXcFHgUbBXcFGwV4BXgFGwUaBXgFGgVzBVEFeAVzBVEFcwVSBRUFeQV6BRUFegV2BXkFewV8BXkFfAV6BXoFfAU0BXoFNAU8BXYFegU8BXYFPAVABToFfQV+BToFfgVFBX0FfwWABX0FgAV+BX4FgAUeBX4FHgV3BUUFfgV3BUUFdwVGBTEEOgSBBTEEgQWCBToEOQQwBToEMAWBBYEFMAU0BYEFNAV8BYIFgQV8BYIFfAV7BToFOQWDBToFgwV9BTkFOwQ+BDkFPgSDBYMFPgQzBIMFMwSEBX0FgwWEBX0FhAV/BYUFhgWHBYUFhwWIBYYFewV5BYYFeQWHBYcFeQUVBYcFFQUSBYgFhwUSBYgFEgURBR4FgAWJBR4FiQUfBYAFfwWKBYAFigWJBYkFigWFBYkFhQWIBR8FiQWIBR8FiAURBRgEIgSLBRgEiwWMBSIEIQQqBCIEKgSLBYsFKgQpBIsFKQQyBIwFiwUyBIwFMgQxBCsELgSNBSsEjQU2BC4EIwQmBC4EJgSNBY0FJgQbBI0FGwSOBTYEjQWOBTYEjgUzBBgEjAWPBRgEjwWQBYwFMQSCBYwFggWPBY8FggV7BY8FewWGBZAFjwWGBZAFhgWFBX8FhAWRBX8FkQWKBYQFMwSOBYQFjgWRBZEFjgUbBJEFGwSSBYoFkQWSBYoFkgWFBRoEGQSTBRoEkwWUBRgEkAWTBRgEkwUZBIUFlAWTBYUFkwWQBYUFkgWVBYUFlQWUBRsEHgSVBRsElQWSBRoElAWVBRoElQUeBEEESgSWBUEElgUxBUoESQT8BEoE/ASWBZYF/AQABZYFAAWXBTEFlgWXBTEFlwUyBQYFBQWYBQYFmAWZBQUFSwROBAUFTgSYBZgFTgRDBJgFQwQ2BZkFmAU2BZkFNgU1BQAFmgWbBQAFmwWXBZoFnAWdBZoFnQWbBZsFnQU+BZsFPgU9BZcFmwU9BZcFPQUyBUEFngWfBUEFnwVCBZ4FoAWhBZ4FoQWfBZ8FoQUGBZ8FBgWZBUIFnwWZBUIFmQU1BZwFogWjBZwFowWdBaIFpAWlBaIFpQWjBaMFpQVKBaMFSgVJBZ0FowVJBZ0FSQU+BU0FpgWnBU0FpwVOBaYFqAWpBaYFqQWnBacFqQWgBacFoAWeBU4FpwWeBU4FngVBBV8FYgWqBV8FqgWrBWIFVgVVBWIFVQWqBaoFVQVKBaoFSgWlBasFqgWlBasFpQWkBU0FWgWsBU0FrAWmBVoFWQVqBVoFagWsBawFagVpBawFaQWtBaYFrAWtBaYFrQWoBesE6gSuBesErgWvBeoE4QSwBeoEsAWuBa4FsAWxBa4FsQWyBa8FrgWyBa8FsgWzBbQFtQW2BbQFtgW3BbUF5ATvBLUF7wS2BbYF7wTuBLYF7gS4BbcFtgW4BbcFuAW5Bf4EugW7Bf4EuwW8BboFswWyBboFsgW7BbsFsgWxBbsFsQW9BbwFuwW9BbwFvQW+BbQFtwW/BbQFvwXABbcFuQXBBbcFwQW/Bb8FwQUBBb8FAQXCBcAFvwXCBcAFwgXDBVEEWgTEBVEExAX9BFoEWQTFBVoExQXEBcQFxQWzBcQFswW6Bf0ExAW6Bf0EugX+BLkFxgXHBbkFxwXBBcYFWwReBMYFXgTHBccFXgRTBMcFUwQCBcEFxwUCBcEFAgUBBVkEyAXJBVkEyQXFBcgFtwS2BMgFtgTJBckFtgS1BMkFtQTKBcUFyQXKBcUFygWzBb0EugTLBb0EywXMBboEuQTNBboEzQXLBcsFzQVbBMsFWwTGBcwFywXGBcwFxgW5BbUE+ATOBbUEzgXKBesErwXOBesEzgX4BLMFygXOBbMFzgWvBbkFuAXPBbkFzwXMBe4E+gTPBe4EzwW4Bb0EzAXPBb0EzwX6BFkEzATQBVkE0AXIBcYExQTQBcYE0AXMBLcEyAXQBbcE0AXFBLkEyATRBbkE0QXNBcoEzQTRBcoE0QXIBFsEzQXRBVsE0QXNBF8F0gXTBV8F0wVgBdIF1AXVBdIF1QXTBdMF1QXWBdMF1gXXBWAF0wXXBWAF1wVjBdgF2QXaBdgF2gXbBdkF3AXdBdkF3QXaBdoF3QVpBdoFaQVoBdsF2gVoBdsFaAVnBQsFbAXeBQsF3gUMBWwFYwXXBWwF1wXeBd4F1wXWBd4F1gXfBQwF3gXfBQwF3wXTBNgF2wXgBdgF4AXhBdsFZwVwBdsFcAXgBeAFcAUPBeAFDwUNBeEF4AUNBeEFDQXcBL4FvQXiBb4F4gXjBb0FsQXkBb0F5AXiBeIF5AXWBeIF1gXVBeMF4gXVBeMF1QXUBdgF5QXmBdgF5gXZBeUFtAXABeUFwAXmBeYFwAXDBeYFwwXnBdkF5gXnBdkF5wXcBeEE4AToBeEE6AWwBeAE1QTpBeAE6QXoBegF6QXWBegF1gXkBbAF6AXkBbAF5AWxBdgF6gXrBdgF6wXlBeoF2ATlBOoF5QTrBesF5QTkBOsF5AS1BeUF6wW1BeUFtQW0BdME3wXsBdME7AXUBNYF6QXsBdYF7AXfBdUE1ATsBdUE7AXpBdgE6gXtBdgE7QXZBNgF4QXtBdgF7QXqBdwE2QTtBdwE7QXhBe4F7wXwBe4F8AXxBe8F8gXzBe8F8wXwBfAF8wX0BfAF9AX1BfEF8AX1BfEF9QX2BfcF+AX5BfcF+QX6BfgF+wX8BfgF/AX5BfkF/AX9BfkF/QX+BfoF+QX+BfoF/gX/Be4F8QUABu4FAAYBBvEF9gUCBvEFAgYABgAGAgYDBgAGAwYEBgEGAAYEBgEGBAYFBgYGBwYIBgYGCAYJBgcG/wX+BQcG/gUIBggG/gX9BQgG/QUKBgkGCAYKBgkGCgYLBgUGBAYMBgUGDAYNBgQGAwYOBgQGDgYMBgwGDgYPBgwGDwYQBg0GDAYQBg0GEAYRBhIGEwYUBhIGFAYVBhMGBgYJBhMGCQYUBhQGCQYLBhQGCwYWBhUGFAYWBhUGFgYXBhEGEAYYBhEGGAYZBhAGDwYaBhAGGgYYBhgGGgYbBhgGGwYcBhkGGAYcBhkGHAYdBh4GHwYgBh4GIAYhBh8GEgYVBh8GFQYgBiAGFQYXBiAGFwYiBiEGIAYiBiEGIgYjBh0GHAYkBh0GJAYlBhwGGwYmBhwGJgYkBiQGJgYnBiQGJwYoBiUGJAYoBiUGKAYpBioGKwYsBioGLAYtBisGHgYhBisGIQYsBiwGIQYjBiwGIwYuBi0GLAYuBi0GLgYvBikGKAYwBikGMAYxBigGJwYyBigGMgYwBjAGMgYzBjAGMwY0BjEGMAY0BjEGNAY1BjYGNwY4BjYGOAY5BjcGKgYtBjcGLQY4BjgGLQYvBjgGLwY6BjkGOAY6BjkGOgY7BicGPAY9BicGPQYyBjwGPgY/BjwGPwY9Bj0GPwZABj0GQAZBBjIGPQZBBjIGQQYzBkIGQwZEBkIGRAZFBkMGRgZHBkMGRwZEBkQGRwYqBkQGKgY3BkUGRAY3BkUGNwY2BhsGSAZJBhsGSQYmBkgGSgZLBkgGSwZJBkkGSwY+BkkGPgY8BiYGSQY8BiYGPAYnBkYGTAZNBkYGTQZHBkwGTgZPBkwGTwZNBk0GTwYeBk0GHgYrBkcGTQYrBkcGKwYqBg8GUAZRBg8GUQYaBlAGUgZTBlAGUwZRBlEGUwZKBlEGSgZIBhoGUQZIBhoGSAYbBk4GVAZVBk4GVQZPBlQGVgZXBlQGVwZVBlUGVwYSBlUGEgYfBk8GVQYfBk8GHwYeBgMGWAZZBgMGWQYOBlgGWgZbBlgGWwZZBlkGWwZSBlkGUgZQBg4GWQZQBg4GUAYPBlYGXAZdBlYGXQZXBlwGXgZfBlwGXwZdBl0GXwYGBl0GBgYTBlcGXQYTBlcGEwYSBvYFYAZhBvYFYQYCBmAGYgZjBmAGYwZhBmEGYwZaBmEGWgZYBgIGYQZYBgIGWAYDBl4GZAZlBl4GZQZfBmQGZgZnBmQGZwZlBmUGZwb/BWUG/wUHBl8GZQYHBl8GBwYGBvYF9QVoBvYFaAZgBvUF9AVpBvUFaQZoBmgGaQZqBmgGagZrBmAGaAZrBmAGawZiBmwGbQZuBmwGbgZvBm0G9wX6BW0G+gVuBm4G+gX/BW4G/wVnBm8GbgZnBm8GZwZmBv4EvAVwBv4EcAZxBrwFvgVyBrwFcgZwBnAGcgZzBnAGcwZ0BnEGcAZ0BnEGdAZ1BnYGdwZ4BnYGeAZ5BncGwwXCBXcGwgV4BngGwgUBBXgGAQV6BnkGeAZ6BnkGegZ7Br4FfAZ9Br4FfQZyBnwGNQY0BnwGNAZ9Bn0GNAYzBn0GMwZ+BnIGfQZ+BnIGfgZzBjYGOQZ/BjYGfwaABjkGOwaBBjkGgQZ/Bn8GgQbDBX8GwwV3BoAGfwZ3BoAGdwZ2BgAF/wSCBgAFggaaBf8E/gRxBv8EcQaCBoIGcQZ1BoIGdQaDBpoFggaDBpoFgwacBXsGegaEBnsGhAaFBnoGAQUEBXoGBAWEBoQGBAUGBYQGBgWhBYUGhAahBYUGoQWgBaQFhgaHBqQFhwaIBoYGiQaKBoYGigaHBocGigb0BYcG9AXzBYgGhwbzBYgG8wXyBfcFiwaMBvcFjAb4BYsGjQaOBosGjgaMBowGjgaoBYwGqAWPBvgFjAaPBvgFjwb7BTMGQQaQBjMGkAZ+BkEGQAaRBkEGkQaQBpAGkQaSBpAGkgaTBn4GkAaTBn4GkwZzBpQGlQaWBpQGlgaXBpUGQgZFBpUGRQaWBpYGRQY2BpYGNgaABpcGlgaABpcGgAZ2BpgGmQaaBpgGmgabBpkGnAadBpkGnQaaBpoGnQZzBpoGcwaTBpsGmgaTBpsGkwaSBnYGngafBnYGnwaXBp4GoAahBp4GoQafBp8GoQaiBp8GogajBpcGnwajBpcGowaUBqQGpQamBqQGpganBqUGnAaZBqUGmQamBqYGmQaYBqYGmAaoBqcGpgaoBqcGqAapBqIGoQaqBqIGqgarBqEGoAasBqEGrAaqBqoGrAatBqoGrQauBqsGqgauBqsGrgavBrAGsQayBrAGsgazBrEGtAa1BrEGtQayBrIGtQacBrIGnAalBrMGsgalBrMGpQakBqAGtga3BqAGtwasBrYGuAa5BrYGuQa3BrcGuQa6BrcGuga7BqwGtwa7BqwGuwatBokGvAa9BokGvQa+BrwGtAaxBrwGsQa9Br0GsQawBr0GsAa/Br4GvQa/Br4GvwbABroGuQbBBroGwQbCBrkGuAbDBrkGwwbBBsEGwwaNBsEGjQbEBsIGwQbEBsIGxAbFBvQFigbGBvQFxgZpBooGiQa+BooGvgbGBsYGvgbABsYGwAbHBmkGxgbHBmkGxwZqBsUGxAbIBsUGyAbJBsQGjQaLBsQGiwbIBsgGiwb3BcgG9wVtBskGyAZtBskGbQZsBpwFygbLBpwFywaiBcoGtAa8BsoGvAbLBssGvAaJBssGiQaGBqIFywaGBqIFhgakBY0GwwbMBo0GzAaOBsMGuAbNBsMGzQbMBswGzQagBcwGoAWpBY4GzAapBY4GqQWoBZwFgwbOBpwFzgbKBoMGdQbPBoMGzwbOBs4GzwacBs4GnAa1BsoGzga1BsoGtQa0BqAG0AbRBqAG0Qa2BtAGewaFBtAGhQbRBtEGhQagBdEGoAXNBrYG0QbNBrYGzQa4BnUGdAbSBnUG0gbPBnMGnQbSBnMG0gZ0BpwGzwbSBpwG0gadBqAGngbTBqAG0wbQBnYGeQbTBnYG0waeBnsG0AbTBnsG0wZ5BmoGxwbUBmoG1AbVBscGwAbWBscG1gbUBtQG1gbXBtQG1wbYBtUG1AbYBtUG2AbZBtoG2wbcBtoG3AbdBtsGxQbJBtsGyQbcBtwGyQZsBtwGbAbeBt0G3AbeBt0G3gbfBsAGvwbgBsAG4AbWBr8GsAbhBr8G4QbgBuAG4QbiBuAG4gbjBtYG4AbjBtYG4wbXBuQG5QbmBuQG5gbnBuUGugbCBuUGwgbmBuYGwgbFBuYGxQbbBucG5gbbBucG2wbaBrAGswboBrAG6AbhBrMGpAbpBrMG6QboBugG6QbqBugG6gbrBuEG6AbrBuEG6wbiBuwG7QbuBuwG7gbvBu0GrQa7Bu0GuwbuBu4Guwa6Bu4GugblBu8G7gblBu8G5QbkBqQGpwbwBqQG8AbpBqcGqQbxBqcG8QbwBvAG8QbyBvAG8gbzBukG8AbzBukG8wbqBvQG9Qb2BvQG9gb3BvUGrwauBvUGrgb2BvYGrgatBvYGrQbtBvcG9gbtBvcG7QbsBqkGqAb4BqkG+AbxBqgGmAb5BqgG+Qb4BvgG+Qb6BvgG+gb7BvEG+Ab7BvEG+wbyBvwG/Qb+BvwG/gb/Bv0GogarBv0Gqwb+Bv4GqwavBv4Grwb1Bv8G/gb1Bv8G9Qb0BpgGmwYAB5gGAAf5BpsGkgYBB5sGAQcABwAHAQcCBwAHAgcDB/kGAAcDB/kGAwf6BgQHBQcGBwQHBgcHBwUHlAajBgUHowYGBwYHowaiBgYHogb9BgcHBgf9BgcH/Qb8BpIGkQYIB5IGCAcBB5EGQAYJB5EGCQcIBwgHCQcKBwgHCgcLBwEHCAcLBwEHCwcCBwwHDQcOBwwHDgcPBw0HQgaVBg0HlQYOBw4HlQaUBg4HlAYFBw8HDgcFBw8HBQcEB2IGawYQB2IGEAcRB2sGagbVBmsG1QYQBxAH1QbZBhAH2QYSBxEHEAcSBxEHEgcTB98G3gYUB98GFAcVB94GbAZvBt4GbwYUBxQHbwZmBhQHZgYWBxUHFAcWBxUHFgcXB1oGYwYYB1oGGAcZB2MGYgYRB2MGEQcYBxgHEQcTBxgHEwcaBxkHGAcaBxkHGgcbBxcHFgccBxcHHAcdBxYHZgZkBhYHZAYcBxwHZAZeBhwHXgYeBx0HHAceBx0HHgcfB1IGWwYgB1IGIAchB1sGWgYZB1sGGQcgByAHGQcbByAHGwciByEHIAciByEHIgcjBx8HHgckBx8HJAclBx4HXgZcBh4HXAYkByQHXAZWBiQHVgYmByUHJAcmByUHJgcnB0oGUwYoB0oGKAcpB1MGUgYhB1MGIQcoBygHIQcjBygHIwcqBykHKAcqBykHKgcrBycHJgcsBycHLActByYHVgZUBiYHVAYsBywHVAZOBiwHTgYuBy0HLAcuBy0HLgcvBz4GSwYwBz4GMAcxB0sGSgYpB0sGKQcwBzAHKQcrBzAHKwcyBzEHMAcyBzEHMgczBy8HLgc0By8HNAc1By4HTgZMBi4HTAY0BzQHTAZGBjQHRgY2BzUHNAc2BzUHNgc3B0AGPwY4B0AGOAcJBz8GPgYxBz8GMQc4BzgHMQczBzgHMwc5BwkHOAc5BwkHOQcKBzcHNgc6BzcHOgc7BzYHRgZDBjYHQwY6BzoHQwZCBjoHQgYNBzsHOgcNBzsHDQcMB/IG+wY8B/IGPAc9B/sG+gY+B/sGPgc8BzwHPgc/BzwHPwdABz0HPAdABz0HQAdBB0IHQwdEB0IHRAdFB0MH/Ab/BkMH/wZEB0QH/wb0BkQH9AZGB0UHRAdGB0UHRgdHB0EHQAdIB0EHSAdJB0AHPwdKB0AHSgdIB0gHSgdLB0gHSwdMB0kHSAdMB0kHTAdNB04HTwdQB04HUAdRB08HQgdFB08HRQdQB1AHRQdHB1AHRwdSB1EHUAdSB1EHUgdTB00HTAdUB00HVAdVB0wHSwdWB0wHVgdUB1QHVgdXB1QHVwdYB1UHVAdYB1UHWAdZB1oHWwdcB1oHXAddB1sHTgdRB1sHUQdcB1wHUQdTB1wHUwdeB10HXAdeB10HXgdfB1kHWAdgB1kHYAdhB1gHVwdiB1gHYgdgB2AHYgdjB2AHYwdkB2EHYAdkB2EHZAdlB2YHZwdoB2YHaAdpB2cHWgddB2cHXQdoB2gHXQdfB2gHXwdqB2kHaAdqB2kHagdrBxMHEgdsBxMHbAdtBxIH2QZuBxIHbgdsB2wHbgdZB2wHWQdhB20HbAdhB20HYQdlB18HbwdwB18HcAdqB28H3wYVB28HFQdwB3AHFQcXB3AHFwdxB2oHcAdxB2oHcQdrB9cGcgdzB9cGcwfYBnIHTQdVB3IHVQdzB3MHVQdZB3MHWQduB9gGcwduB9gGbgfZBl8HXgd0B18HdAdvB14HUwd1B14HdQd0B3QHdQfaBnQH2gbdBm8HdAfdBm8H3QbfBtcG4wZ2B9cGdgdyB+MG4gZ3B+MGdwd2B3YHdwdBB3YHQQdJB3IHdgdJB3IHSQdNB0cHeAd5B0cHeQdSB3gH5AbnBngH5wZ5B3kH5wbaBnkH2gZ1B1IHeQd1B1IHdQdTB/IGPQd6B/IGegfzBj0HQQd3Bz0Hdwd6B3oHdwfiBnoH4gbrBvMGegfrBvMG6wbqBuQGeAd7B+QGewfvBngHRwdGB3gHRgd7B3sHRgf0BnsH9Ab3Bu8Gewf3Bu8G9wbsBgIHCwd8BwIHfAcDBwsHCgd9BwsHfQd8B3wHfQc/B3wHPwc+BwMHfAc+BwMHPgf6BkIHfgd/B0IHfwdDB34HDAcPB34HDwd/B38HDwcEB38HBAcHB0MHfwcHB0MHBwf8BjMHgAeBBzMHgQc5B4AHSwdKB4AHSgeBB4EHSgc/B4EHPwd9BzkHgQd9BzkHfQcKB0IHTweCB0IHggd+B08HTgeDB08HgweCB4IHgwc3B4IHNwc7B34Hggc7B34HOwcMBysHhAeFBysHhQcyB4QHVwdWB4QHVgeFB4UHVgdLB4UHSweABzIHhQeABzIHgAczB04HWweGB04HhgeDB1sHWgeHB1sHhweGB4YHhwcvB4YHLwc1B4MHhgc1B4MHNQc3ByMHiAeJByMHiQcqB4gHYwdiB4gHYgeJB4kHYgdXB4kHVweEByoHiQeEByoHhAcrB1oHZweKB1oHigeHB2cHZgeLB2cHiweKB4oHiwcnB4oHJwctB4cHigctB4cHLQcvBxsHjAeNBxsHjQciB4wHZQdkB4wHZAeNB40HZAdjB40HYweIByIHjQeIByIHiAcjB2YHaQeOB2YHjgeLB2kHawePB2kHjweOB44HjwcfB44HHwclB4sHjgclB4sHJQcnBxMHbQeQBxMHkAcaB2UHjAeQB2UHkAdtBxsHGgeQBxsHkAeMBx8HjweRBx8HkQcdB2sHcQeRB2sHkQePBxcHHQeRBxcHkQdxBykGMQaSBykGkgeTBzEGNQaUBzEGlAeSB5IHlAeVB5IHlQeWB5MHkgeWB5MHlgeXB5gHmQeaB5gHmgebB5kHOwY6BpkHOgaaB5oHOgYvBpoHLwacB5sHmgecB5sHnAedBx0GJQaeBx0GngefByUGKQaTByUGkweeB54HkweXB54HlwegB58HngegB58HoAehB50HnAeiB50HogejB5wHLwYuBpwHLgaiB6IHLgYjBqIHIwakB6MHogekB6MHpAelBxEGGQamBxEGpgenBxkGHQafBxkGnwemB6YHnwehB6YHoQeoB6cHpgeoB6cHqAepB6UHpAeqB6UHqgerB6QHIwYiBqQHIgaqB6oHIgYXBqoHFwasB6sHqgesB6sHrAetBwUGDQauBwUGrgevBw0GEQanBw0GpweuB64HpwepB64HqQewB68HrgewB68HsAexB60HrAeyB60HsgezB6wHFwYWBqwHFgayB7IHFgYLBrIHCwa0B7MHsge0B7MHtAe1B+4FAQa2B+4Ftge3BwEGBQavBwEGrwe2B7YHrwexB7YHsQe4B7cHtge4B7cHuAe5B7UHtAe6B7UHuge7B7QHCwYKBrQHCga6B7oHCgb9BboH/QW8B7sHuge8B7sHvAe9B/IF7wW+B/IFvge/B+8F7gW3B+8Ftwe+B74Htwe5B74HuQfAB78HvgfAB78HwAfBB70HvAfCB70HwgfDB7wH/QX8BbwH/AXCB8IH/AX7BcIH+wXEB8MHwgfEB8MHxAfFB7kHxgfHB7kHxwfAB8YHlweWB8YHlgfHB8cHlgeVB8cHlQfIB8AHxwfIB8AHyAfBB5gHmwfJB5gHyQfKB5sHnQfLB5sHywfJB8kHywe9B8kHvQfDB8oHyQfDB8oHwwfFB7kHuAfMB7kHzAfGB7gHsQfNB7gHzQfMB8wHzQehB8wHoQegB8YHzAegB8YHoAeXB6UHzgfPB6UHzwejB84HtQe7B84HuwfPB88Huwe9B88HvQfLB6MHzwfLB6MHywedB7EHsAfQB7EH0AfNB6kHqAfQB6kH0AewB6EHzQfQB6EH0AeoB6UHqwfRB6UH0QfOB60HswfRB60H0QerB7UHzgfRB7UH0QezB18FqwXSB18F0gfTB6sFpAWIBqsFiAbSB9IHiAbyBdIH8gW/B9MH0ge/B9MHvwfBB/sFjwbUB/sF1AfEB48GqAWtBY8GrQXUB9QHrQVpBdQHaQXVB8QH1AfVB8QH1QfFB18F0wfWB18F1gfSBdMHwQfIB9MHyAfWB9YHyAeVB9YHlQfXB9IF1gfXB9IF1wfUBZgHygfYB5gH2AfZB8oHxQfVB8oH1QfYB9gH1QdpBdgHaQXdBdkH2AfdBdkH3QXcBb4F4wXaB74F2gd8BuMF1AXXB+MF1wfaB9oH1weVB9oHlQeUB3wG2geUB3wGlAc1BpgH2QfbB5gH2weZB9kH3AXnBdkH5wXbB9sH5wXDBdsHwwWBBpkH2weBBpkHgQY7Bg==" } ] } diff --git a/crates/bevy_app/src/app_builder.rs b/crates/bevy_app/src/app_builder.rs index 8c9b0d1052e39..dfd7aa236a7e7 100644 --- a/crates/bevy_app/src/app_builder.rs +++ b/crates/bevy_app/src/app_builder.rs @@ -170,7 +170,8 @@ impl AppBuilder { } pub fn add_default_stages(&mut self) -> &mut Self { - self.add_startup_stage(startup_stage::STARTUP) + self.add_startup_stage(startup_stage::PRE_STARTUP) + .add_startup_stage(startup_stage::STARTUP) .add_startup_stage(startup_stage::POST_STARTUP) .add_stage(stage::FIRST) .add_stage(stage::EVENT_UPDATE) diff --git a/crates/bevy_app/src/startup_stage.rs b/crates/bevy_app/src/startup_stage.rs index 65fd39be74ae6..54804904bdbf9 100644 --- a/crates/bevy_app/src/startup_stage.rs +++ b/crates/bevy_app/src/startup_stage.rs @@ -1,5 +1,8 @@ +/// Name of app stage that runs once before the startup stage +pub const PRE_STARTUP: &str = "pre_startup"; + /// Name of app stage that runs once when an app starts up pub const STARTUP: &str = "startup"; -/// Name of app stage that runs once after startup +/// Name of app stage that runs once after the startup stage pub const POST_STARTUP: &str = "post_startup"; diff --git a/crates/bevy_asset/Cargo.toml b/crates/bevy_asset/Cargo.toml index cc44ece932c40..4bdc7a8b93b7d 100644 --- a/crates/bevy_asset/Cargo.toml +++ b/crates/bevy_asset/Cargo.toml @@ -28,16 +28,12 @@ bevy_utils = { path = "../bevy_utils", version = "0.2.1" } # other uuid = { version = "0.8", features = ["v4", "serde"] } serde = { version = "1", features = ["derive"] } +ron = "0.6.2" crossbeam-channel = "0.4.4" anyhow = "1.0" thiserror = "1.0" +downcast-rs = "1.2.0" log = { version = "0.4", features = ["release_max_level_info"] } notify = { version = "5.0.0-pre.2", optional = true } parking_lot = "0.11.0" -async-trait = "0.1.40" - -[target.'cfg(target_arch = "wasm32")'.dependencies] -wasm-bindgen = { version = "0.2" } -web-sys = { version = "0.3", features = ["Request", "Window", "Response"]} -wasm-bindgen-futures = "0.4" -js-sys = "0.3" +rand = "0.7.3" diff --git a/crates/bevy_asset/src/asset_server.rs b/crates/bevy_asset/src/asset_server.rs index e241406d401f4..fb62719a3dbf8 100644 --- a/crates/bevy_asset/src/asset_server.rs +++ b/crates/bevy_asset/src/asset_server.rs @@ -1,414 +1,438 @@ use crate::{ - filesystem_watcher::FilesystemWatcher, AssetLoadError, AssetLoadRequestHandler, AssetLoader, - Assets, Handle, HandleId, LoadRequest, + path::{AssetPath, AssetPathId, SourcePathId}, + Asset, AssetIo, AssetIoError, AssetLifecycle, AssetLifecycleChannel, AssetLifecycleEvent, + AssetLoader, Assets, FileAssetIo, Handle, HandleId, HandleUntyped, LabelId, LoadContext, + LoadState, RefChange, RefChangeChannel, SourceInfo, SourceMeta, }; use anyhow::Result; -use bevy_ecs::{Res, Resource, Resources}; +use bevy_ecs::Res; use bevy_tasks::TaskPool; -use bevy_utils::{HashMap, HashSet}; +use bevy_utils::HashMap; use crossbeam_channel::TryRecvError; use parking_lot::RwLock; -use std::{ - fs, io, - path::{Path, PathBuf}, - sync::Arc, -}; +use std::{collections::hash_map::Entry, path::Path, sync::Arc}; use thiserror::Error; - -/// The type used for asset versioning -pub type AssetVersion = usize; +use uuid::Uuid; /// Errors that occur while loading assets with an AssetServer #[derive(Error, Debug)] pub enum AssetServerError { #[error("Asset folder path is not a directory.")] AssetFolderNotADirectory(String), - #[error("Invalid root path")] - InvalidRootPath, - #[error("No AssetHandler found for the given extension.")] - MissingAssetHandler, #[error("No AssetLoader found for the given extension.")] - MissingAssetLoader, + MissingAssetLoader(Option), + #[error("The given type does not match the type of the loaded asset.")] + IncorrectHandleType, #[error("Encountered an error while loading an asset.")] - AssetLoadError(#[from] AssetLoadError), - #[error("Encountered an io error.")] - Io(#[from] io::Error), - #[error("Failed to watch asset folder.")] - AssetWatchError { path: PathBuf }, + AssetLoaderError(anyhow::Error), + #[error("PathLoader encountered an error")] + PathLoaderError(#[from] AssetIoError), } -/// Info about a specific asset, such as its path and its current load state -#[derive(Clone, Debug)] -pub struct AssetInfo { - pub handle_id: HandleId, - pub path: PathBuf, - pub load_state: LoadState, +#[derive(Default)] +pub(crate) struct AssetRefCounter { + pub(crate) channel: Arc, + pub(crate) ref_counts: Arc>>, } -/// The load state of an asset -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum LoadState { - Loading(AssetVersion), - Loaded(AssetVersion), - Failed(AssetVersion), -} - -impl LoadState { - pub fn get_version(&self) -> AssetVersion { - match *self { - LoadState::Loaded(version) => version, - LoadState::Loading(version) => version, - LoadState::Failed(version) => version, - } - } +pub struct AssetServerInternal { + pub(crate) asset_io: TAssetIo, + pub(crate) asset_ref_counter: AssetRefCounter, + pub(crate) asset_sources: Arc>>, + pub(crate) asset_lifecycles: Arc>>>, + loaders: RwLock>>>, + extension_to_loader_index: RwLock>, + handle_to_path: Arc>>>, + task_pool: TaskPool, } /// Loads assets from the filesystem on background threads -pub struct AssetServer { - asset_folders: RwLock>, - asset_handlers: RwLock>>, - loaders: Vec, - task_pool: TaskPool, - extension_to_handler_index: HashMap, - extension_to_loader_index: HashMap, - asset_info: RwLock>, - asset_info_paths: RwLock>, - #[cfg(feature = "filesystem_watcher")] - filesystem_watcher: Arc>>, +pub struct AssetServer { + pub(crate) server: Arc>, } -impl AssetServer { - pub fn new(task_pool: TaskPool) -> Self { - AssetServer { - asset_folders: Default::default(), - asset_handlers: Default::default(), - loaders: Default::default(), - extension_to_handler_index: Default::default(), - extension_to_loader_index: Default::default(), - asset_info_paths: Default::default(), - asset_info: Default::default(), - task_pool, - #[cfg(feature = "filesystem_watcher")] - filesystem_watcher: Arc::new(RwLock::new(None)), +impl Clone for AssetServer { + fn clone(&self) -> Self { + Self { + server: self.server.clone(), } } +} - pub fn add_handler(&mut self, asset_handler: T) - where - T: AssetLoadRequestHandler, - { - let mut asset_handlers = self.asset_handlers.write(); - let handler_index = asset_handlers.len(); - for extension in asset_handler.extensions().iter() { - self.extension_to_handler_index - .insert(extension.to_string(), handler_index); +impl AssetServer { + pub fn new(source_io: TAssetIo, task_pool: TaskPool) -> Self { + AssetServer { + server: Arc::new(AssetServerInternal { + loaders: Default::default(), + extension_to_loader_index: Default::default(), + asset_sources: Default::default(), + asset_ref_counter: Default::default(), + handle_to_path: Default::default(), + asset_lifecycles: Default::default(), + task_pool, + asset_io: source_io, + }), } + } - asset_handlers.push(Arc::new(asset_handler)); + pub(crate) fn register_asset_type(&self) -> Assets { + self.server.asset_lifecycles.write().insert( + T::TYPE_UUID, + Box::new(AssetLifecycleChannel::::default()), + ); + Assets::new(self.server.asset_ref_counter.channel.sender.clone()) } - pub fn add_loader(&mut self, loader: TLoader) + pub fn add_loader(&self, loader: T) where - TLoader: AssetLoader, - TAsset: 'static, + T: AssetLoader, { - let loader_index = self.loaders.len(); + let mut loaders = self.server.loaders.write(); + let loader_index = loaders.len(); for extension in loader.extensions().iter() { - self.extension_to_loader_index + self.server + .extension_to_loader_index + .write() .insert(extension.to_string(), loader_index); } + loaders.push(Arc::new(Box::new(loader))); + } - let mut resources = Resources::default(); - resources.insert::>>(Box::new(loader)); - self.loaders.push(resources); + pub fn watch_for_changes(&self) -> Result<(), AssetServerError> { + self.server.asset_io.watch_for_changes()?; + Ok(()) } - pub fn load_asset_folder>( - &self, - path: P, - ) -> Result, AssetServerError> { - let root_path = self.get_root_path()?; - let asset_folder = root_path.join(path); - let handle_ids = self.load_assets_in_folder_recursive(&asset_folder)?; - self.asset_folders.write().push(asset_folder); - Ok(handle_ids) + pub fn get_handle>(&self, id: I) -> Handle { + let sender = self.server.asset_ref_counter.channel.sender.clone(); + Handle::strong(id.into(), sender) + } + + pub fn get_handle_untyped>(&self, id: I) -> HandleUntyped { + let sender = self.server.asset_ref_counter.channel.sender.clone(); + HandleUntyped::strong(id.into(), sender) } - pub fn get_handle>(&self, path: P) -> Option> { - self.asset_info_paths + fn get_asset_loader( + &self, + extension: &str, + ) -> Result>, AssetServerError> { + self.server + .extension_to_loader_index .read() - .get(path.as_ref()) - .map(|handle_id| Handle::from(*handle_id)) + .get(extension) + .map(|index| self.server.loaders.read()[*index].clone()) + .ok_or_else(|| AssetServerError::MissingAssetLoader(Some(extension.to_string()))) } - #[cfg(feature = "filesystem_watcher")] - fn watch_path_for_changes>( - filesystem_watcher: &mut Option, + fn get_path_asset_loader>( + &self, path: P, - ) -> Result<(), AssetServerError> { - if let Some(watcher) = filesystem_watcher { - watcher - .watch(&path) - .map_err(|_error| AssetServerError::AssetWatchError { - path: path.as_ref().to_owned(), - })?; - } - - Ok(()) + ) -> Result>, AssetServerError> { + path.as_ref() + .extension() + .and_then(|e| e.to_str()) + .ok_or(AssetServerError::MissingAssetLoader(None)) + .and_then(|extension| self.get_asset_loader(extension)) } - #[cfg(feature = "filesystem_watcher")] - pub fn watch_for_changes(&self) -> Result<(), AssetServerError> { - let mut filesystem_watcher = self.filesystem_watcher.write(); + pub fn get_handle_path>(&self, handle: H) -> Option> { + self.server + .handle_to_path + .read() + .get(&handle.into()) + .cloned() + } - let _ = filesystem_watcher.get_or_insert_with(FilesystemWatcher::default); - // watch current files - let asset_info_paths = self.asset_info_paths.read(); - for asset_path in asset_info_paths.keys() { - Self::watch_path_for_changes(&mut filesystem_watcher, asset_path)?; + pub fn get_load_state>(&self, handle: H) -> LoadState { + match handle.into() { + HandleId::AssetPathId(id) => { + let asset_sources = self.server.asset_sources.read(); + asset_sources + .get(&id.source_path_id()) + .map_or(LoadState::NotLoaded, |info| info.load_state) + } + HandleId::Id(_, _) => LoadState::NotLoaded, } - - Ok(()) } - #[cfg(not(target_arch = "wasm32"))] - fn get_root_path(&self) -> Result { - if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") { - Ok(PathBuf::from(manifest_dir)) - } else { - match std::env::current_exe() { - Ok(exe_path) => exe_path - .parent() - .ok_or(AssetServerError::InvalidRootPath) - .map(|exe_parent_path| exe_parent_path.to_owned()), - Err(err) => Err(AssetServerError::Io(err)), + pub fn get_group_load_state(&self, handles: impl IntoIterator) -> LoadState { + let mut load_state = LoadState::Loaded; + for handle_id in handles { + match handle_id { + HandleId::AssetPathId(id) => match self.get_load_state(id) { + LoadState::Loaded => continue, + LoadState::Loading => { + load_state = LoadState::Loading; + } + LoadState::Failed => return LoadState::Failed, + LoadState::NotLoaded => return LoadState::NotLoaded, + }, + HandleId::Id(_, _) => return LoadState::NotLoaded, } } - } - #[cfg(target_arch = "wasm32")] - fn get_root_path(&self) -> Result { - Ok(PathBuf::from("/")) + load_state } - // TODO: add type checking here. people shouldn't be able to request a Handle for a Mesh asset - pub fn load>(&self, path: P) -> Result, AssetServerError> { - self.load_untyped(self.get_root_path()?.join(path)) - .map(Handle::from) + pub fn load<'a, T: Asset, P: Into>>(&self, path: P) -> Handle { + self.load_untyped(path).typed() } - pub fn load_sync>( + // TODO: properly set failed LoadState in all failure cases + fn load_sync<'a, P: Into>>( &self, - assets: &mut Assets, path: P, - ) -> Result, AssetServerError> - where - T: 'static, - { - let path = self.get_root_path()?.join(path); - if let Some(ref extension) = path.extension() { - if let Some(index) = self.extension_to_loader_index.get( - extension - .to_str() - .expect("extension should be a valid string"), - ) { - let mut asset_info_paths = self.asset_info_paths.write(); - let handle_id = HandleId::new(); - let resources = &self.loaders[*index]; - let loader = resources.get::>>().unwrap(); - let asset = loader.load_from_file(path.as_ref())?; - let handle = Handle::from(handle_id); - - assets.set(handle, asset); - asset_info_paths.insert(path.to_owned(), handle_id); - Ok(handle) - } else { - Err(AssetServerError::MissingAssetHandler) + force: bool, + ) -> Result { + let asset_path: AssetPath = path.into(); + let asset_loader = self.get_path_asset_loader(asset_path.path())?; + let asset_path_id: AssetPathId = asset_path.get_id(); + + // load metadata and update source info. this is done in a scope to ensure we release the locks before loading + let version = { + let mut asset_sources = self.server.asset_sources.write(); + let source_info = match asset_sources.entry(asset_path_id.source_path_id()) { + Entry::Occupied(entry) => entry.into_mut(), + Entry::Vacant(entry) => entry.insert(SourceInfo { + asset_types: Default::default(), + committed_assets: Default::default(), + load_state: LoadState::NotLoaded, + meta: None, + path: asset_path.path().to_owned(), + version: 0, + }), + }; + + // if asset is already loaded (or is loading), don't load again + if !force + && source_info + .committed_assets + .contains(&asset_path_id.label_id()) + { + return Ok(asset_path_id); } - } else { - Err(AssetServerError::MissingAssetHandler) + + source_info.load_state = LoadState::Loading; + source_info.committed_assets.clear(); + source_info.version += 1; + source_info.meta = None; + source_info.version + }; + + // load the asset bytes + let bytes = self.server.asset_io.load_path(asset_path.path())?; + + // load the asset source using the corresponding AssetLoader + let mut load_context = LoadContext::new( + asset_path.path(), + &self.server.asset_ref_counter.channel, + &self.server.asset_io, + version, + ); + asset_loader + .load(&bytes, &mut load_context) + .map_err(AssetServerError::AssetLoaderError)?; + + // if version has changed since we loaded and grabbed a lock, return. theres is a newer version being loaded + let mut asset_sources = self.server.asset_sources.write(); + let source_info = asset_sources + .get_mut(&asset_path_id.source_path_id()) + .expect("AssetSource should exist at this point"); + if version != source_info.version { + return Ok(asset_path_id); } - } - pub fn load_untyped>(&self, path: P) -> Result { - let path = path.as_ref(); - if let Some(ref extension) = path.extension() { - if let Some(index) = self.extension_to_handler_index.get( - extension - .to_str() - .expect("Extension should be a valid string."), - ) { - let mut new_version = 0; - let handle_id = { - let mut asset_info = self.asset_info.write(); - let mut asset_info_paths = self.asset_info_paths.write(); - if let Some(asset_info) = asset_info_paths - .get(path) - .and_then(|handle_id| asset_info.get_mut(&handle_id)) - { - asset_info.load_state = - if let LoadState::Loaded(_version) = asset_info.load_state { - new_version += 1; - LoadState::Loading(new_version) - } else { - LoadState::Loading(new_version) - }; - asset_info.handle_id - } else { - let handle_id = HandleId::new(); - asset_info.insert( - handle_id, - AssetInfo { - handle_id, - path: path.to_owned(), - load_state: LoadState::Loading(new_version), - }, - ); - asset_info_paths.insert(path.to_owned(), handle_id); - handle_id - } - }; - - let load_request = LoadRequest { - handle_id, - path: path.to_owned(), - handler_index: *index, - version: new_version, - }; - - let handlers = self.asset_handlers.read(); - let request_handler = handlers[load_request.handler_index].clone(); - - self.task_pool - .spawn(async move { - request_handler.handle_request(&load_request).await; - }) - .detach(); - - // TODO: watching each asset explicitly is a simpler implementation, its possible it would be more efficient to watch - // folders instead (when possible) - #[cfg(feature = "filesystem_watcher")] - Self::watch_path_for_changes( - &mut self.filesystem_watcher.write(), - path.to_owned(), - )?; - Ok(handle_id) - } else { - Err(AssetServerError::MissingAssetHandler) - } - } else { - Err(AssetServerError::MissingAssetHandler) + // if all assets have been committed already (aka there were 0), set state to "Loaded" + if source_info.is_loaded() { + source_info.load_state = LoadState::Loaded; } - } - pub fn set_load_state(&self, handle_id: HandleId, load_state: LoadState) { - if let Some(asset_info) = self.asset_info.write().get_mut(&handle_id) { - if load_state.get_version() >= asset_info.load_state.get_version() { - asset_info.load_state = load_state; + // reset relevant SourceInfo fields + source_info.committed_assets.clear(); + // TODO: queue free old assets + source_info.asset_types.clear(); + + source_info.meta = Some(SourceMeta { + assets: load_context.get_asset_metas(), + }); + + // load asset dependencies and prepare asset type hashmap + for (label, loaded_asset) in load_context.labeled_assets.iter_mut() { + let label_id = LabelId::from(label.as_ref().map(|label| label.as_str())); + let type_uuid = loaded_asset.value.as_ref().unwrap().type_uuid(); + source_info.asset_types.insert(label_id, type_uuid); + for dependency in loaded_asset.dependencies.iter() { + self.load_untyped(dependency.clone()); } } - } - pub fn get_load_state_untyped(&self, handle_id: HandleId) -> Option { - self.asset_info - .read() - .get(&handle_id) - .map(|asset_info| asset_info.load_state.clone()) + self.server + .asset_io + .watch_path_for_changes(asset_path.path()) + .unwrap(); + self.create_assets_in_load_context(&mut load_context); + Ok(asset_path_id) } - pub fn get_load_state(&self, handle: Handle) -> Option { - self.get_load_state_untyped(handle.id) + pub fn load_untyped<'a, P: Into>>(&self, path: P) -> HandleUntyped { + let handle_id = self.load_untracked(path, false); + self.get_handle_untyped(handle_id) } - pub fn get_group_load_state(&self, handle_ids: &[HandleId]) -> Option { - let mut load_state = LoadState::Loaded(0); - for handle_id in handle_ids.iter() { - match self.get_load_state_untyped(*handle_id) { - Some(LoadState::Loaded(_)) => continue, - Some(LoadState::Loading(_)) => { - load_state = LoadState::Loading(0); - } - Some(LoadState::Failed(_)) => return Some(LoadState::Failed(0)), - None => return None, - } - } - - Some(load_state) + pub(crate) fn load_untracked<'a, P: Into>>( + &self, + path: P, + force: bool, + ) -> HandleId { + let asset_path: AssetPath<'a> = path.into(); + let server = self.clone(); + let owned_path = asset_path.to_owned(); + self.server + .task_pool + .spawn(async move { + server.load_sync(owned_path, force).unwrap(); + }) + .detach(); + asset_path.into() } - fn load_assets_in_folder_recursive( + pub fn load_folder>( &self, - path: &Path, - ) -> Result, AssetServerError> { - if !path.is_dir() { + path: P, + ) -> Result, AssetServerError> { + let path = path.as_ref(); + if !self.server.asset_io.is_directory(path) { return Err(AssetServerError::AssetFolderNotADirectory( path.to_str().unwrap().to_string(), )); } - let root_path = self.get_root_path()?; - let mut handle_ids = Vec::new(); - for entry in fs::read_dir(path)? { - let entry = entry?; - let child_path = entry.path(); - if child_path.is_dir() { - handle_ids.extend(self.load_assets_in_folder_recursive(&child_path)?); + let mut handles = Vec::new(); + for child_path in self.server.asset_io.read_directory(path.as_ref())? { + if self.server.asset_io.is_directory(&child_path) { + handles.extend(self.load_folder(&child_path)?); } else { - let relative_child_path = child_path.strip_prefix(&root_path).unwrap(); - let handle = match self.load_untyped( - relative_child_path - .to_str() - .expect("Path should be a valid string"), - ) { - Ok(handle) => handle, - Err(AssetServerError::MissingAssetHandler) => continue, - Err(err) => return Err(err), - }; - - handle_ids.push(handle); + if self.get_path_asset_loader(&child_path).is_err() { + continue; + } + let handle = + self.load_untyped(child_path.to_str().expect("Path should be a valid string")); + handles.push(handle); } } - Ok(handle_ids) + Ok(handles) } -} -#[cfg(feature = "filesystem_watcher")] -pub fn filesystem_watcher_system(asset_server: Res) { - let mut changed = HashSet::default(); + pub fn free_unused_assets(&self) { + let receiver = &self.server.asset_ref_counter.channel.receiver; + let mut ref_counts = self.server.asset_ref_counter.ref_counts.write(); + let asset_sources = self.server.asset_sources.read(); + let mut potential_frees = Vec::new(); + loop { + let ref_change = match receiver.try_recv() { + Ok(ref_change) => ref_change, + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => panic!("RefChange channel disconnected"), + }; + match ref_change { + RefChange::Increment(handle_id) => *ref_counts.entry(handle_id).or_insert(0) += 1, + RefChange::Decrement(handle_id) => { + let entry = ref_counts.entry(handle_id).or_insert(0); + *entry -= 1; + if *entry == 0 { + potential_frees.push(handle_id); + } + } + } + } + + if !potential_frees.is_empty() { + let asset_lifecycles = self.server.asset_lifecycles.read(); + for potential_free in potential_frees { + if let Some(i) = ref_counts.get(&potential_free).cloned() { + if i == 0 { + let type_uuid = match potential_free { + HandleId::Id(type_uuid, _) => Some(type_uuid), + HandleId::AssetPathId(id) => asset_sources + .get(&id.source_path_id()) + .and_then(|source_info| source_info.get_asset_type(id.label_id())), + }; + + if let Some(type_uuid) = type_uuid { + if let Some(asset_lifecycle) = asset_lifecycles.get(&type_uuid) { + asset_lifecycle.free_asset(potential_free); + } + } + } + } + } + } + } - loop { - let result = { - let rwlock_guard = asset_server.filesystem_watcher.read(); - if let Some(filesystem_watcher) = rwlock_guard.as_ref() { - filesystem_watcher.receiver.try_recv() + fn create_assets_in_load_context(&self, load_context: &mut LoadContext) { + let asset_lifecycles = self.server.asset_lifecycles.read(); + for (label, asset) in load_context.labeled_assets.iter_mut() { + let asset_value = asset + .value + .take() + .expect("Asset should exist at this point"); + if let Some(asset_lifecycle) = asset_lifecycles.get(&asset_value.type_uuid()) { + let asset_path = + AssetPath::new_ref(&load_context.path, label.as_ref().map(|l| l.as_str())); + asset_lifecycle.create_asset(asset_path.into(), asset_value, load_context.version); } else { - break; + panic!("Failed to find AssetLifecycle for label {:?}, which has an asset type {:?}. Are you sure that is a registered asset type?", label, asset_value.type_uuid()); } - }; - let event = match result { - Ok(result) => result.unwrap(), - Err(TryRecvError::Empty) => break, - Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected"), - }; - if let notify::event::Event { - kind: notify::event::EventKind::Modify(_), - paths, - .. - } = event - { - for path in paths.iter() { - if !changed.contains(path) { - let root_path = asset_server.get_root_path().unwrap(); - let relative_path = path.strip_prefix(root_path).unwrap(); - match asset_server.load_untyped(relative_path) { - Ok(_) => {} - Err(AssetServerError::AssetLoadError(error)) => panic!("{:?}", error), - Err(_) => {} + } + } + + pub(crate) fn update_asset_storage(&self, assets: &mut Assets) { + let asset_lifecycles = self.server.asset_lifecycles.read(); + let asset_lifecycle = asset_lifecycles.get(&T::TYPE_UUID).unwrap(); + let mut asset_sources = self.server.asset_sources.write(); + let channel = asset_lifecycle + .downcast_ref::>() + .unwrap(); + + loop { + match channel.receiver.try_recv() { + Ok(AssetLifecycleEvent::Create(result)) => { + // update SourceInfo if this asset was loaded from an AssetPath + if let HandleId::AssetPathId(id) = result.id { + if let Some(source_info) = asset_sources.get_mut(&id.source_path_id()) { + if source_info.version == result.version { + source_info.committed_assets.insert(id.label_id()); + if source_info.is_loaded() { + source_info.load_state = LoadState::Loaded; + } + } + } } + + assets.set(result.id, result.asset); } + Ok(AssetLifecycleEvent::Free(handle_id)) => { + if let HandleId::AssetPathId(id) = handle_id { + if let Some(source_info) = asset_sources.get_mut(&id.source_path_id()) { + source_info.committed_assets.remove(&id.label_id()); + if source_info.is_loaded() { + source_info.load_state = LoadState::Loaded; + } + } + } + assets.remove(handle_id); + } + Err(TryRecvError::Empty) => { + break; + } + Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected"), } - changed.extend(paths); } } } + +pub fn free_unused_assets_system(asset_server: Res) { + asset_server.free_unused_assets(); +} diff --git a/crates/bevy_asset/src/assets.rs b/crates/bevy_asset/src/assets.rs index 2e67972fc85c3..074608815c9b5 100644 --- a/crates/bevy_asset/src/assets.rs +++ b/crates/bevy_asset/src/assets.rs @@ -1,92 +1,133 @@ use crate::{ - update_asset_storage_system, AssetChannel, AssetLoader, AssetServer, ChannelAssetHandler, - Handle, HandleId, + update_asset_storage_system, Asset, AssetLoader, AssetServer, Handle, HandleId, RefChange, }; use bevy_app::{prelude::Events, AppBuilder}; -use bevy_ecs::{FromResources, IntoQuerySystem, ResMut, Resource}; +use bevy_ecs::{FromResources, IntoQuerySystem, ResMut}; use bevy_type_registry::RegisterType; use bevy_utils::HashMap; +use crossbeam_channel::Sender; +use std::fmt::Debug; /// Events that happen on assets of type `T` -#[derive(Debug)] -pub enum AssetEvent { +pub enum AssetEvent { Created { handle: Handle }, Modified { handle: Handle }, Removed { handle: Handle }, } +impl Debug for AssetEvent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AssetEvent::Created { handle } => f + .debug_struct(&format!( + "AssetEvent<{}>::Created", + std::any::type_name::() + )) + .field("handle", &handle.id) + .finish(), + AssetEvent::Modified { handle } => f + .debug_struct(&format!( + "AssetEvent<{}>::Modified", + std::any::type_name::() + )) + .field("handle", &handle.id) + .finish(), + AssetEvent::Removed { handle } => f + .debug_struct(&format!( + "AssetEvent<{}>::Removed", + std::any::type_name::() + )) + .field("handle", &handle.id) + .finish(), + } + } +} + /// Stores Assets of a given type and tracks changes to them. #[derive(Debug)] -pub struct Assets { - assets: HashMap, T>, +pub struct Assets { + assets: HashMap, events: Events>, + pub(crate) ref_change_sender: Sender, } -impl Default for Assets { - fn default() -> Self { +impl Assets { + pub(crate) fn new(ref_change_sender: Sender) -> Self { Assets { assets: HashMap::default(), events: Events::default(), + ref_change_sender, } } -} -impl Assets { pub fn add(&mut self, asset: T) -> Handle { - let handle = Handle::new(); - self.assets.insert(handle, asset); - self.events.send(AssetEvent::Created { handle }); - handle + let id = HandleId::random::(); + self.assets.insert(id, asset); + self.events.send(AssetEvent::Created { + handle: Handle::weak(id), + }); + self.get_handle(id) } - pub fn set(&mut self, handle: Handle, asset: T) { - let exists = self.assets.contains_key(&handle); - self.assets.insert(handle, asset); - - if exists { - self.events.send(AssetEvent::Modified { handle }); + pub fn set>(&mut self, handle: H, asset: T) -> Handle { + let id: HandleId = handle.into(); + if self.assets.insert(id, asset).is_some() { + self.events.send(AssetEvent::Modified { + handle: Handle::weak(id), + }); } else { - self.events.send(AssetEvent::Created { handle }); + self.events.send(AssetEvent::Created { + handle: Handle::weak(id), + }); } + + self.get_handle(id) } - pub fn add_default(&mut self, asset: T) -> Handle { - let handle = Handle::default(); - let exists = self.assets.contains_key(&handle); - self.assets.insert(handle, asset); - if exists { - self.events.send(AssetEvent::Modified { handle }); + pub fn set_untracked>(&mut self, handle: H, asset: T) { + let id: HandleId = handle.into(); + if self.assets.insert(id, asset).is_some() { + self.events.send(AssetEvent::Modified { + handle: Handle::weak(id), + }); } else { - self.events.send(AssetEvent::Created { handle }); + self.events.send(AssetEvent::Created { + handle: Handle::weak(id), + }); } - handle } - pub fn get_with_id(&self, id: HandleId) -> Option<&T> { - self.get(&Handle::from_id(id)) + pub fn get>(&self, handle: H) -> Option<&T> { + self.assets.get(&handle.into()) } - pub fn get_with_id_mut(&mut self, id: HandleId) -> Option<&mut T> { - self.get_mut(&Handle::from_id(id)) + pub fn contains>(&self, handle: H) -> bool { + self.assets.contains_key(&handle.into()) } - pub fn get(&self, handle: &Handle) -> Option<&T> { - self.assets.get(&handle) + pub fn get_mut>(&mut self, handle: H) -> Option<&mut T> { + let id: HandleId = handle.into(); + self.events.send(AssetEvent::Modified { + handle: Handle::weak(id), + }); + self.assets.get_mut(&id) } - pub fn get_mut(&mut self, handle: &Handle) -> Option<&mut T> { - self.events.send(AssetEvent::Modified { handle: *handle }); - self.assets.get_mut(&handle) + pub fn get_handle>(&self, handle: H) -> Handle { + Handle::strong(handle.into(), self.ref_change_sender.clone()) } - pub fn get_or_insert_with( + pub fn get_or_insert_with>( &mut self, - handle: Handle, + handle: H, insert_fn: impl FnOnce() -> T, ) -> &mut T { let mut event = None; - let borrowed = self.assets.entry(handle).or_insert_with(|| { - event = Some(AssetEvent::Created { handle }); + let id: HandleId = handle.into(); + let borrowed = self.assets.entry(id).or_insert_with(|| { + event = Some(AssetEvent::Created { + handle: Handle::weak(id), + }); insert_fn() }); @@ -96,12 +137,23 @@ impl Assets { borrowed } - pub fn iter(&self) -> impl Iterator, &T)> { + pub fn iter(&self) -> impl Iterator { self.assets.iter().map(|(k, v)| (*k, v)) } - pub fn remove(&mut self, handle: &Handle) -> Option { - self.assets.remove(&handle) + pub fn ids(&self) -> impl Iterator + '_ { + self.assets.keys().cloned() + } + + pub fn remove>(&mut self, handle: H) -> Option { + let id: HandleId = handle.into(); + let asset = self.assets.remove(&id); + if asset.is_some() { + self.events.send(AssetEvent::Removed { + handle: Handle::weak(id), + }); + } + asset } /// Clears the inner asset map, removing all key-value pairs. @@ -132,75 +184,67 @@ impl Assets { ) { events.extend(assets.events.drain()) } + + pub fn len(&self) -> usize { + self.assets.len() + } + + pub fn is_empty(&self) -> bool { + self.assets.is_empty() + } } /// [AppBuilder] extension methods for adding new asset types pub trait AddAsset { fn add_asset(&mut self) -> &mut Self where - T: Send + Sync + 'static; - fn add_asset_loader(&mut self) -> &mut Self + T: Asset; + fn init_asset_loader(&mut self) -> &mut Self where - TLoader: AssetLoader + FromResources, - TAsset: Send + Sync + 'static; - fn add_asset_loader_from_instance(&mut self, instance: TLoader) -> &mut Self + T: AssetLoader + FromResources; + fn add_asset_loader(&mut self, loader: T) -> &mut Self where - TLoader: AssetLoader + FromResources, - TAsset: Send + Sync + 'static; + T: AssetLoader; } impl AddAsset for AppBuilder { fn add_asset(&mut self) -> &mut Self where - T: Resource, + T: Asset, { - self.init_resource::>() + let assets = { + let asset_server = self.resources().get::().unwrap(); + asset_server.register_asset_type::() + }; + + self.add_resource(assets) .register_component::>() .add_system_to_stage( super::stage::ASSET_EVENTS, Assets::::asset_event_system.system(), ) + .add_system_to_stage( + crate::stage::LOAD_ASSETS, + update_asset_storage_system::.system(), + ) .add_event::>() } - fn add_asset_loader_from_instance(&mut self, instance: TLoader) -> &mut Self + fn init_asset_loader(&mut self) -> &mut Self where - TLoader: AssetLoader + FromResources, - TAsset: Send + Sync + 'static, + T: AssetLoader + FromResources, { - { - if !self.resources().contains::>() { - self.resources_mut().insert(AssetChannel::::new()); - self.add_system_to_stage( - crate::stage::LOAD_ASSETS, - update_asset_storage_system::.system(), - ); - } - let asset_channel = self - .resources() - .get::>() - .expect("AssetChannel should always exist at this point."); - let mut asset_server = self - .resources() - .get_mut::() - .expect("AssetServer does not exist. Consider adding it as a resource."); - asset_server.add_loader(instance); - let handler = ChannelAssetHandler::new( - TLoader::from_resources(self.resources()), - asset_channel.sender.clone(), - ); - asset_server.add_handler(handler); - } - self + self.add_asset_loader(T::from_resources(self.resources())) } - fn add_asset_loader(&mut self) -> &mut Self + fn add_asset_loader(&mut self, loader: T) -> &mut Self where - TLoader: AssetLoader + FromResources, - TAsset: Send + Sync + 'static, + T: AssetLoader, { - self.add_asset_loader_from_instance::(TLoader::from_resources( - self.resources(), - )) + self.resources() + .get_mut::() + .expect("AssetServer does not exist. Consider adding it as a resource.") + .add_loader(loader); + self } } diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index e3170b660a095..f028309197d37 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -2,27 +2,54 @@ use std::{ cmp::Ordering, fmt::Debug, hash::{Hash, Hasher}, + marker::PhantomData, }; use bevy_property::{Properties, Property}; +use crossbeam_channel::{Receiver, Sender}; use serde::{Deserialize, Serialize}; -use std::{any::TypeId, marker::PhantomData}; use uuid::Uuid; -/// The ID of the "default" asset -pub(crate) const DEFAULT_HANDLE_ID: HandleId = - HandleId(Uuid::from_u128(240940089166493627844978703213080810552)); +use crate::{ + path::{AssetPath, AssetPathId}, + Asset, Assets, +}; -/// A unique id that corresponds to a specific asset in the [Assets](crate::Assets) collection. +/// A unique, stable asset id #[derive( - Debug, Clone, Copy, Eq, PartialOrd, Ord, PartialEq, Hash, Serialize, Deserialize, Property, + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, )] -pub struct HandleId(pub Uuid); +pub enum HandleId { + Id(Uuid, u64), + AssetPathId(AssetPathId), +} + +impl From for HandleId { + fn from(value: AssetPathId) -> Self { + HandleId::AssetPathId(value) + } +} + +impl<'a> From> for HandleId { + fn from(value: AssetPath<'a>) -> Self { + HandleId::AssetPathId(AssetPathId::from(value)) + } +} impl HandleId { - #[allow(clippy::new_without_default)] - pub fn new() -> HandleId { - HandleId(Uuid::new_v4()) + #[inline] + pub fn random() -> Self { + HandleId::Id(T::TYPE_UUID, rand::random()) + } + + #[inline] + pub fn default() -> Self { + HandleId::Id(T::TYPE_UUID, 0) + } + + #[inline] + pub const fn new(type_uuid: Uuid, id: u64) -> Self { + HandleId::Id(type_uuid, id) } } @@ -36,89 +63,122 @@ where { pub id: HandleId, #[property(ignore)] + handle_type: HandleType, + #[property(ignore)] marker: PhantomData, } +enum HandleType { + Weak, + Strong(Sender), +} + +impl Debug for HandleType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + HandleType::Weak => f.write_str("Weak"), + HandleType::Strong(_) => f.write_str("Strong"), + } + } +} + impl Handle { - pub fn new() -> Self { - Handle { - id: HandleId::new(), + // TODO: remove "uuid" parameter whenever rust support type constraints in const fns + pub const fn weak_from_u64(uuid: Uuid, id: u64) -> Self { + Self { + id: HandleId::new(uuid, id), + handle_type: HandleType::Weak, marker: PhantomData, } } +} - /// Gets a handle for the given type that has this handle's id. This is useful when an - /// asset is derived from another asset. In this case, a common handle can be used to - /// correlate them. - /// NOTE: This pattern might eventually be replaced by a more formal asset dependency system. - pub fn as_handle(&self) -> Handle { - Handle::from_id(self.id) +impl Handle { + pub(crate) fn strong(id: HandleId, ref_change_sender: Sender) -> Self { + ref_change_sender.send(RefChange::Increment(id)).unwrap(); + Self { + id, + handle_type: HandleType::Strong(ref_change_sender), + marker: PhantomData, + } } - pub const fn from_id(id: HandleId) -> Self { - Handle { + pub fn weak(id: HandleId) -> Self { + Self { id, + handle_type: HandleType::Weak, marker: PhantomData, } } - pub const fn from_u128(value: u128) -> Self { + pub fn as_weak(&self) -> Handle { Handle { - id: HandleId(Uuid::from_u128(value)), + id: self.id, + handle_type: HandleType::Weak, marker: PhantomData, } } - pub const fn from_bytes(bytes: [u8; 16]) -> Self { - Handle { - id: HandleId(Uuid::from_bytes(bytes)), - marker: PhantomData, + pub fn is_weak(&self) -> bool { + matches!(self.handle_type, HandleType::Weak) + } + + pub fn is_strong(&self) -> bool { + matches!(self.handle_type, HandleType::Strong(_)) + } + + pub fn make_strong(&mut self, assets: &mut Assets) { + if self.is_strong() { + return; } + let sender = assets.ref_change_sender.clone(); + sender.send(RefChange::Increment(self.id)).unwrap(); + self.handle_type = HandleType::Strong(sender); + } + + pub fn clone_weak(&self) -> Self { + Handle::weak(self.id) } - pub fn from_untyped(untyped_handle: HandleUntyped) -> Option> - where - T: 'static, - { - if TypeId::of::() == untyped_handle.type_id { - Some(Handle::from_id(untyped_handle.id)) - } else { - None + pub fn clone_untyped(&self) -> HandleUntyped { + match &self.handle_type { + HandleType::Strong(sender) => HandleUntyped::strong(self.id, sender.clone()), + HandleType::Weak => HandleUntyped::weak(self.id), } } + + pub fn clone_weak_untyped(&self) -> HandleUntyped { + HandleUntyped::weak(self.id) + } } -impl From for Handle { - fn from(value: HandleId) -> Self { - Handle::from_id(value) +impl Drop for Handle { + fn drop(&mut self) { + match self.handle_type { + HandleType::Strong(ref sender) => { + // ignore send errors because this means the channel is shut down / the game has stopped + let _ = sender.send(RefChange::Decrement(self.id)); + } + HandleType::Weak => {} + } } } -impl From for Handle { - fn from(value: u128) -> Self { - Handle::from_u128(value) +impl From> for HandleId { + fn from(value: Handle) -> Self { + value.id } } -impl From<[u8; 16]> for Handle { - fn from(value: [u8; 16]) -> Self { - Handle::from_bytes(value) +impl From<&str> for HandleId { + fn from(value: &str) -> Self { + AssetPathId::from(value).into() } } -impl From for Handle -where - T: 'static, -{ - fn from(handle: HandleUntyped) -> Self { - if TypeId::of::() == handle.type_id { - Handle { - id: handle.id, - marker: PhantomData::default(), - } - } else { - panic!("attempted to convert untyped handle to incorrect typed handle") - } +impl From<&Handle> for HandleId { + fn from(value: &Handle) -> Self { + value.id } } @@ -148,31 +208,27 @@ impl Ord for Handle { } } -impl Debug for Handle { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - let name = std::any::type_name::().split("::").last().unwrap(); - write!(f, "Handle<{}>({:?})", name, self.id.0) +impl Default for Handle { + fn default() -> Self { + Handle::weak(HandleId::default::()) } } -impl Default for Handle { - fn default() -> Self { - Handle { - id: DEFAULT_HANDLE_ID, - marker: PhantomData, - } +impl Debug for Handle { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { + let name = std::any::type_name::().split("::").last().unwrap(); + write!(f, "{:?}Handle<{}>({:?})", self.handle_type, name, self.id) } } -impl Clone for Handle { +impl Clone for Handle { fn clone(&self) -> Self { - Handle { - id: self.id, - marker: PhantomData, + match self.handle_type { + HandleType::Strong(ref sender) => Handle::strong(self.id, sender.clone()), + HandleType::Weak => Handle::weak(self.id), } } } -impl Copy for Handle {} // SAFE: T is phantom data and Handle::id is an integer unsafe impl Send for Handle {} @@ -181,26 +237,115 @@ unsafe impl Sync for Handle {} /// A non-generic version of [Handle] /// /// This allows handles to be mingled in a cross asset context. For example, storing `Handle` and `Handle` in the same `HashSet`. -#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Debug)] pub struct HandleUntyped { pub id: HandleId, - pub type_id: TypeId, + handle_type: HandleType, } impl HandleUntyped { - pub fn is_handle(untyped: &HandleUntyped) -> bool { - TypeId::of::() == untyped.type_id + pub(crate) fn strong(id: HandleId, ref_change_sender: Sender) -> Self { + ref_change_sender.send(RefChange::Increment(id)).unwrap(); + Self { + id, + handle_type: HandleType::Strong(ref_change_sender), + } + } + + pub fn weak(id: HandleId) -> Self { + Self { + id, + handle_type: HandleType::Weak, + } + } + + pub fn clone_weak(&self) -> HandleUntyped { + HandleUntyped::weak(self.id) + } + + pub fn is_weak(&self) -> bool { + matches!(self.handle_type, HandleType::Weak) + } + + pub fn is_strong(&self) -> bool { + matches!(self.handle_type, HandleType::Strong(_)) + } + + pub fn typed(mut self) -> Handle { + if let HandleId::Id(type_uuid, _) = self.id { + if T::TYPE_UUID != type_uuid { + panic!("attempted to convert handle to invalid type"); + } + } + let handle_type = match &self.handle_type { + HandleType::Strong(sender) => HandleType::Strong(sender.clone()), + HandleType::Weak => HandleType::Weak, + }; + // ensure we don't send the RefChange event when "self" is dropped + self.handle_type = HandleType::Weak; + Handle { + handle_type, + id: self.id, + marker: PhantomData::default(), + } } } -impl From> for HandleUntyped -where - T: 'static, -{ - fn from(handle: Handle) -> Self { - HandleUntyped { - id: handle.id, - type_id: TypeId::of::(), +impl Drop for HandleUntyped { + fn drop(&mut self) { + match self.handle_type { + HandleType::Strong(ref sender) => { + // ignore send errors because this means the channel is shut down / the game has stopped + let _ = sender.send(RefChange::Decrement(self.id)); + } + HandleType::Weak => {} } } } + +impl From<&HandleUntyped> for HandleId { + fn from(value: &HandleUntyped) -> Self { + value.id + } +} + +impl Hash for HandleUntyped { + fn hash(&self, state: &mut H) { + self.id.hash(state); + } +} + +impl PartialEq for HandleUntyped { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +impl Eq for HandleUntyped {} + +impl Clone for HandleUntyped { + fn clone(&self) -> Self { + match self.handle_type { + HandleType::Strong(ref sender) => HandleUntyped::strong(self.id, sender.clone()), + HandleType::Weak => HandleUntyped::weak(self.id), + } + } +} + +pub(crate) enum RefChange { + Increment(HandleId), + Decrement(HandleId), +} + +#[derive(Clone)] +pub(crate) struct RefChangeChannel { + pub sender: Sender, + pub receiver: Receiver, +} + +impl Default for RefChangeChannel { + fn default() -> Self { + let (sender, receiver) = crossbeam_channel::unbounded(); + RefChangeChannel { sender, receiver } + } +} diff --git a/crates/bevy_asset/src/info.rs b/crates/bevy_asset/src/info.rs new file mode 100644 index 0000000000000..506f09dbfe4f1 --- /dev/null +++ b/crates/bevy_asset/src/info.rs @@ -0,0 +1,49 @@ +use crate::{path::AssetPath, LabelId}; +use bevy_utils::{HashMap, HashSet}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; +use uuid::Uuid; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SourceMeta { + pub assets: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AssetMeta { + pub label: Option, + pub dependencies: Vec>, + pub type_uuid: Uuid, +} + +/// Info about a specific asset, such as its path and its current load state +#[derive(Clone, Debug)] +pub struct SourceInfo { + pub meta: Option, + pub path: PathBuf, + pub asset_types: HashMap, + pub load_state: LoadState, + pub committed_assets: HashSet, + pub version: usize, +} + +impl SourceInfo { + pub fn is_loaded(&self) -> bool { + self.meta.as_ref().map_or(false, |meta| { + self.committed_assets.len() == meta.assets.len() + }) + } + + pub fn get_asset_type(&self, label_id: LabelId) -> Option { + self.asset_types.get(&label_id).cloned() + } +} + +/// The load state of an asset +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] +pub enum LoadState { + NotLoaded, + Loading, + Loaded, + Failed, +} diff --git a/crates/bevy_asset/src/io.rs b/crates/bevy_asset/src/io.rs new file mode 100644 index 0000000000000..aa44d35dd16f7 --- /dev/null +++ b/crates/bevy_asset/src/io.rs @@ -0,0 +1,168 @@ +use anyhow::Result; +use bevy_ecs::Res; +use bevy_utils::HashSet; +use crossbeam_channel::TryRecvError; +use fs::File; +use io::Read; +use parking_lot::RwLock; +use std::{ + env, fs, io, + path::{Path, PathBuf}, + sync::Arc, +}; +use thiserror::Error; + +use crate::{filesystem_watcher::FilesystemWatcher, AssetServer}; + +/// Errors that occur while loading assets +#[derive(Error, Debug)] +pub enum AssetIoError { + #[error("Path not found")] + NotFound(PathBuf), + #[error("Encountered an io error while loading asset.")] + Io(#[from] io::Error), + #[error("Failed to watch path")] + PathWatchError(PathBuf), +} + +/// Handles load requests from an AssetServer +pub trait AssetIo: Send + Sync + 'static { + fn load_path(&self, path: &Path) -> Result, AssetIoError>; + fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>; + fn read_directory( + &self, + path: &Path, + ) -> Result>, AssetIoError>; + fn is_directory(&self, path: &Path) -> bool; + fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError>; + fn watch_for_changes(&self) -> Result<(), AssetIoError>; +} + +pub struct FileAssetIo { + root_path: PathBuf, + #[cfg(feature = "filesystem_watcher")] + filesystem_watcher: Arc>>, +} + +impl FileAssetIo { + pub fn new>(path: P) -> Self { + FileAssetIo { + filesystem_watcher: Default::default(), + root_path: Self::get_root_path().join(path.as_ref()), + } + } + + pub fn get_root_path() -> PathBuf { + if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") { + PathBuf::from(manifest_dir) + } else { + env::current_exe() + .map(|path| { + path.parent() + .map(|exe_parent_path| exe_parent_path.to_owned()) + .unwrap() + }) + .unwrap() + } + } +} + +impl AssetIo for FileAssetIo { + fn load_path(&self, path: &Path) -> Result, AssetIoError> { + let mut bytes = Vec::new(); + match File::open(self.root_path.join(path)) { + Ok(mut file) => { + file.read_to_end(&mut bytes)?; + } + Err(e) => { + if e.kind() == std::io::ErrorKind::NotFound { + return Err(AssetIoError::NotFound(path.to_owned())); + } else { + return Err(e.into()); + } + } + } + Ok(bytes) + } + + fn read_directory( + &self, + path: &Path, + ) -> Result>, AssetIoError> { + let root_path = self.root_path.to_owned(); + Ok(Box::new(fs::read_dir(root_path.join(path))?.map( + move |entry| { + let path = entry.unwrap().path(); + path.strip_prefix(&root_path).unwrap().to_owned() + }, + ))) + } + + fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError> { + let path = self.root_path.join(path); + if let Some(parent_path) = path.parent() { + fs::create_dir_all(parent_path)?; + } + + Ok(fs::write(self.root_path.join(path), bytes)?) + } + + fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> { + #[cfg(feature = "filesystem_watcher")] + { + let path = self.root_path.join(path); + let mut watcher = self.filesystem_watcher.write(); + if let Some(ref mut watcher) = *watcher { + watcher + .watch(&path) + .map_err(|_error| AssetIoError::PathWatchError(path))?; + } + } + + Ok(()) + } + + fn watch_for_changes(&self) -> Result<(), AssetIoError> { + #[cfg(feature = "filesystem_watcher")] + { + *self.filesystem_watcher.write() = Some(FilesystemWatcher::default()); + } + + Ok(()) + } + + fn is_directory(&self, path: &Path) -> bool { + self.root_path.join(path).is_dir() + } +} + +#[cfg(feature = "filesystem_watcher")] +pub fn filesystem_watcher_system(asset_server: Res) { + let mut changed = HashSet::default(); + let watcher = asset_server.server.asset_io.filesystem_watcher.read(); + if let Some(ref watcher) = *watcher { + loop { + let event = match watcher.receiver.try_recv() { + Ok(result) => result.unwrap(), + Err(TryRecvError::Empty) => break, + Err(TryRecvError::Disconnected) => panic!("FilesystemWatcher disconnected"), + }; + if let notify::event::Event { + kind: notify::event::EventKind::Modify(_), + paths, + .. + } = event + { + for path in paths.iter() { + if !changed.contains(path) { + let relative_path = path + .strip_prefix(&asset_server.server.asset_io.root_path) + .unwrap(); + let _ = asset_server.load_untracked(relative_path, true); + } + } + changed.extend(paths); + } + } + } +} diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index 01186472482f2..322e651517cc7 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -3,15 +3,19 @@ mod assets; #[cfg(feature = "filesystem_watcher")] mod filesystem_watcher; mod handle; -mod load_request; +mod info; +mod io; mod loader; +mod path; pub use asset_server::*; pub use assets::*; use bevy_tasks::IoTaskPool; pub use handle::*; -pub use load_request::*; +pub use info::*; +pub use io::*; pub use loader::*; +pub use path::*; /// The names of asset stages in an App Schedule pub mod stage { @@ -20,7 +24,7 @@ pub mod stage { } pub mod prelude { - pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle}; + pub use crate::{AddAsset, AssetEvent, AssetServer, Assets, Handle, HandleUntyped}; } use bevy_app::{prelude::Plugin, AppBuilder}; @@ -32,6 +36,18 @@ use bevy_type_registry::RegisterType; #[derive(Default)] pub struct AssetPlugin; +pub struct AssetServerSettings { + asset_folder: String, +} + +impl Default for AssetServerSettings { + fn default() -> Self { + Self { + asset_folder: "assets".to_string(), + } + } +} + impl Plugin for AssetPlugin { fn build(&self, app: &mut AppBuilder) { let task_pool = app @@ -40,15 +56,25 @@ impl Plugin for AssetPlugin { .expect("IoTaskPool resource not found") .0 .clone(); + + let asset_server = { + let settings = app + .resources_mut() + .get_or_insert_with(AssetServerSettings::default); + let source = FileAssetIo::new(&settings.asset_folder); + AssetServer::new(source, task_pool) + }; + app.add_stage_before(bevy_app::stage::PRE_UPDATE, stage::LOAD_ASSETS) .add_stage_after(bevy_app::stage::POST_UPDATE, stage::ASSET_EVENTS) - .add_resource(AssetServer::new(task_pool)) - .register_property::(); + .add_resource(asset_server) + .register_property::() + .add_system_to_stage( + bevy_app::stage::PRE_UPDATE, + asset_server::free_unused_assets_system.system(), + ); #[cfg(feature = "filesystem_watcher")] - app.add_system_to_stage( - stage::LOAD_ASSETS, - asset_server::filesystem_watcher_system.system(), - ); + app.add_system_to_stage(stage::LOAD_ASSETS, io::filesystem_watcher_system.system()); } } diff --git a/crates/bevy_asset/src/load_request/mod.rs b/crates/bevy_asset/src/load_request/mod.rs deleted file mode 100644 index 55a64c3825355..0000000000000 --- a/crates/bevy_asset/src/load_request/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::{AssetLoader, AssetResult, AssetVersion, HandleId}; -use crossbeam_channel::Sender; -use std::path::PathBuf; - -#[cfg(not(target_arch = "wasm32"))] -#[path = "platform_default.rs"] -mod platform_specific; - -#[cfg(target_arch = "wasm32")] -#[path = "platform_wasm.rs"] -mod platform_specific; - -pub use platform_specific::*; - -/// A request from an [AssetServer](crate::AssetServer) to load an asset. -#[derive(Debug)] -pub struct LoadRequest { - pub path: PathBuf, - pub handle_id: HandleId, - pub handler_index: usize, - pub version: AssetVersion, -} - -pub(crate) struct ChannelAssetHandler -where - TLoader: AssetLoader, - TAsset: 'static, -{ - sender: Sender>, - loader: TLoader, -} diff --git a/crates/bevy_asset/src/load_request/platform_default.rs b/crates/bevy_asset/src/load_request/platform_default.rs deleted file mode 100644 index 4aa84a0faf388..0000000000000 --- a/crates/bevy_asset/src/load_request/platform_default.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::{ChannelAssetHandler, LoadRequest}; -use crate::{AssetLoadError, AssetLoader, AssetResult, Handle}; -use anyhow::Result; -use async_trait::async_trait; -use crossbeam_channel::Sender; -use std::{fs::File, io::Read}; - -/// Handles load requests from an AssetServer - -#[async_trait] -pub trait AssetLoadRequestHandler: Send + Sync + 'static { - async fn handle_request(&self, load_request: &LoadRequest); - fn extensions(&self) -> &[&str]; -} - -impl ChannelAssetHandler -where - TLoader: AssetLoader, -{ - pub fn new(loader: TLoader, sender: Sender>) -> Self { - ChannelAssetHandler { sender, loader } - } - - fn load_asset(&self, load_request: &LoadRequest) -> Result { - match File::open(&load_request.path) { - Ok(mut file) => { - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - let asset = self.loader.from_bytes(&load_request.path, bytes)?; - Ok(asset) - } - Err(e) => Err(AssetLoadError::Io(std::io::Error::new( - e.kind(), - format!("{}", load_request.path.display()), - ))), - } - } -} - -#[async_trait] -impl AssetLoadRequestHandler for ChannelAssetHandler -where - TLoader: AssetLoader + 'static, - TAsset: Send + 'static, -{ - async fn handle_request(&self, load_request: &LoadRequest) { - let result = self.load_asset(load_request); - let asset_result = AssetResult { - handle: Handle::from(load_request.handle_id), - result, - path: load_request.path.clone(), - version: load_request.version, - }; - self.sender - .send(asset_result) - .expect("loaded asset should have been sent"); - } - - fn extensions(&self) -> &[&str] { - self.loader.extensions() - } -} diff --git a/crates/bevy_asset/src/load_request/platform_wasm.rs b/crates/bevy_asset/src/load_request/platform_wasm.rs deleted file mode 100644 index 4a240bf3ea696..0000000000000 --- a/crates/bevy_asset/src/load_request/platform_wasm.rs +++ /dev/null @@ -1,62 +0,0 @@ -use super::{ChannelAssetHandler, LoadRequest}; -use crate::{AssetLoadError, AssetLoader, AssetResult, Handle}; -use anyhow::Result; -use async_trait::async_trait; -use crossbeam_channel::Sender; - -use js_sys::Uint8Array; -use wasm_bindgen::JsCast; -use wasm_bindgen_futures::JsFuture; -use web_sys::Response; - -#[async_trait(?Send)] -pub trait AssetLoadRequestHandler: Send + Sync + 'static { - async fn handle_request(&self, load_request: &LoadRequest); - fn extensions(&self) -> &[&str]; -} - -impl ChannelAssetHandler -where - TLoader: AssetLoader, -{ - pub fn new(loader: TLoader, sender: Sender>) -> Self { - ChannelAssetHandler { sender, loader } - } - - async fn load_asset(&self, load_request: &LoadRequest) -> Result { - // TODO - get rid of some unwraps below (do some retrying maybe?) - let window = web_sys::window().unwrap(); - let resp_value = JsFuture::from(window.fetch_with_str(load_request.path.to_str().unwrap())) - .await - .unwrap(); - let resp: Response = resp_value.dyn_into().unwrap(); - let data = JsFuture::from(resp.array_buffer().unwrap()).await.unwrap(); - let bytes = Uint8Array::new(&data).to_vec(); - let asset = self.loader.from_bytes(&load_request.path, bytes).unwrap(); - Ok(asset) - } -} - -#[async_trait(?Send)] -impl AssetLoadRequestHandler for ChannelAssetHandler -where - TLoader: AssetLoader + 'static, - TAsset: Send + 'static, -{ - async fn handle_request(&self, load_request: &LoadRequest) { - let asset = self.load_asset(load_request).await; - let asset_result = AssetResult { - handle: Handle::from(load_request.handle_id), - result: asset, - path: load_request.path.clone(), - version: load_request.version, - }; - self.sender - .send(asset_result) - .expect("loaded asset should have been sent"); - } - - fn extensions(&self) -> &[&str] { - self.loader.extensions() - } -} diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 945b5a5ba937f..5d92acc0416b7 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -1,85 +1,173 @@ -use crate::{AssetServer, AssetVersion, Assets, Handle, LoadState}; +use crate::{ + path::AssetPath, AssetIo, AssetIoError, AssetMeta, AssetServer, Assets, Handle, HandleId, + RefChangeChannel, +}; use anyhow::Result; use bevy_ecs::{Res, ResMut, Resource}; -use crossbeam_channel::{Receiver, Sender, TryRecvError}; -use fs::File; -use io::Read; -use std::{ - fs, io, - path::{Path, PathBuf}, -}; -use thiserror::Error; - -/// Errors that occur while loading assets -#[derive(Error, Debug)] -pub enum AssetLoadError { - #[error("Encountered an io error while loading asset.")] - Io(#[from] io::Error), - #[error("This asset's loader encountered an error while loading.")] - LoaderError(#[from] anyhow::Error), -} +use bevy_type_registry::{TypeUuid, TypeUuidDynamic}; +use bevy_utils::HashMap; +use crossbeam_channel::{Receiver, Sender}; +use downcast_rs::{impl_downcast, Downcast}; +use std::path::Path; -/// A loader for a given asset of type `T` -pub trait AssetLoader: Send + Sync + 'static { - fn from_bytes(&self, asset_path: &Path, bytes: Vec) -> Result; +/// A loader for an asset source +pub trait AssetLoader: Send + Sync + 'static { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error>; fn extensions(&self) -> &[&str]; - fn load_from_file(&self, asset_path: &Path) -> Result { - let mut file = File::open(asset_path)?; - let mut bytes = Vec::new(); - file.read_to_end(&mut bytes)?; - let asset = self.from_bytes(asset_path, bytes)?; - Ok(asset) +} + +pub trait Asset: TypeUuid + AssetDynamic {} + +pub trait AssetDynamic: Downcast + TypeUuidDynamic + Send + Sync + 'static {} +impl_downcast!(AssetDynamic); + +impl Asset for T where T: TypeUuid + AssetDynamic + TypeUuidDynamic {} + +impl AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {} + +pub struct LoadedAsset { + pub(crate) value: Option>, + pub(crate) dependencies: Vec>, +} + +impl LoadedAsset { + pub fn new(value: T) -> Self { + Self { + value: Some(Box::new(value)), + dependencies: Vec::new(), + } + } + + pub fn with_dependency(mut self, asset_path: AssetPath) -> Self { + self.dependencies.push(asset_path.to_owned()); + self + } + + pub fn with_dependencies(mut self, asset_paths: Vec>) -> Self { + self.dependencies.extend(asset_paths); + self + } +} + +pub struct LoadContext<'a> { + pub(crate) ref_change_channel: &'a RefChangeChannel, + pub(crate) asset_io: &'a dyn AssetIo, + pub(crate) labeled_assets: HashMap, LoadedAsset>, + pub(crate) path: &'a Path, + pub(crate) version: usize, +} + +impl<'a> LoadContext<'a> { + pub(crate) fn new( + path: &'a Path, + ref_change_channel: &'a RefChangeChannel, + asset_io: &'a dyn AssetIo, + version: usize, + ) -> Self { + Self { + ref_change_channel, + asset_io, + labeled_assets: Default::default(), + version, + path, + } + } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn has_labeled_asset(&self, label: &str) -> bool { + self.labeled_assets.contains_key(&Some(label.to_string())) + } + + pub fn set_default_asset(&mut self, asset: LoadedAsset) { + self.labeled_assets.insert(None, asset); + } + + pub fn set_labeled_asset(&mut self, label: &str, asset: LoadedAsset) { + assert!(!label.is_empty()); + self.labeled_assets.insert(Some(label.to_string()), asset); + } + + pub fn get_handle, T: Asset>(&self, id: I) -> Handle { + Handle::strong(id.into(), self.ref_change_channel.sender.clone()) + } + + pub fn read_asset_bytes>(&self, path: P) -> Result, AssetIoError> { + self.asset_io.load_path(path.as_ref()) + } + + pub fn get_asset_metas(&self) -> Vec { + let mut asset_metas = Vec::new(); + for (label, asset) in self.labeled_assets.iter() { + asset_metas.push(AssetMeta { + dependencies: asset.dependencies.clone(), + label: label.clone(), + type_uuid: asset.value.as_ref().unwrap().type_uuid(), + }); + } + asset_metas } } /// The result of loading an asset of type `T` #[derive(Debug)] -pub struct AssetResult { - pub result: Result, - pub handle: Handle, - pub path: PathBuf, - pub version: AssetVersion, +pub struct AssetResult { + pub asset: T, + pub id: HandleId, + pub version: usize, } /// A channel to send and receive [AssetResult]s #[derive(Debug)] -pub struct AssetChannel { - pub sender: Sender>, - pub receiver: Receiver>, +pub struct AssetLifecycleChannel { + pub sender: Sender>, + pub receiver: Receiver>, } -impl AssetChannel { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { +pub enum AssetLifecycleEvent { + Create(AssetResult), + Free(HandleId), +} + +pub trait AssetLifecycle: Downcast + Send + Sync + 'static { + fn create_asset(&self, id: HandleId, asset: Box, version: usize); + fn free_asset(&self, id: HandleId); +} +impl_downcast!(AssetLifecycle); + +impl AssetLifecycle for AssetLifecycleChannel { + fn create_asset(&self, id: HandleId, asset: Box, version: usize) { + if let Ok(asset) = asset.downcast::() { + self.sender + .send(AssetLifecycleEvent::Create(AssetResult { + id, + asset: *asset, + version, + })) + .unwrap() + } else { + panic!("failed to downcast asset to {}", std::any::type_name::()); + } + } + + fn free_asset(&self, id: HandleId) { + self.sender.send(AssetLifecycleEvent::Free(id)).unwrap(); + } +} + +impl Default for AssetLifecycleChannel { + fn default() -> Self { let (sender, receiver) = crossbeam_channel::unbounded(); - AssetChannel { sender, receiver } + AssetLifecycleChannel { sender, receiver } } } /// Reads [AssetResult]s from an [AssetChannel] and updates the [Assets] collection and [LoadState] accordingly -pub fn update_asset_storage_system( - asset_channel: Res>, +pub fn update_asset_storage_system( asset_server: Res, mut assets: ResMut>, ) { - loop { - match asset_channel.receiver.try_recv() { - Ok(result) => match result.result { - Ok(asset) => { - assets.set(result.handle, asset); - asset_server - .set_load_state(result.handle.id, LoadState::Loaded(result.version)); - } - Err(err) => { - asset_server - .set_load_state(result.handle.id, LoadState::Failed(result.version)); - log::error!("Failed to load asset: {:?}", err); - } - }, - Err(TryRecvError::Empty) => { - break; - } - Err(TryRecvError::Disconnected) => panic!("AssetChannel disconnected"), - } - } + asset_server.update_asset_storage(&mut assets); } diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs new file mode 100644 index 0000000000000..89abed1547a6d --- /dev/null +++ b/crates/bevy_asset/src/path.rs @@ -0,0 +1,168 @@ +use bevy_property::Property; +use bevy_utils::AHasher; +use serde::{Deserialize, Serialize}; +use std::{ + borrow::Cow, + hash::{Hash, Hasher}, + path::{Path, PathBuf}, +}; + +#[derive(Debug, Hash, Clone, Serialize, Deserialize)] +pub struct AssetPath<'a> { + path: Cow<'a, Path>, + label: Option>, +} + +impl<'a> AssetPath<'a> { + #[inline] + pub fn new_ref(path: &'a Path, label: Option<&'a str>) -> AssetPath<'a> { + AssetPath { + path: Cow::Borrowed(path), + label: label.map(|val| Cow::Borrowed(val)), + } + } + + #[inline] + pub fn new(path: PathBuf, label: Option) -> AssetPath<'a> { + AssetPath { + path: Cow::Owned(path), + label: label.map(Cow::Owned), + } + } + + #[inline] + pub fn get_id(&self) -> AssetPathId { + AssetPathId::from(self) + } + + #[inline] + pub fn label(&self) -> Option<&str> { + self.label.as_ref().map(|label| label.as_ref()) + } + + #[inline] + pub fn path(&self) -> &Path { + &self.path + } + + #[inline] + pub fn to_owned(&self) -> AssetPath<'static> { + AssetPath { + path: Cow::Owned(self.path.to_path_buf()), + label: self + .label + .as_ref() + .map(|value| Cow::Owned(value.to_string())), + } + } +} + +#[derive( + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, +)] +pub struct AssetPathId(SourcePathId, LabelId); + +#[derive( + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, +)] +pub struct SourcePathId(u64); + +#[derive( + Debug, Clone, Copy, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, Property, +)] +pub struct LabelId(u64); + +impl<'a> From<&'a Path> for SourcePathId { + fn from(value: &'a Path) -> Self { + let mut hasher = get_hasher(); + value.hash(&mut hasher); + SourcePathId(hasher.finish()) + } +} + +impl From for SourcePathId { + fn from(id: AssetPathId) -> Self { + id.source_path_id() + } +} + +impl<'a> From> for SourcePathId { + fn from(path: AssetPath) -> Self { + AssetPathId::from(path).source_path_id() + } +} + +impl<'a> From> for LabelId { + fn from(value: Option<&'a str>) -> Self { + let mut hasher = get_hasher(); + value.hash(&mut hasher); + LabelId(hasher.finish()) + } +} + +impl AssetPathId { + pub fn source_path_id(&self) -> SourcePathId { + self.0 + } + + pub fn label_id(&self) -> LabelId { + self.1 + } +} + +/// this hasher provides consistent results across runs +pub(crate) fn get_hasher() -> AHasher { + AHasher::new_with_keys(42, 23) +} + +impl<'a, T> From for AssetPathId +where + T: Into>, +{ + fn from(value: T) -> Self { + let asset_path: AssetPath = value.into(); + AssetPathId( + SourcePathId::from(asset_path.path()), + LabelId::from(asset_path.label()), + ) + } +} + +impl<'a, 'b> From<&'a AssetPath<'b>> for AssetPathId { + fn from(asset_path: &'a AssetPath<'b>) -> Self { + AssetPathId( + SourcePathId::from(asset_path.path()), + LabelId::from(asset_path.label()), + ) + } +} + +impl<'a> From<&'a str> for AssetPath<'a> { + fn from(asset_path: &'a str) -> Self { + let mut parts = asset_path.split('#'); + let path = Path::new(parts.next().expect("path must be set")); + let label = parts.next(); + AssetPath { + path: Cow::Borrowed(path), + label: label.map(|label| Cow::Borrowed(label)), + } + } +} + +impl<'a> From<&'a Path> for AssetPath<'a> { + fn from(path: &'a Path) -> Self { + AssetPath { + path: Cow::Borrowed(path), + label: None, + } + } +} + +impl<'a> From for AssetPath<'a> { + fn from(path: PathBuf) -> Self { + AssetPath { + path: Cow::Owned(path), + label: None, + } + } +} diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 99c91ada54483..7ab6292c77d86 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -17,6 +17,7 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.2.1" } bevy_asset = { path = "../bevy_asset", version = "0.2.1" } bevy_ecs = { path = "../bevy_ecs", version = "0.2.1" } +bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" } # other anyhow = "1.0" diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index a3a5f91bd1b86..50c29a9670b6a 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -1,5 +1,5 @@ use crate::{AudioSource, Decodable}; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Asset, Assets, Handle}; use bevy_ecs::Res; use parking_lot::RwLock; use rodio::{Device, Sink}; @@ -39,7 +39,7 @@ where impl

AudioOutput

where - P: Decodable, + P: Asset + Decodable,

::Decoder: rodio::Source + Send + Sync, <

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, { @@ -71,8 +71,10 @@ where } /// Plays audio currently queued in the [AudioOutput] resource -pub fn play_queued_audio_system

(audio_sources: Res>, audio_output: Res>) -where +pub fn play_queued_audio_system( + audio_sources: Res>, + audio_output: Res>, +) where P: Decodable,

::Decoder: rodio::Source + Send + Sync, <

::Decoder as Iterator>::Item: rodio::Sample + Send + Sync, diff --git a/crates/bevy_audio/src/audio_source.rs b/crates/bevy_audio/src/audio_source.rs index 335c08003218b..2dd852d480546 100644 --- a/crates/bevy_audio/src/audio_source.rs +++ b/crates/bevy_audio/src/audio_source.rs @@ -1,9 +1,11 @@ use anyhow::Result; -use bevy_asset::AssetLoader; -use std::{io::Cursor, path::Path, sync::Arc}; +use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; +use bevy_type_registry::TypeUuid; +use std::{io::Cursor, sync::Arc}; /// A source of audio data -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "7a14806a-672b-443b-8d16-4f18afefa463"] pub struct AudioSource { pub bytes: Arc<[u8]>, } @@ -18,11 +20,12 @@ impl AsRef<[u8]> for AudioSource { #[derive(Default)] pub struct Mp3Loader; -impl AssetLoader for Mp3Loader { - fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { - Ok(AudioSource { +impl AssetLoader for Mp3Loader { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { + load_context.set_default_asset(LoadedAsset::new(AudioSource { bytes: bytes.into(), - }) + })); + Ok(()) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_audio/src/lib.rs b/crates/bevy_audio/src/lib.rs index 945ba853f6000..7c1d422a6c566 100644 --- a/crates/bevy_audio/src/lib.rs +++ b/crates/bevy_audio/src/lib.rs @@ -20,7 +20,7 @@ impl Plugin for AudioPlugin { fn build(&self, app: &mut AppBuilder) { app.init_resource::>() .add_asset::() - .add_asset_loader::() + .init_asset_loader::() .add_system_to_stage( stage::POST_UPDATE, play_queued_audio_system::.system(), diff --git a/crates/bevy_derive/Cargo.toml b/crates/bevy_derive/Cargo.toml index 8c01427b0bd7c..5158bc1087f95 100644 --- a/crates/bevy_derive/Cargo.toml +++ b/crates/bevy_derive/Cargo.toml @@ -21,3 +21,4 @@ proc-macro-crate = "0.1.5" proc-macro2 = "1.0" quote = "1.0" syn = "1.0" +uuid = { version = "0.8", features = ["v4", "serde"] } diff --git a/crates/bevy_derive/src/lib.rs b/crates/bevy_derive/src/lib.rs index 382f4265a13c7..fe3a6a96db77c 100644 --- a/crates/bevy_derive/src/lib.rs +++ b/crates/bevy_derive/src/lib.rs @@ -8,6 +8,7 @@ mod render_resource; mod render_resources; mod resource; mod shader_defs; +mod type_uuid; use proc_macro::TokenStream; @@ -55,3 +56,14 @@ pub fn derive_as_vertex_buffer_descriptor(input: TokenStream) -> TokenStream { pub fn derive_dynamic_plugin(input: TokenStream) -> TokenStream { app_plugin::derive_dynamic_plugin(input) } + +// From https://github.com/randomPoison/type-uuid +#[proc_macro_derive(TypeUuid, attributes(uuid))] +pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + type_uuid::type_uuid_derive(input) +} + +#[proc_macro] +pub fn external_type_uuid(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + type_uuid::external_type_uuid(tokens) +} diff --git a/crates/bevy_derive/src/modules.rs b/crates/bevy_derive/src/modules.rs index 75a7b9c31f509..91596242f09bc 100644 --- a/crates/bevy_derive/src/modules.rs +++ b/crates/bevy_derive/src/modules.rs @@ -8,6 +8,7 @@ pub struct Modules { pub bevy_asset: String, pub bevy_core: String, pub bevy_app: String, + pub bevy_type_registry: String, } impl Modules { @@ -17,6 +18,7 @@ impl Modules { bevy_render: "bevy::render".to_string(), bevy_core: "bevy::core".to_string(), bevy_app: "bevy::app".to_string(), + bevy_type_registry: "bevy::type_registry".to_string(), } } @@ -26,6 +28,7 @@ impl Modules { bevy_render: "bevy_render".to_string(), bevy_core: "bevy_core".to_string(), bevy_app: "bevy_app".to_string(), + bevy_type_registry: "bevy_type_registry".to_string(), } } } diff --git a/crates/bevy_derive/src/render_resource.rs b/crates/bevy_derive/src/render_resource.rs index 933ec8da0ca75..ea6a0b8609e05 100644 --- a/crates/bevy_derive/src/render_resource.rs +++ b/crates/bevy_derive/src/render_resource.rs @@ -25,7 +25,7 @@ pub fn derive_render_resource(input: TokenStream) -> TokenStream { use #bevy_core_path::Bytes; Some(self.byte_len()) } - fn texture(&self) -> Option<#bevy_asset_path::Handle<#bevy_render_path::texture::Texture>> { + fn texture(&self) -> Option<&#bevy_asset_path::Handle<#bevy_render_path::texture::Texture>> { None } diff --git a/crates/bevy_derive/src/type_uuid.rs b/crates/bevy_derive/src/type_uuid.rs new file mode 100644 index 0000000000000..9078ff3be1353 --- /dev/null +++ b/crates/bevy_derive/src/type_uuid.rs @@ -0,0 +1,98 @@ +extern crate proc_macro; + +use quote::quote; +use syn::{parse::*, *}; +use uuid::Uuid; + +use crate::modules::{get_modules, get_path}; + +pub fn type_uuid_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + // Construct a representation of Rust code as a syntax tree + // that we can manipulate + let ast: DeriveInput = syn::parse(input).unwrap(); + let modules = get_modules(&ast.attrs); + let bevy_type_registry_path: Path = get_path(&modules.bevy_type_registry); + + // Build the trait implementation + let name = &ast.ident; + + let mut uuid = None; + for attribute in ast.attrs.iter().filter_map(|attr| attr.parse_meta().ok()) { + let name_value = if let Meta::NameValue(name_value) = attribute { + name_value + } else { + continue; + }; + + if name_value + .path + .get_ident() + .map(|i| i != "uuid") + .unwrap_or(true) + { + continue; + } + + let uuid_str = match name_value.lit { + Lit::Str(lit_str) => lit_str, + _ => panic!("uuid attribute must take the form `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"`"), + }; + + uuid = Some( + Uuid::parse_str(&uuid_str.value()) + .expect("Value specified to `#[uuid]` attribute is not a valid UUID"), + ); + } + + let uuid = + uuid.expect("No `#[uuid = \"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"` attribute found"); + let bytes = uuid + .as_bytes() + .iter() + .map(|byte| format!("{:#X}", byte)) + .map(|byte_str| syn::parse_str::(&byte_str).unwrap()); + + let gen = quote! { + impl #bevy_type_registry_path::TypeUuid for #name { + const TYPE_UUID: #bevy_type_registry_path::Uuid = #bevy_type_registry_path::Uuid::from_bytes([ + #( #bytes ),* + ]); + } + }; + gen.into() +} + +struct ExternalDeriveInput { + path: ExprPath, + uuid_str: LitStr, +} + +impl Parse for ExternalDeriveInput { + fn parse(input: ParseStream) -> Result { + let path = input.parse()?; + input.parse::()?; + let uuid_str = input.parse()?; + Ok(Self { path, uuid_str }) + } +} + +pub fn external_type_uuid(tokens: proc_macro::TokenStream) -> proc_macro::TokenStream { + let ExternalDeriveInput { path, uuid_str } = parse_macro_input!(tokens as ExternalDeriveInput); + + let uuid = Uuid::parse_str(&uuid_str.value()).expect("Value was not a valid UUID"); + + let bytes = uuid + .as_bytes() + .iter() + .map(|byte| format!("{:#X}", byte)) + .map(|byte_str| syn::parse_str::(&byte_str).unwrap()); + + let gen = quote! { + impl crate::TypeUuid for #path { + const TYPE_UUID: Uuid = uuid::Uuid::from_bytes([ + #( #bytes ),* + ]); + } + }; + gen.into() +} diff --git a/crates/bevy_ecs/Cargo.toml b/crates/bevy_ecs/Cargo.toml index ccc2135c0662c..ccc61d7930ccf 100644 --- a/crates/bevy_ecs/Cargo.toml +++ b/crates/bevy_ecs/Cargo.toml @@ -21,6 +21,7 @@ bevy_hecs = { path = "hecs", features = ["macros", "serialize"], version = "0.2. bevy_tasks = { path = "../bevy_tasks", version = "0.2.1" } bevy_utils = { path = "../bevy_utils", version = "0.2.1" } rand = "0.7.3" +thiserror = "1.0" fixedbitset = "0.3.1" downcast-rs = "1.2.0" parking_lot = "0.11.0" diff --git a/crates/bevy_ecs/src/resource/resources.rs b/crates/bevy_ecs/src/resource/resources.rs index a582c3ea11646..e957ae674ec36 100644 --- a/crates/bevy_ecs/src/resource/resources.rs +++ b/crates/bevy_ecs/src/resource/resources.rs @@ -157,6 +157,18 @@ impl Resources { }) } + pub fn get_or_insert_with( + &mut self, + get_resource: impl FnOnce() -> T, + ) -> RefMut<'_, T> { + // NOTE: this double-get is really weird. why cant we use an if-let here? + if self.get::().is_some() { + return self.get_mut::().unwrap(); + } + self.insert(get_resource()); + self.get_mut().unwrap() + } + /// Returns a clone of the underlying resource, this is helpful when borrowing something /// cloneable (like a task pool) without taking a borrow on the resource map pub fn get_cloned(&self) -> Option { diff --git a/crates/bevy_ecs/src/system/commands.rs b/crates/bevy_ecs/src/system/commands.rs index 90cab955f5e76..f0b2218009ec2 100644 --- a/crates/bevy_ecs/src/system/commands.rs +++ b/crates/bevy_ecs/src/system/commands.rs @@ -2,32 +2,11 @@ use super::SystemId; use crate::resource::{Resource, Resources}; use bevy_hecs::{Bundle, Component, DynamicBundle, Entity, EntityReserver, World}; use parking_lot::Mutex; -use std::{fmt, marker::PhantomData, sync::Arc}; - -/// A queued command to mutate the current [World] or [Resources] -pub enum Command { - WriteWorld(Box), - WriteResources(Box), -} - -impl fmt::Debug for Command { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Command::WriteWorld(x) => f - .debug_tuple("WriteWorld") - .field(&(x.as_ref() as *const dyn WorldWriter)) - .finish(), - Command::WriteResources(x) => f - .debug_tuple("WriteResources") - .field(&(x.as_ref() as *const dyn ResourcesWriter)) - .finish(), - } - } -} +use std::{marker::PhantomData, sync::Arc}; /// A [World] mutation -pub trait WorldWriter: Send + Sync { - fn write(self: Box, world: &mut World); +pub trait Command: Send + Sync { + fn write(self: Box, world: &mut World, resources: &mut Resources); } #[derive(Debug)] @@ -38,11 +17,11 @@ where components: T, } -impl WorldWriter for Spawn +impl Command for Spawn where T: DynamicBundle + Send + Sync + 'static, { - fn write(self: Box, world: &mut World) { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { world.spawn(self.components); } } @@ -55,12 +34,12 @@ where components_iter: I, } -impl WorldWriter for SpawnBatch +impl Command for SpawnBatch where I: IntoIterator + Send + Sync, I::Item: Bundle, { - fn write(self: Box, world: &mut World) { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { world.spawn_batch(self.components_iter); } } @@ -70,8 +49,8 @@ pub(crate) struct Despawn { entity: Entity, } -impl WorldWriter for Despawn { - fn write(self: Box, world: &mut World) { +impl Command for Despawn { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { if let Err(e) = world.despawn(self.entity) { log::debug!("Failed to despawn entity {:?}: {}", self.entity, e); } @@ -86,11 +65,11 @@ where components: T, } -impl WorldWriter for Insert +impl Command for Insert where T: DynamicBundle + Send + Sync + 'static, { - fn write(self: Box, world: &mut World) { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { world.insert(self.entity, self.components).unwrap(); } } @@ -104,11 +83,11 @@ where component: T, } -impl WorldWriter for InsertOne +impl Command for InsertOne where T: Component, { - fn write(self: Box, world: &mut World) { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { world.insert(self.entity, (self.component,)).unwrap(); } } @@ -122,11 +101,11 @@ where phantom: PhantomData, } -impl WorldWriter for RemoveOne +impl Command for RemoveOne where T: Component, { - fn write(self: Box, world: &mut World) { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { if world.get::(self.entity).is_ok() { world.remove_one::(self.entity).unwrap(); } @@ -142,11 +121,11 @@ where phantom: PhantomData, } -impl WorldWriter for Remove +impl Command for Remove where T: Bundle + Send + Sync + 'static, { - fn write(self: Box, world: &mut World) { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { world.remove::(self.entity).unwrap(); } } @@ -159,8 +138,8 @@ pub struct InsertResource { resource: T, } -impl ResourcesWriter for InsertResource { - fn write(self: Box, resources: &mut Resources) { +impl Command for InsertResource { + fn write(self: Box, _world: &mut World, resources: &mut Resources) { resources.insert(self.resource); } } @@ -171,15 +150,15 @@ pub(crate) struct InsertLocalResource { system_id: SystemId, } -impl ResourcesWriter for InsertLocalResource { - fn write(self: Box, resources: &mut Resources) { +impl Command for InsertLocalResource { + fn write(self: Box, _world: &mut World, resources: &mut Resources) { resources.insert_local(self.system_id, self.resource); } } -#[derive(Debug, Default)] +#[derive(Default)] pub struct CommandsInternal { - pub commands: Vec, + pub commands: Vec>, pub current_entity: Option, pub entity_reserver: Option, } @@ -192,8 +171,7 @@ impl CommandsInternal { .expect("entity reserver has not been set") .reserve_entity(); self.current_entity = Some(entity); - self.commands - .push(Command::WriteWorld(Box::new(Insert { entity, components }))); + self.commands.push(Box::new(Insert { entity, components })); self } @@ -202,43 +180,35 @@ impl CommandsInternal { components: impl DynamicBundle + Send + Sync + 'static, ) -> &mut Self { let current_entity = self.current_entity.expect("Cannot add components because the 'current entity' is not set. You should spawn an entity first."); - self.commands.push(Command::WriteWorld(Box::new(Insert { + self.commands.push(Box::new(Insert { entity: current_entity, components, - }))); + })); self } pub fn with(&mut self, component: impl Component) -> &mut Self { let current_entity = self.current_entity.expect("Cannot add component because the 'current entity' is not set. You should spawn an entity first."); - self.commands.push(Command::WriteWorld(Box::new(InsertOne { + self.commands.push(Box::new(InsertOne { entity: current_entity, component, - }))); + })); self } - pub fn write_world(&mut self, world_writer: W) -> &mut Self { - self.write_world_boxed(Box::new(world_writer)) - } - - pub fn write_world_boxed(&mut self, world_writer: Box) -> &mut Self { - self.commands.push(Command::WriteWorld(world_writer)); + pub fn add_command(&mut self, command: C) -> &mut Self { + self.commands.push(Box::new(command)); self } - pub fn write_resources( - &mut self, - resources_writer: W, - ) -> &mut Self { - self.commands - .push(Command::WriteResources(Box::new(resources_writer))); + pub fn add_command_boxed(&mut self, command: Box) -> &mut Self { + self.commands.push(command); self } } /// A queue of [Command]s to run on the current [World] and [Resources] -#[derive(Debug, Default, Clone)] +#[derive(Default, Clone)] pub struct Commands { pub commands: Arc>, } @@ -257,12 +227,12 @@ impl Commands { I: IntoIterator + Send + Sync + 'static, I::Item: Bundle, { - self.write_world(SpawnBatch { components_iter }) + self.add_command(SpawnBatch { components_iter }) } /// Despawns only the specified entity, ignoring any other consideration. pub fn despawn(&mut self, entity: Entity) -> &mut Self { - self.write_world(Despawn { entity }) + self.add_command(Despawn { entity }) } pub fn with(&mut self, component: impl Component) -> &mut Self { @@ -289,15 +259,15 @@ impl Commands { entity: Entity, components: impl DynamicBundle + Send + Sync + 'static, ) -> &mut Self { - self.write_world(Insert { entity, components }) + self.add_command(Insert { entity, components }) } pub fn insert_one(&mut self, entity: Entity, component: impl Component) -> &mut Self { - self.write_world(InsertOne { entity, component }) + self.add_command(InsertOne { entity, component }) } pub fn insert_resource(&mut self, resource: T) -> &mut Self { - self.write_resources(InsertResource { resource }) + self.add_command(InsertResource { resource }) } pub fn insert_local_resource( @@ -305,39 +275,26 @@ impl Commands { system_id: SystemId, resource: T, ) -> &mut Self { - self.write_resources(InsertLocalResource { + self.add_command(InsertLocalResource { system_id, resource, }) } - pub fn write_world(&mut self, world_writer: W) -> &mut Self { - self.commands.lock().write_world(world_writer); + pub fn add_command(&mut self, command: C) -> &mut Self { + self.commands.lock().add_command(command); self } - pub fn write_world_boxed(&mut self, world_writer: Box) -> &mut Self { - self.commands.lock().write_world_boxed(world_writer); - self - } - - pub fn write_resources( - &mut self, - resources_writer: W, - ) -> &mut Self { - self.commands.lock().write_resources(resources_writer); + pub fn add_command_boxed(&mut self, command: Box) -> &mut Self { + self.commands.lock().add_command_boxed(command); self } pub fn apply(&self, world: &mut World, resources: &mut Resources) { let mut commands = self.commands.lock(); for command in commands.commands.drain(..) { - match command { - Command::WriteWorld(writer) => { - writer.write(world); - } - Command::WriteResources(writer) => writer.write(resources), - } + command.write(world, resources); } } @@ -361,7 +318,7 @@ impl Commands { where T: Component, { - self.write_world(RemoveOne:: { + self.add_command(RemoveOne:: { entity, phantom: PhantomData, }) @@ -371,7 +328,7 @@ impl Commands { where T: Bundle + Send + Sync + 'static, { - self.write_world(Remove:: { + self.add_command(Remove:: { entity, phantom: PhantomData, }) diff --git a/crates/bevy_ecs/src/world/entity_map.rs b/crates/bevy_ecs/src/world/entity_map.rs new file mode 100644 index 0000000000000..a188156c7c6b7 --- /dev/null +++ b/crates/bevy_ecs/src/world/entity_map.rs @@ -0,0 +1,49 @@ +use std::collections::hash_map::Entry; + +use bevy_hecs::Entity; +use bevy_utils::HashMap; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum MapEntitiesError { + #[error("The given entity does not exist in the map.")] + EntityNotFound(Entity), +} + +pub trait MapEntities { + fn map_entities(&mut self, entity_map: &EntityMap) -> Result<(), MapEntitiesError>; +} + +#[derive(Default, Debug)] +pub struct EntityMap { + map: HashMap, +} + +impl EntityMap { + pub fn insert(&mut self, from: Entity, to: Entity) { + self.map.insert(from, to); + } + + pub fn remove(&mut self, entity: Entity) { + self.map.remove(&entity); + } + + pub fn entry(&mut self, entity: Entity) -> Entry<'_, Entity, Entity> { + self.map.entry(entity) + } + + pub fn get(&self, entity: Entity) -> Result { + self.map + .get(&entity) + .cloned() + .ok_or(MapEntitiesError::EntityNotFound(entity)) + } + + pub fn keys(&self) -> impl Iterator + '_ { + self.map.keys().cloned() + } + + pub fn values(&self) -> impl Iterator + '_ { + self.map.values().cloned() + } +} diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index aba809de37e37..3e59bd70aea1b 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1,3 +1,5 @@ +mod entity_map; mod world_builder; +pub use entity_map::*; pub use world_builder::*; diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index bbb88a2caae25..3d97bd1ad7014 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -16,10 +16,17 @@ keywords = ["bevy"] # bevy bevy_app = { path = "../bevy_app", version = "0.2.1" } bevy_asset = { path = "../bevy_asset", version = "0.2.1" } +bevy_ecs = { path = "../bevy_ecs", version = "0.2.1" } +bevy_pbr = { path = "../bevy_pbr", version = "0.2.1" } bevy_render = { path = "../bevy_render", version = "0.2.1" } +bevy_transform = { path = "../bevy_transform", version = "0.2.1" } +bevy_math = { path = "../bevy_math", version = "0.2.1" } +bevy_scene = { path = "../bevy_scene", version = "0.2.1" } +bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" } # other gltf = { version = "0.15.2", default-features = false, features = ["utils"] } +image = { version = "0.23", default-features = false } thiserror = "1.0" anyhow = "1.0" base64 = "0.12.3" diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 2d5f7db90d3d0..a819b1ef75314 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -3,7 +3,6 @@ pub use loader::*; use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_render::mesh::Mesh; /// Adds support for GLTF file loading to Apps #[derive(Default)] @@ -11,6 +10,6 @@ pub struct GltfPlugin; impl Plugin for GltfPlugin { fn build(&self, app: &mut AppBuilder) { - app.add_asset_loader::(); + app.init_asset_loader::(); } } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index c926139ddaf14..3587c40a80aa3 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1,32 +1,24 @@ +use anyhow::Result; +use bevy_asset::{AssetIoError, AssetLoader, AssetPath, LoadContext, LoadedAsset}; +use bevy_ecs::{World, WorldBuilderSource}; +use bevy_math::Mat4; +use bevy_pbr::prelude::{PbrComponents, StandardMaterial}; use bevy_render::{ mesh::{Indices, Mesh, VertexAttribute}, pipeline::PrimitiveTopology, + prelude::{Color, Texture}, + texture::TextureFormat, }; - -use anyhow::Result; -use bevy_asset::AssetLoader; -use gltf::{buffer::Source, mesh::Mode}; -use std::{fs, io, path::Path}; +use bevy_scene::Scene; +use bevy_transform::{ + hierarchy::{BuildWorldChildren, WorldChildBuilder}, + prelude::{GlobalTransform, Transform}, +}; +use gltf::{mesh::Mode, Primitive}; +use image::{GenericImageView, ImageFormat}; +use std::path::Path; use thiserror::Error; -/// Loads meshes from GLTF files into Mesh assets -/// -/// NOTE: eventually this will loading into Scenes instead of Meshes -#[derive(Debug, Default)] -pub struct GltfLoader; - -impl AssetLoader for GltfLoader { - fn from_bytes(&self, asset_path: &Path, bytes: Vec) -> Result { - let mesh = load_gltf(asset_path, bytes)?; - Ok(mesh) - } - - fn extensions(&self) -> &[&str] { - static EXTENSIONS: &[&str] = &["gltf", "glb"]; - EXTENSIONS - } -} - /// An error that occurs when loading a GLTF file #[derive(Error, Debug)] pub enum GltfError { @@ -34,91 +26,258 @@ pub enum GltfError { UnsupportedPrimitive { mode: Mode }, #[error("Invalid GLTF file.")] Gltf(#[from] gltf::Error), - #[error("Failed to load file.")] - Io(#[from] io::Error), #[error("Binary blob is missing.")] MissingBlob, #[error("Failed to decode base64 mesh data.")] Base64Decode(#[from] base64::DecodeError), #[error("Unsupported buffer format.")] BufferFormatUnsupported, + #[error("Invalid image mime type.")] + InvalidImageMimeType(String), + #[error("Failed to convert image to rgb8.")] + ImageRgb8ConversionFailure, + #[error("Failed to load an image.")] + ImageError(#[from] image::ImageError), + #[error("Failed to load an asset path.")] + AssetIoError(#[from] AssetIoError), } -fn get_primitive_topology(mode: Mode) -> Result { - match mode { - Mode::Points => Ok(PrimitiveTopology::PointList), - Mode::Lines => Ok(PrimitiveTopology::LineList), - Mode::LineStrip => Ok(PrimitiveTopology::LineStrip), - Mode::Triangles => Ok(PrimitiveTopology::TriangleList), - Mode::TriangleStrip => Ok(PrimitiveTopology::TriangleStrip), - mode => Err(GltfError::UnsupportedPrimitive { mode }), +/// Loads meshes from GLTF files into Mesh assets +#[derive(Default)] +pub struct GltfLoader; + +impl AssetLoader for GltfLoader { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { + Ok(load_gltf(bytes, load_context)?) + } + + fn extensions(&self) -> &[&str] { + static EXTENSIONS: &[&str] = &["gltf", "glb"]; + EXTENSIONS } } -// TODO: this should return a scene -pub fn load_gltf(asset_path: &Path, bytes: Vec) -> Result { - let gltf = gltf::Gltf::from_slice(&bytes)?; - let buffer_data = load_buffers(&gltf, asset_path)?; - for scene in gltf.scenes() { - if let Some(node) = scene.nodes().next() { - return Ok(load_node(&buffer_data, &node, 1)?); +fn load_gltf(bytes: &[u8], load_context: &mut LoadContext) -> Result<(), GltfError> { + let gltf = gltf::Gltf::from_slice(bytes)?; + let mut world = World::default(); + let buffer_data = load_buffers(&gltf, load_context, load_context.path())?; + + let world_builder = &mut world.build(); + + for mesh in gltf.meshes() { + for primitive in mesh.primitives() { + let primitive_label = primitive_label(&mesh, &primitive); + if !load_context.has_labeled_asset(&primitive_label) { + let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()])); + let primitive_topology = get_primitive_topology(primitive.mode())?; + + let mut mesh = Mesh::new(primitive_topology); + + if let Some(vertex_attribute) = reader + .read_positions() + .map(|v| VertexAttribute::position(v.collect())) + { + mesh.attributes.push(vertex_attribute); + } + + if let Some(vertex_attribute) = reader + .read_normals() + .map(|v| VertexAttribute::normal(v.collect())) + { + mesh.attributes.push(vertex_attribute); + } + + if let Some(vertex_attribute) = reader + .read_tex_coords(0) + .map(|v| VertexAttribute::uv(v.into_f32().collect())) + { + mesh.attributes.push(vertex_attribute); + } + + if let Some(indices) = reader.read_indices() { + mesh.indices = Some(Indices::U32(indices.into_u32().collect())); + }; + + load_context.set_labeled_asset(&primitive_label, LoadedAsset::new(mesh)); + }; } } - // TODO: remove this when full gltf support is added - panic!("no mesh found!") -} + for texture in gltf.textures() { + if let gltf::image::Source::View { view, mime_type } = texture.source().source() { + let start = view.offset() as usize; + let end = (view.offset() + view.length()) as usize; + let buffer = &buffer_data[view.buffer().index()][start..end]; + let format = match mime_type { + "image/png" => Ok(ImageFormat::Png), + "image/jpeg" => Ok(ImageFormat::Jpeg), + _ => Err(GltfError::InvalidImageMimeType(mime_type.to_string())), + }?; + let image = image::load_from_memory_with_format(buffer, format)?; + let size = image.dimensions(); + let image = image + .as_rgba8() + .ok_or(GltfError::ImageRgb8ConversionFailure)?; -fn load_node(buffer_data: &[Vec], node: &gltf::Node, depth: i32) -> Result { - if let Some(mesh) = node.mesh() { - if let Some(primitive) = mesh.primitives().next() { - let reader = primitive.reader(|buffer| Some(&buffer_data[buffer.index()])); - let primitive_topology = get_primitive_topology(primitive.mode())?; - let mut mesh = Mesh::new(primitive_topology); - - if let Some(vertex_attribute) = reader - .read_positions() - .map(|v| VertexAttribute::position(v.collect())) - { - mesh.attributes.push(vertex_attribute); - } + let texture_label = texture_label(&texture); + load_context.set_labeled_asset( + &texture_label, + LoadedAsset::new(Texture { + data: image.clone().into_vec(), + size: bevy_math::f32::vec2(size.0 as f32, size.1 as f32), + format: TextureFormat::Rgba8Unorm, + }), + ); + } + } - if let Some(vertex_attribute) = reader - .read_normals() - .map(|v| VertexAttribute::normal(v.collect())) - { - mesh.attributes.push(vertex_attribute); + for material in gltf.materials() { + let material_label = material_label(&material); + let pbr = material.pbr_metallic_roughness(); + let mut dependencies = Vec::new(); + let texture_handle = if let Some(info) = pbr.base_color_texture() { + match info.texture().source().source() { + gltf::image::Source::View { .. } => { + let label = texture_label(&info.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } + gltf::image::Source::Uri { uri, .. } => { + let parent = load_context.path().parent().unwrap(); + let image_path = parent.join(uri); + let asset_path = AssetPath::new(image_path, None); + let handle = load_context.get_handle(asset_path.clone()); + dependencies.push(asset_path); + Some(handle) + } } + } else { + None + }; + let color = pbr.base_color_factor(); + load_context.set_labeled_asset( + &material_label, + LoadedAsset::new(StandardMaterial { + albedo: Color::rgba(color[0], color[1], color[2], color[3]), + albedo_texture: texture_handle, + ..Default::default() + }) + .with_dependencies(dependencies), + ) + } + + for scene in gltf.scenes() { + let mut err = None; + world_builder + .spawn((Transform::default(), GlobalTransform::default())) + .with_children(|parent| { + for node in scene.nodes() { + let result = load_node(&node, parent, load_context, &buffer_data); + if result.is_err() { + err = Some(result); + return; + } + } + }); + if let Some(Err(err)) = err { + return Err(err); + } + } + + load_context.set_default_asset(LoadedAsset::new(Scene::new(world))); + + Ok(()) +} - if let Some(vertex_attribute) = reader - .read_tex_coords(0) - .map(|v| VertexAttribute::uv(v.into_f32().collect())) - { - mesh.attributes.push(vertex_attribute); +fn load_node( + node: &gltf::Node, + world_builder: &mut WorldChildBuilder, + load_context: &mut LoadContext, + buffer_data: &[Vec], +) -> Result<(), GltfError> { + let transform = node.transform(); + let mut gltf_error = None; + world_builder + .spawn(( + Transform::from_matrix(Mat4::from_cols_array_2d(&transform.matrix())), + GlobalTransform::default(), + )) + .with_children(|parent| { + if let Some(mesh) = node.mesh() { + for primitive in mesh.primitives() { + let primitive_label = primitive_label(&mesh, &primitive); + let mesh_asset_path = + AssetPath::new_ref(load_context.path(), Some(&primitive_label)); + let material = primitive.material(); + let material_label = material_label(&material); + let material_asset_path = + AssetPath::new_ref(load_context.path(), Some(&material_label)); + parent.spawn(PbrComponents { + mesh: load_context.get_handle(mesh_asset_path), + material: load_context.get_handle(material_asset_path), + ..Default::default() + }); + } } - if let Some(indices) = reader.read_indices() { - mesh.indices = Some(Indices::U32(indices.into_u32().collect())); + if parent.current_entity().is_none() { + return; } - return Ok(mesh); - } + parent.with_children(|parent| { + for child in node.children() { + if let Err(err) = load_node(&child, parent, load_context, buffer_data) { + gltf_error = Some(err); + return; + } + } + }); + }); + if let Some(err) = gltf_error { + Err(err) + } else { + Ok(()) } +} - if let Some(child) = node.children().next() { - return Ok(load_node(buffer_data, &child, depth + 1)?); +fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String { + format!("Mesh{}/Primitive{}", mesh.index(), primitive.index()) +} + +fn material_label(material: &gltf::Material) -> String { + if let Some(index) = material.index() { + format!("Material{}", index) + } else { + "MaterialDefault".to_string() } +} + +fn texture_label(texture: &gltf::Texture) -> String { + format!("Texture{}", texture.index()) +} - panic!("failed to find mesh") +fn get_primitive_topology(mode: Mode) -> Result { + match mode { + Mode::Points => Ok(PrimitiveTopology::PointList), + Mode::Lines => Ok(PrimitiveTopology::LineList), + Mode::LineStrip => Ok(PrimitiveTopology::LineStrip), + Mode::Triangles => Ok(PrimitiveTopology::TriangleList), + Mode::TriangleStrip => Ok(PrimitiveTopology::TriangleStrip), + mode => Err(GltfError::UnsupportedPrimitive { mode }), + } } -fn load_buffers(gltf: &gltf::Gltf, asset_path: &Path) -> Result>, GltfError> { +fn load_buffers( + gltf: &gltf::Gltf, + load_context: &LoadContext, + asset_path: &Path, +) -> Result>, GltfError> { const OCTET_STREAM_URI: &str = "data:application/octet-stream;base64,"; let mut buffer_data = Vec::new(); for buffer in gltf.buffers() { match buffer.source() { - Source::Uri(uri) => { + gltf::buffer::Source::Uri(uri) => { if uri.starts_with("data:") { if uri.starts_with(OCTET_STREAM_URI) { buffer_data.push(base64::decode(&uri[OCTET_STREAM_URI.len()..])?); @@ -126,12 +285,13 @@ fn load_buffers(gltf: &gltf::Gltf, asset_path: &Path) -> Result>, Gl return Err(GltfError::BufferFormatUnsupported); } } else { + // TODO: Remove this and add dep let buffer_path = asset_path.parent().unwrap().join(uri); - let buffer_bytes = fs::read(buffer_path)?; + let buffer_bytes = load_context.read_asset_bytes(buffer_path)?; buffer_data.push(buffer_bytes); } } - Source::Bin => { + gltf::buffer::Source::Bin => { if let Some(blob) = gltf.blob.as_deref() { buffer_data.push(blob.into()); } else { diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 18c11176e4064..3feb8cc6cdd40 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -13,9 +13,9 @@ pub mod prelude { } use bevy_app::prelude::*; -use bevy_asset::AddAsset; +use bevy_asset::{AddAsset, Assets, Handle}; use bevy_ecs::IntoQuerySystem; -use bevy_render::{render_graph::RenderGraph, shader}; +use bevy_render::{prelude::Color, render_graph::RenderGraph, shader}; use bevy_type_registry::RegisterType; use light::Light; use material::StandardMaterial; @@ -36,5 +36,19 @@ impl Plugin for PbrPlugin { let resources = app.resources(); let mut render_graph = resources.get_mut::().unwrap(); add_pbr_graph(&mut render_graph, resources); + + // add default StandardMaterial + let mut materials = app + .resources() + .get_mut::>() + .unwrap(); + materials.set_untracked( + Handle::::default(), + StandardMaterial { + albedo: Color::PINK, + shaded: false, + albedo_texture: None, + }, + ); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 7c6b3fcf239e3..6f63146730685 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,9 +1,10 @@ use bevy_asset::{self, Handle}; use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture}; +use bevy_type_registry::TypeUuid; /// A material with "standard" properties used in PBR lighting -#[derive(Debug, RenderResources, ShaderDefs)] -#[allow(clippy::manual_non_exhaustive)] +#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)] +#[uuid = "dace545e-4bc6-4595-a79d-c224fc694975"] pub struct StandardMaterial { pub albedo: Color, #[shader_def] @@ -11,12 +12,6 @@ pub struct StandardMaterial { #[render_resources(ignore)] #[shader_def] pub shaded: bool, - - // this is a manual implementation of the non exhaustive pattern, - // especially made to allow ..Default::default() - #[render_resources(ignore)] - #[doc(hidden)] - pub __non_exhaustive: (), } impl Default for StandardMaterial { @@ -25,7 +20,6 @@ impl Default for StandardMaterial { albedo: Color::rgb(1.0, 1.0, 1.0), albedo_texture: None, shaded: true, - __non_exhaustive: (), } } } diff --git a/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs b/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs index 2de22b657f559..e87425ecb5f83 100644 --- a/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs +++ b/crates/bevy_pbr/src/render_graph/forward_pipeline/mod.rs @@ -8,9 +8,10 @@ use bevy_render::{ shader::{Shader, ShaderStage, ShaderStages}, texture::TextureFormat, }; +use bevy_type_registry::TypeUuid; pub const FORWARD_PIPELINE_HANDLE: Handle = - Handle::from_u128(131483623140127713893804825450360211204); + Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 13148362314012771389); pub(crate) fn build_forward_pipeline(shaders: &mut Assets) -> PipelineDescriptor { PipelineDescriptor { diff --git a/crates/bevy_pbr/src/render_graph/mod.rs b/crates/bevy_pbr/src/render_graph/mod.rs index 845ae4b65ad31..8fb942427bcbf 100644 --- a/crates/bevy_pbr/src/render_graph/mod.rs +++ b/crates/bevy_pbr/src/render_graph/mod.rs @@ -38,7 +38,7 @@ pub(crate) fn add_pbr_graph(graph: &mut RenderGraph, resources: &Resources) { graph.add_system_node(node::LIGHTS, LightsNode::new(10)); let mut shaders = resources.get_mut::>().unwrap(); let mut pipelines = resources.get_mut::>().unwrap(); - pipelines.set( + pipelines.set_untracked( FORWARD_PIPELINE_HANDLE, build_forward_pipeline(&mut shaders), ); diff --git a/crates/bevy_render/src/batch/batch.rs b/crates/bevy_render/src/batch/batch.rs deleted file mode 100644 index 80f1862092807..0000000000000 --- a/crates/bevy_render/src/batch/batch.rs +++ /dev/null @@ -1,36 +0,0 @@ -use super::{BatchKey, Key}; - -#[derive(Debug, Eq, PartialEq)] -pub struct Batch -where - TKey: Key, -{ - pub batch_key: BatchKey, - pub values: Vec, - pub data: TData, -} - -impl Batch -where - TKey: Key, -{ - pub fn new(batch_key: BatchKey, data: TData) -> Self { - Batch { - data, - values: Vec::new(), - batch_key, - } - } - - pub fn add(&mut self, value: TValue) { - self.values.push(value); - } - - pub fn iter(&self) -> impl Iterator { - self.values.iter() - } - - pub fn get_key(&self, index: usize) -> Option<&TKey> { - self.batch_key.0.get(index) - } -} diff --git a/crates/bevy_render/src/batch/batcher.rs b/crates/bevy_render/src/batch/batcher.rs deleted file mode 100644 index 7eeb639aefece..0000000000000 --- a/crates/bevy_render/src/batch/batcher.rs +++ /dev/null @@ -1,292 +0,0 @@ -use super::Batch; -use bevy_utils::HashMap; -use smallvec::{smallvec, SmallVec}; -use std::{borrow::Cow, fmt, hash::Hash}; - -// TODO: add sorting by primary / secondary handle to reduce rebinds of data - -// TValue: entityid -// TKey: handleuntyped - -pub trait Key: Clone + Eq + Hash + 'static {} -impl Key for T {} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct BatchKey(pub Cow<'static, SmallVec<[TKey; 2]>>); - -impl BatchKey { - pub fn key1(key: TKey) -> Self { - BatchKey(Cow::Owned(smallvec![key])) - } - - pub fn key2(key1: TKey, key2: TKey) -> Self { - BatchKey(Cow::Owned(smallvec![key1, key2])) - } - - pub fn key3(key1: TKey, key2: TKey, key3: TKey) -> Self { - BatchKey(Cow::Owned(smallvec![key1, key2, key3])) - } -} - -#[derive(Debug)] -pub struct BatcherKeyState { - batch_key: Option>, - keys: SmallVec<[Option; 2]>, -} - -impl BatcherKeyState { - pub fn new(size: usize) -> Self { - BatcherKeyState { - keys: smallvec![None; size], - batch_key: None, - } - } - - pub fn set(&mut self, index: usize, key: TKey) { - self.keys[index] = Some(key); - } - - pub fn finish(&mut self) -> Option> { - let finished = self.keys.iter().filter(|x| x.is_some()).count() == self.keys.len(); - if finished { - let batch_key = BatchKey(Cow::Owned( - self.keys - .drain(..) - .map(|k| k.unwrap()) - .collect::>(), - )); - self.batch_key = Some(batch_key); - self.batch_key.clone() - } else { - None - } - } -} - -/// An unordered batcher intended to support an arbitrary number of keys of the same type (but with some distinguishing factor) -/// NOTE: this may or may not be useful for anything. when paired with a higher-level "BatcherSet" it would allow updating batches -// per-key (ex: material, mesh) with no global knowledge of the number of batch types (ex: (Mesh), (Material, Mesh)) that key belongs -// to. The downside is that it is completely unordered, so it probably isn't useful for front->back or back->front rendering. But -// _maybe_ for gpu instancing? -pub struct Batcher -where - TKey: Key, -{ - pub batches: HashMap, Batch>, - pub is_index: Vec bool>, - pub key_states: HashMap>, - pub key_count: usize, -} - -impl fmt::Debug for Batcher -where - TKey: Key + fmt::Debug, - TValue: fmt::Debug, - TData: fmt::Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let is_index = self - .is_index - .iter() - .map(|f| f as *const for<'r> fn(&'r TKey) -> bool) - .collect::>(); - - f.debug_struct("Batcher") - .field("batches", &self.batches) - .field("is_index", &is_index) - .field("key_states", &self.key_states) - .field("key_count", &self.key_count) - .finish() - } -} - -impl Batcher -where - TKey: Key, - TValue: Clone + Eq + Hash, - TData: Default, -{ - pub fn new(is_index: Vec bool>) -> Self { - Batcher { - batches: HashMap::default(), - key_states: HashMap::default(), - key_count: is_index.len(), - is_index, - } - } - - pub fn get_batch(&self, batch_key: &BatchKey) -> Option<&Batch> { - self.batches.get(batch_key) - } - - pub fn get_batch_mut( - &mut self, - batch_key: &BatchKey, - ) -> Option<&mut Batch> { - self.batches.get_mut(batch_key) - } - - pub fn add(&mut self, key: TKey, value: TValue) -> bool { - let batch_key = { - let key_count = self.key_count; - let key_state = self - .key_states - .entry(value.clone()) - .or_insert_with(|| BatcherKeyState::new(key_count)); - - // if all key states are set, the value is already in the batch - if key_state.batch_key.is_some() { - // TODO: if weights are ever added, make sure to get the batch and set the weight here - return true; - } - - let key_index = self - .is_index - .iter() - .enumerate() - .find(|(_i, is_index)| is_index(&key)) - .map(|(i, _)| i); - if let Some(key_index) = key_index { - key_state.set(key_index, key); - key_state.finish() - } else { - return false; - } - }; - - if let Some(batch_key) = batch_key { - let batch = self - .batches - .entry(batch_key.clone()) - .or_insert_with(|| Batch::new(batch_key, TData::default())); - - batch.add(value); - } - - true - } - - pub fn iter(&self) -> impl Iterator> { - self.batches.values() - } - - pub fn iter_mut(&mut self) -> impl Iterator> { - self.batches.values_mut() - } -} - -#[cfg(test)] -mod tests { - use super::{Batch, BatchKey, Batcher}; - use bevy_asset::{Handle, HandleUntyped}; - - #[derive(Debug, Eq, PartialEq)] - struct A; - #[derive(Debug, Eq, PartialEq)] - struct B; - #[derive(Debug, Eq, PartialEq)] - struct C; - #[derive(Debug, Eq, PartialEq, Default)] - struct Data; - #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] - struct Entity(usize); - #[test] - fn test_batcher_2() { - let mut batcher: Batcher = Batcher::new(vec![ - HandleUntyped::is_handle::, - HandleUntyped::is_handle::, - ]); - - let e1 = Entity(1); - let e2 = Entity(2); - let e3 = Entity(3); - - let a1: HandleUntyped = Handle::::new().into(); - let b1: HandleUntyped = Handle::::new().into(); - let c1: HandleUntyped = Handle::::new().into(); - - let a2: HandleUntyped = Handle::::new().into(); - let b2: HandleUntyped = Handle::::new().into(); - - let a1_b1 = BatchKey::key2(a1, b1); - let a2_b2 = BatchKey::key2(a2, b2); - - assert_eq!( - batcher.get_batch(&a1_b1), - None, - "a1_b1 batch should not exist yet" - ); - batcher.add(a1, e1); - assert_eq!( - batcher.get_batch(&a1_b1), - None, - "a1_b1 batch should not exist yet" - ); - batcher.add(b1, e1); - - let a1_b1_batch = Batch { - batch_key: a1_b1.clone(), - values: vec![e1], - data: Data, - }; - - assert_eq!( - batcher.get_batch(&a1_b1), - Some(&a1_b1_batch), - "a1_b1 batch should exist" - ); - - assert_eq!( - batcher.get_batch(&a2_b2), - None, - "a2_b2 batch should not exist yet" - ); - batcher.add(a2, e2); - assert_eq!( - batcher.get_batch(&a2_b2), - None, - "a2_b2 batch should not exist yet" - ); - batcher.add(b2, e2); - - let expected_batch = Batch { - batch_key: a2_b2.clone(), - values: vec![e2], - data: Data, - }; - - assert_eq!( - batcher.get_batch(&a2_b2), - Some(&expected_batch), - "a2_b2 batch should have e2" - ); - - batcher.add(a2, e3); - batcher.add(b2, e3); - batcher.add(c1, e3); // this should be ignored - let a2_b2_batch = Batch { - batch_key: a2_b2.clone(), - values: vec![e2, e3], - data: Data, - }; - - assert_eq!( - batcher.get_batch(&a2_b2), - Some(&a2_b2_batch), - "a2_b2 batch should have e2 and e3" - ); - - let mut found_a1_b1 = false; - let mut found_a2_b2 = false; - for batch in batcher.iter() { - if batch == &a1_b1_batch { - found_a1_b1 = true; - } else if batch == &a2_b2_batch { - found_a2_b2 = true; - } - } - - assert!(found_a1_b1 && found_a2_b2); - assert_eq!(batcher.iter().count(), 2); - } -} diff --git a/crates/bevy_render/src/batch/batcher_set.rs b/crates/bevy_render/src/batch/batcher_set.rs deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/crates/bevy_render/src/batch/mod.rs b/crates/bevy_render/src/batch/mod.rs deleted file mode 100644 index 2e1d1740ee78e..0000000000000 --- a/crates/bevy_render/src/batch/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -// mod asset_batcher; -// mod asset_batcher2; -#[allow(clippy::module_inception)] -mod batch; -mod batcher; - -// pub use asset_batcher::*; -// pub use asset_batcher2::*; -pub use batch::*; -pub use batcher::*; diff --git a/crates/bevy_render/src/color.rs b/crates/bevy_render/src/color.rs index 75aefb9e301ac..65ee754f0b989 100644 --- a/crates/bevy_render/src/color.rs +++ b/crates/bevy_render/src/color.rs @@ -28,6 +28,7 @@ impl Color { pub const BLUE: Color = Color::rgb_linear(0.0, 0.0, 1.0); pub const GREEN: Color = Color::rgb_linear(0.0, 1.0, 0.0); pub const NONE: Color = Color::rgba_linear(0.0, 0.0, 0.0, 0.0); + pub const PINK: Color = Color::rgb_linear(1.0, 0.08, 0.58); pub const RED: Color = Color::rgb_linear(1.0, 0.0, 0.0); pub const WHITE: Color = Color::rgb_linear(1.0, 1.0, 1.0); @@ -259,11 +260,18 @@ impl From for Color { } } +impl From<[f32; 4]> for Color { + fn from(value: [f32; 4]) -> Self { + Color::rgba(value[0], value[1], value[2], value[3]) + } +} + impl Into<[f32; 4]> for Color { fn into(self) -> [f32; 4] { [self.red, self.green, self.blue, self.alpha] } } + impl Mul for Color { type Output = Color; diff --git a/crates/bevy_render/src/draw.rs b/crates/bevy_render/src/draw.rs index e64ee9c493e59..39ea3cbc587a9 100644 --- a/crates/bevy_render/src/draw.rs +++ b/crates/bevy_render/src/draw.rs @@ -73,8 +73,10 @@ impl Draw { self.render_commands.clear(); } - pub fn set_pipeline(&mut self, pipeline: Handle) { - self.render_command(RenderCommand::SetPipeline { pipeline }); + pub fn set_pipeline(&mut self, pipeline: &Handle) { + self.render_command(RenderCommand::SetPipeline { + pipeline: pipeline.clone_weak(), + }); } pub fn set_vertex_buffer(&mut self, slot: u32, buffer: BufferId, offset: u64) { @@ -143,7 +145,7 @@ impl<'a> UnsafeClone for DrawContext<'a> { render_resource_context: self.render_resource_context.unsafe_clone(), vertex_buffer_descriptors: self.vertex_buffer_descriptors.unsafe_clone(), shared_buffers: self.shared_buffers.unsafe_clone(), - current_pipeline: self.current_pipeline, + current_pipeline: self.current_pipeline.clone(), } } } @@ -252,7 +254,7 @@ impl<'a> DrawContext<'a> { pub fn set_pipeline( &mut self, draw: &mut Draw, - pipeline_handle: Handle, + pipeline_handle: &Handle, specialization: &PipelineSpecialization, ) -> Result<(), DrawError> { let specialized_pipeline = if let Some(specialized_pipeline) = self @@ -271,14 +273,15 @@ impl<'a> DrawContext<'a> { ) }; - draw.set_pipeline(specialized_pipeline); - self.current_pipeline = Some(specialized_pipeline); + draw.set_pipeline(&specialized_pipeline); + self.current_pipeline = Some(specialized_pipeline.clone_weak()); Ok(()) } pub fn get_pipeline_descriptor(&self) -> Result<&PipelineDescriptor, DrawError> { self.current_pipeline - .and_then(|handle| self.pipelines.get(&handle)) + .as_ref() + .and_then(|handle| self.pipelines.get(handle)) .ok_or(DrawError::NoPipelineSet) } @@ -295,10 +298,13 @@ impl<'a> DrawContext<'a> { draw: &mut Draw, render_resource_bindings: &mut [&mut RenderResourceBindings], ) -> Result<(), DrawError> { - let pipeline = self.current_pipeline.ok_or(DrawError::NoPipelineSet)?; + let pipeline = self + .current_pipeline + .as_ref() + .ok_or(DrawError::NoPipelineSet)?; let pipeline_descriptor = self .pipelines - .get(&pipeline) + .get(pipeline) .ok_or(DrawError::NonExistentPipeline)?; let layout = pipeline_descriptor .get_layout() @@ -325,10 +331,13 @@ impl<'a> DrawContext<'a> { index: u32, bind_group: &BindGroup, ) -> Result<(), DrawError> { - let pipeline = self.current_pipeline.ok_or(DrawError::NoPipelineSet)?; + let pipeline = self + .current_pipeline + .as_ref() + .ok_or(DrawError::NoPipelineSet)?; let pipeline_descriptor = self .pipelines - .get(&pipeline) + .get(pipeline) .ok_or(DrawError::NonExistentPipeline)?; let layout = pipeline_descriptor .get_layout() @@ -344,10 +353,13 @@ impl<'a> DrawContext<'a> { draw: &mut Draw, render_resource_bindings: &[&RenderResourceBindings], ) -> Result<(), DrawError> { - let pipeline = self.current_pipeline.ok_or(DrawError::NoPipelineSet)?; + let pipeline = self + .current_pipeline + .as_ref() + .ok_or(DrawError::NoPipelineSet)?; let pipeline_descriptor = self .pipelines - .get(&pipeline) + .get(pipeline) .ok_or(DrawError::NonExistentPipeline)?; let layout = pipeline_descriptor .get_layout() diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 5d30da39a2439..a1dc9ea65b284 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -1,4 +1,3 @@ -pub mod batch; pub mod camera; pub mod color; pub mod colorspace; @@ -12,6 +11,7 @@ pub mod renderer; pub mod shader; pub mod texture; +use bevy_type_registry::RegisterType; pub use once_cell; pub mod prelude { @@ -33,7 +33,6 @@ use base::{MainPass, Msaa}; use bevy_app::prelude::*; use bevy_asset::AddAsset; use bevy_ecs::{IntoQuerySystem, IntoThreadLocalSystem}; -use bevy_type_registry::RegisterType; use camera::{ ActiveCameras, Camera, OrthographicProjection, PerspectiveProjection, VisibleEntities, }; @@ -83,11 +82,11 @@ impl Plugin for RenderPlugin { fn build(&self, app: &mut AppBuilder) { #[cfg(feature = "png")] { - app.add_asset_loader::(); + app.init_asset_loader::(); } #[cfg(feature = "hdr")] { - app.add_asset_loader::(); + app.init_asset_loader::(); } if app.resources().get::().is_none() { diff --git a/crates/bevy_render/src/mesh/mesh.rs b/crates/bevy_render/src/mesh/mesh.rs index f08eae7edbc84..592869959cf5f 100644 --- a/crates/bevy_render/src/mesh/mesh.rs +++ b/crates/bevy_render/src/mesh/mesh.rs @@ -11,6 +11,7 @@ use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_core::AsBytes; use bevy_ecs::{Local, Query, Res, ResMut}; use bevy_math::*; +use bevy_type_registry::TypeUuid; use bevy_utils::HashSet; use std::borrow::Cow; use thiserror::Error; @@ -112,7 +113,8 @@ pub enum Indices { U32(Vec), } -#[derive(Debug)] +#[derive(Debug, TypeUuid)] +#[uuid = "8ecbac0f-f545-4473-ad43-e1f4243af51e"] pub struct Mesh { pub primitive_topology: PrimitiveTopology, pub attributes: Vec, @@ -476,10 +478,10 @@ pub mod shape { fn remove_current_mesh_resources( render_resource_context: &dyn RenderResourceContext, - handle: Handle, + handle: &Handle, ) { if let Some(RenderResourceId::Buffer(buffer)) = - render_resource_context.get_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX) + render_resource_context.get_asset_resource(&handle, VERTEX_BUFFER_ASSET_INDEX) { render_resource_context.remove_buffer(buffer); render_resource_context.remove_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX); @@ -520,15 +522,15 @@ pub fn mesh_resource_provider_system( let render_resource_context = &**render_resource_context; for event in state.mesh_event_reader.iter(&mesh_events) { match event { - AssetEvent::Created { handle } => { - changed_meshes.insert(*handle); + AssetEvent::Created { ref handle } => { + changed_meshes.insert(handle.clone_weak()); } - AssetEvent::Modified { handle } => { - changed_meshes.insert(*handle); - remove_current_mesh_resources(render_resource_context, *handle); + AssetEvent::Modified { ref handle } => { + changed_meshes.insert(handle.clone_weak()); + remove_current_mesh_resources(render_resource_context, handle); } - AssetEvent::Removed { handle } => { - remove_current_mesh_resources(render_resource_context, *handle); + AssetEvent::Removed { ref handle } => { + remove_current_mesh_resources(render_resource_context, handle); // if mesh was modified and removed in the same update, ignore the modification // events are ordered so future modification events are ok changed_meshes.remove(handle); @@ -560,12 +562,12 @@ pub fn mesh_resource_provider_system( ); render_resource_context.set_asset_resource( - *changed_mesh_handle, + changed_mesh_handle, RenderResourceId::Buffer(vertex_buffer), VERTEX_BUFFER_ASSET_INDEX, ); render_resource_context.set_asset_resource( - *changed_mesh_handle, + changed_mesh_handle, RenderResourceId::Buffer(index_buffer), INDEX_BUFFER_ASSET_INDEX, ); @@ -574,20 +576,20 @@ pub fn mesh_resource_provider_system( // TODO: remove this once batches are pipeline specific and deprecate assigned_meshes draw target for (handle, mut render_pipelines) in &mut query.iter() { - if let Some(mesh) = meshes.get(&handle) { + if let Some(mesh) = meshes.get(handle) { for render_pipeline in render_pipelines.pipelines.iter_mut() { render_pipeline.specialization.primitive_topology = mesh.primitive_topology; } } if let Some(RenderResourceId::Buffer(vertex_buffer)) = - render_resource_context.get_asset_resource(*handle, VERTEX_BUFFER_ASSET_INDEX) + render_resource_context.get_asset_resource(handle, VERTEX_BUFFER_ASSET_INDEX) { render_pipelines.bindings.set_vertex_buffer( "Vertex", vertex_buffer, render_resource_context - .get_asset_resource(*handle, INDEX_BUFFER_ASSET_INDEX) + .get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX) .and_then(|r| { if let RenderResourceId::Buffer(buffer) = r { Some(buffer) diff --git a/crates/bevy_render/src/pass/render_pass.rs b/crates/bevy_render/src/pass/render_pass.rs index e8b58b175e7d2..aac0bf4bc9c48 100644 --- a/crates/bevy_render/src/pass/render_pass.rs +++ b/crates/bevy_render/src/pass/render_pass.rs @@ -9,7 +9,7 @@ pub trait RenderPass { fn get_render_context(&self) -> &dyn RenderContext; fn set_index_buffer(&mut self, buffer: BufferId, offset: u64); fn set_vertex_buffer(&mut self, start_slot: u32, buffer: BufferId, offset: u64); - fn set_pipeline(&mut self, pipeline_handle: Handle); + fn set_pipeline(&mut self, pipeline_handle: &Handle); fn set_viewport(&mut self, x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32); fn set_stencil_reference(&mut self, reference: u32); fn draw(&mut self, vertices: Range, instances: Range); diff --git a/crates/bevy_render/src/pipeline/pipeline.rs b/crates/bevy_render/src/pipeline/pipeline.rs index a163cc74f82fe..06cff5dcf0e03 100644 --- a/crates/bevy_render/src/pipeline/pipeline.rs +++ b/crates/bevy_render/src/pipeline/pipeline.rs @@ -11,8 +11,10 @@ use crate::{ texture::TextureFormat, }; use bevy_asset::Assets; +use bevy_type_registry::TypeUuid; -#[derive(Clone, Debug)] +#[derive(Clone, Debug, TypeUuid)] +#[uuid = "ebfc1d11-a2a4-44cb-8f12-c49cc631146c"] pub struct PipelineDescriptor { pub name: Option, pub layout: Option, @@ -137,7 +139,7 @@ impl PipelineDescriptor { .shader_stages .fragment .as_ref() - .map(|handle| shaders.get(&handle).unwrap()); + .map(|handle| shaders.get(handle).unwrap()); let mut layouts = vec![vertex_spirv.reflect_layout(bevy_conventions).unwrap()]; if let Some(ref fragment_spirv) = fragment_spirv { diff --git a/crates/bevy_render/src/pipeline/pipeline_compiler.rs b/crates/bevy_render/src/pipeline/pipeline_compiler.rs index 276f9e94a6a06..8bbe138d87169 100644 --- a/crates/bevy_render/src/pipeline/pipeline_compiler.rs +++ b/crates/bevy_render/src/pipeline/pipeline_compiler.rs @@ -77,14 +77,14 @@ impl PipelineCompiler { ) -> Handle { let specialized_shaders = self .specialized_shaders - .entry(*shader_handle) + .entry(shader_handle.clone_weak()) .or_insert_with(Vec::new); let shader = shaders.get(shader_handle).unwrap(); // don't produce new shader if the input source is already spirv if let ShaderSource::Spirv(_) = shader.source { - return *shader_handle; + return shader_handle.clone_weak(); } if let Some(specialized_shader) = @@ -95,7 +95,7 @@ impl PipelineCompiler { }) { // if shader has already been compiled with current configuration, use existing shader - specialized_shader.shader + specialized_shader.shader.clone_weak() } else { // if no shader exists with the current configuration, create new shader and compile let shader_def_vec = shader_specialization @@ -105,21 +105,22 @@ impl PipelineCompiler { .collect::>(); let compiled_shader = shader.get_spirv_shader(Some(&shader_def_vec)); let specialized_handle = shaders.add(compiled_shader); + let weak_specialized_handle = specialized_handle.clone_weak(); specialized_shaders.push(SpecializedShader { shader: specialized_handle, specialization: shader_specialization.clone(), }); - specialized_handle + weak_specialized_handle } } pub fn get_specialized_pipeline( &self, - pipeline: Handle, + pipeline: &Handle, specialization: &PipelineSpecialization, ) -> Option> { self.specialized_pipelines - .get(&pipeline) + .get(pipeline) .and_then(|specialized_pipelines| { specialized_pipelines .iter() @@ -127,7 +128,7 @@ impl PipelineCompiler { ¤t_specialized_pipeline.specialization == specialization }) }) - .map(|specialized_pipeline| specialized_pipeline.pipeline) + .map(|specialized_pipeline| specialized_pipeline.pipeline.clone_weak()) } pub fn compile_pipeline( @@ -135,11 +136,11 @@ impl PipelineCompiler { render_resource_context: &dyn RenderResourceContext, pipelines: &mut Assets, shaders: &mut Assets, - source_pipeline: Handle, + source_pipeline: &Handle, vertex_buffer_descriptors: &VertexBufferDescriptors, pipeline_specialization: &PipelineSpecialization, ) -> Handle { - let source_descriptor = pipelines.get(&source_pipeline).unwrap(); + let source_descriptor = pipelines.get(source_pipeline).unwrap(); let mut specialized_descriptor = source_descriptor.clone(); specialized_descriptor.shader_stages.vertex = self.compile_shader( shaders, @@ -171,21 +172,22 @@ impl PipelineCompiler { let specialized_pipeline_handle = pipelines.add(specialized_descriptor); render_resource_context.create_render_pipeline( - specialized_pipeline_handle, + specialized_pipeline_handle.clone_weak(), pipelines.get(&specialized_pipeline_handle).unwrap(), &shaders, ); let specialized_pipelines = self .specialized_pipelines - .entry(source_pipeline) + .entry(source_pipeline.clone_weak()) .or_insert_with(Vec::new); + let weak_specialized_pipeline_handle = specialized_pipeline_handle.clone_weak(); specialized_pipelines.push(SpecializedPipeline { pipeline: specialized_pipeline_handle, specialization: pipeline_specialization.clone(), }); - specialized_pipeline_handle + weak_specialized_pipeline_handle } pub fn iter_compiled_pipelines( diff --git a/crates/bevy_render/src/pipeline/render_pipelines.rs b/crates/bevy_render/src/pipeline/render_pipelines.rs index b941412230dfe..0d66933bb34a0 100644 --- a/crates/bevy_render/src/pipeline/render_pipelines.rs +++ b/crates/bevy_render/src/pipeline/render_pipelines.rs @@ -56,7 +56,7 @@ impl RenderPipelines { RenderPipelines { pipelines: handles .into_iter() - .map(|pipeline| RenderPipeline::new(*pipeline)) + .map(|pipeline| RenderPipeline::new(pipeline.clone_weak())) .collect::>(), ..Default::default() } @@ -84,7 +84,13 @@ pub fn draw_render_pipelines_system( continue; } - let mesh = meshes.get(mesh_handle).unwrap(); + // don't render if the mesh isn't loaded yet + let mesh = if let Some(mesh) = meshes.get(mesh_handle) { + mesh + } else { + continue; + }; + let (index_range, index_format) = match mesh.indices.as_ref() { Some(Indices::U32(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint32), Some(Indices::U16(indices)) => (Some(0..indices.len() as u32), IndexFormat::Uint16), @@ -101,7 +107,7 @@ pub fn draw_render_pipelines_system( draw_context .set_pipeline( &mut draw, - render_pipeline.pipeline, + &render_pipeline.pipeline, &render_pipeline.specialization, ) .unwrap(); diff --git a/crates/bevy_render/src/render_graph/nodes/pass_node.rs b/crates/bevy_render/src/render_graph/nodes/pass_node.rs index 979edd1a2aae2..a693a212aed3f 100644 --- a/crates/bevy_render/src/render_graph/nodes/pass_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/pass_node.rs @@ -246,9 +246,9 @@ where match render_command { RenderCommand::SetPipeline { pipeline } => { // TODO: Filter pipelines - render_pass.set_pipeline(*pipeline); + render_pass.set_pipeline(pipeline); let descriptor = pipelines.get(pipeline).unwrap(); - draw_state.set_pipeline(*pipeline, descriptor); + draw_state.set_pipeline(pipeline, descriptor); // try to set current camera bind group let layout = descriptor.get_layout().unwrap(); @@ -303,7 +303,7 @@ where bind_group, dynamic_uniform_indices, } => { - let pipeline = pipelines.get(&draw_state.pipeline.unwrap()).unwrap(); + let pipeline = pipelines.get(draw_state.pipeline.as_ref().unwrap()).unwrap(); let layout = pipeline.get_layout().unwrap(); let bind_group_descriptor = layout.get_bind_group(*index).unwrap(); render_pass.set_bind_group( @@ -358,14 +358,14 @@ impl DrawState { pub fn set_pipeline( &mut self, - handle: Handle, + handle: &Handle, descriptor: &PipelineDescriptor, ) { self.bind_groups.clear(); self.vertex_buffers.clear(); self.index_buffer = None; - self.pipeline = Some(handle); + self.pipeline = Some(handle.clone_weak()); let layout = descriptor.get_layout().unwrap(); self.bind_groups.resize(layout.bind_groups.len(), None); self.vertex_buffers diff --git a/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs b/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs index d1f4341fddb97..dbf4cabe10310 100644 --- a/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs @@ -9,7 +9,7 @@ use crate::{ texture, }; -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Asset, Assets, Handle, HandleId}; use bevy_ecs::{ Commands, Entity, IntoQuerySystem, Local, Query, Res, ResMut, Resources, System, World, }; @@ -547,7 +547,7 @@ const EXPECT_ASSET_MESSAGE: &str = "Only assets that exist should be in the modi impl SystemNode for AssetRenderResourcesNode where - T: renderer::RenderResources, + T: renderer::RenderResources + Asset, { fn get_system(&self, commands: &mut Commands) -> Box { let system = asset_render_resources_node_system::.system(); @@ -555,7 +555,7 @@ where system.id(), RenderResourcesNodeState { command_queue: self.command_queue.clone(), - uniform_buffer_arrays: UniformBufferArrays::, T>::default(), + uniform_buffer_arrays: UniformBufferArrays::::default(), dynamic_uniforms: self.dynamic_uniforms, }, ); @@ -564,8 +564,8 @@ where } } -fn asset_render_resources_node_system( - mut state: Local, T>>, +fn asset_render_resources_node_system( + mut state: Local>, assets: Res>, mut asset_render_resource_bindings: ResMut, render_resource_context: Res>, @@ -575,22 +575,20 @@ fn asset_render_resources_node_system( let uniform_buffer_arrays = &mut state.uniform_buffer_arrays; let render_resource_context = &**render_resource_context; - let modified_assets = assets - .iter() - .map(|(handle, _)| handle) - .collect::>>(); + let modified_assets = assets.ids().collect::>(); uniform_buffer_arrays.begin_update(); // initialize uniform buffer arrays using the first RenderResources if let Some(first_handle) = modified_assets.get(0) { - let asset = assets.get(first_handle).expect(EXPECT_ASSET_MESSAGE); + let asset = assets.get(*first_handle).expect(EXPECT_ASSET_MESSAGE); uniform_buffer_arrays.initialize(asset); } for asset_handle in modified_assets.iter() { - let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE); + let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE); uniform_buffer_arrays.prepare_uniform_buffers(*asset_handle, asset); - let mut bindings = asset_render_resource_bindings.get_or_insert_mut(*asset_handle); + let mut bindings = + asset_render_resource_bindings.get_or_insert_mut(&Handle::::weak(*asset_handle)); setup_uniform_texture_resources::(&asset, render_resource_context, &mut bindings); } @@ -604,9 +602,9 @@ fn asset_render_resources_node_system( 0..state.uniform_buffer_arrays.staging_buffer_size as u64, &mut |mut staging_buffer, _render_resource_context| { for asset_handle in modified_assets.iter() { - let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE); - let mut render_resource_bindings = - asset_render_resource_bindings.get_or_insert_mut(*asset_handle); + let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE); + let mut render_resource_bindings = asset_render_resource_bindings + .get_or_insert_mut(&Handle::::weak(*asset_handle)); // TODO: only setup buffer if we haven't seen this handle before state.uniform_buffer_arrays.write_uniform_buffers( *asset_handle, @@ -627,9 +625,9 @@ fn asset_render_resources_node_system( } else { let mut staging_buffer: [u8; 0] = []; for asset_handle in modified_assets.iter() { - let asset = assets.get(&asset_handle).expect(EXPECT_ASSET_MESSAGE); + let asset = assets.get(*asset_handle).expect(EXPECT_ASSET_MESSAGE); let mut render_resource_bindings = - asset_render_resource_bindings.get_or_insert_mut(*asset_handle); + asset_render_resource_bindings.get_or_insert_mut(&Handle::::weak(*asset_handle)); // TODO: only setup buffer if we haven't seen this handle before state.uniform_buffer_arrays.write_uniform_buffers( *asset_handle, @@ -646,7 +644,7 @@ fn asset_render_resources_node_system( if !draw.is_visible { continue; } - if let Some(asset_bindings) = asset_render_resource_bindings.get(*asset_handle) { + if let Some(asset_bindings) = asset_render_resource_bindings.get(asset_handle) { render_pipelines.bindings.extend(asset_bindings); } } diff --git a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs b/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs index 6b642d39c7215..2dbf48b172124 100644 --- a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs +++ b/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs @@ -31,7 +31,7 @@ impl Node for TextureCopyNode { for event in self.texture_event_reader.iter(&texture_events) { match event { AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - if let Some(texture) = textures.get(&handle) { + if let Some(texture) = textures.get(handle) { let texture_descriptor: TextureDescriptor = texture.into(); let width = texture.size.x() as usize; let aligned_width = get_aligned(texture.size.x()); @@ -57,7 +57,7 @@ impl Node for TextureCopyNode { let texture_resource = render_context .resources() - .get_asset_resource(*handle, TEXTURE_ASSET_INDEX) + .get_asset_resource(handle, TEXTURE_ASSET_INDEX) .unwrap(); render_context.copy_buffer_to_texture( diff --git a/crates/bevy_render/src/renderer/headless_render_resource_context.rs b/crates/bevy_render/src/renderer/headless_render_resource_context.rs index 7e478ce75878e..196c050675bb7 100644 --- a/crates/bevy_render/src/renderer/headless_render_resource_context.rs +++ b/crates/bevy_render/src/renderer/headless_render_resource_context.rs @@ -76,7 +76,7 @@ impl RenderResourceContext for HeadlessRenderResourceContext { buffer } - fn create_shader_module(&self, _shader_handle: Handle, _shaders: &Assets) {} + fn create_shader_module(&self, _shader_handle: &Handle, _shaders: &Assets) {} fn remove_buffer(&self, buffer: BufferId) { self.buffer_info.write().remove(&buffer); @@ -122,7 +122,7 @@ impl RenderResourceContext for HeadlessRenderResourceContext { ) { } - fn create_shader_module_from_source(&self, _shader_handle: Handle, _shader: &Shader) {} + fn create_shader_module_from_source(&self, _shader_handle: &Handle, _shader: &Shader) {} fn remove_asset_resource_untyped(&self, handle: HandleUntyped, index: usize) { self.asset_resources.write().remove(&(handle, index)); diff --git a/crates/bevy_render/src/renderer/render_resource/render_resource.rs b/crates/bevy_render/src/renderer/render_resource/render_resource.rs index 121722a7dd9b9..616c8b65e9a9a 100644 --- a/crates/bevy_render/src/renderer/render_resource/render_resource.rs +++ b/crates/bevy_render/src/renderer/render_resource/render_resource.rs @@ -77,7 +77,7 @@ pub trait RenderResource { fn write_buffer_bytes(&self, buffer: &mut [u8]); fn buffer_byte_len(&self) -> Option; // TODO: consider making these panic by default, but return non-options - fn texture(&self) -> Option>; + fn texture(&self) -> Option<&Handle>; } pub trait RenderResources: Send + Sync + 'static { @@ -136,7 +136,7 @@ macro_rules! impl_render_resource_bytes { Some(self.byte_len()) } - fn texture(&self) -> Option> { + fn texture(&self) -> Option<&Handle> { None } } @@ -175,7 +175,7 @@ where Some(self.byte_len()) } - fn texture(&self) -> Option> { + fn texture(&self) -> Option<&Handle> { None } } @@ -194,7 +194,7 @@ impl RenderResource for GlobalTransform { Some(std::mem::size_of::<[f32; 16]>()) } - fn texture(&self) -> Option> { + fn texture(&self) -> Option<&Handle> { None } } diff --git a/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs b/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs index 7cded917f03db..99dcdf07f5217 100644 --- a/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs +++ b/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs @@ -3,7 +3,7 @@ use crate::{ pipeline::{BindGroupDescriptor, BindGroupDescriptorId, PipelineDescriptor}, renderer::RenderResourceContext, }; -use bevy_asset::{Handle, HandleUntyped}; +use bevy_asset::{Asset, Handle, HandleUntyped}; use bevy_utils::{HashMap, HashSet}; use std::{hash::Hash, ops::Range}; use uuid::Uuid; @@ -259,18 +259,21 @@ pub struct AssetRenderResourceBindings { } impl AssetRenderResourceBindings { - pub fn get(&self, handle: Handle) -> Option<&RenderResourceBindings> { - self.bindings.get(&HandleUntyped::from(handle)) + pub fn get(&self, handle: &Handle) -> Option<&RenderResourceBindings> { + self.bindings.get(&handle.clone_weak_untyped()) } - pub fn get_or_insert_mut(&mut self, handle: Handle) -> &mut RenderResourceBindings { + pub fn get_or_insert_mut( + &mut self, + handle: &Handle, + ) -> &mut RenderResourceBindings { self.bindings - .entry(HandleUntyped::from(handle)) + .entry(handle.clone_weak_untyped()) .or_insert_with(RenderResourceBindings::default) } - pub fn get_mut(&mut self, handle: Handle) -> Option<&mut RenderResourceBindings> { - self.bindings.get_mut(&HandleUntyped::from(handle)) + pub fn get_mut(&mut self, handle: &Handle) -> Option<&mut RenderResourceBindings> { + self.bindings.get_mut(&handle.clone_weak_untyped()) } } diff --git a/crates/bevy_render/src/renderer/render_resource_context.rs b/crates/bevy_render/src/renderer/render_resource_context.rs index 95e1f21329831..8849d51c31ea5 100644 --- a/crates/bevy_render/src/renderer/render_resource_context.rs +++ b/crates/bevy_render/src/renderer/render_resource_context.rs @@ -4,7 +4,7 @@ use crate::{ shader::Shader, texture::{SamplerDescriptor, TextureDescriptor}, }; -use bevy_asset::{Assets, Handle, HandleUntyped}; +use bevy_asset::{Asset, Assets, Handle, HandleUntyped}; use bevy_window::Window; use downcast_rs::{impl_downcast, Downcast}; use std::ops::Range; @@ -27,8 +27,8 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static { fn map_buffer(&self, id: BufferId); fn unmap_buffer(&self, id: BufferId); fn create_buffer_with_data(&self, buffer_info: BufferInfo, data: &[u8]) -> BufferId; - fn create_shader_module(&self, shader_handle: Handle, shaders: &Assets); - fn create_shader_module_from_source(&self, shader_handle: Handle, shader: &Shader); + fn create_shader_module(&self, shader_handle: &Handle, shaders: &Assets); + fn create_shader_module_from_source(&self, shader_handle: &Handle, shader: &Shader); fn remove_buffer(&self, buffer: BufferId); fn remove_texture(&self, texture: TextureId); fn remove_sampler(&self, sampler: SamplerId); @@ -63,25 +63,33 @@ pub trait RenderResourceContext: Downcast + Send + Sync + 'static { } impl dyn RenderResourceContext { - pub fn set_asset_resource(&self, handle: Handle, resource: RenderResourceId, index: usize) - where - T: 'static, + pub fn set_asset_resource( + &self, + handle: &Handle, + resource: RenderResourceId, + index: usize, + ) where + T: Asset, { - self.set_asset_resource_untyped(handle.into(), resource, index); + self.set_asset_resource_untyped(handle.clone_weak_untyped(), resource, index); } - pub fn get_asset_resource(&self, handle: Handle, index: usize) -> Option + pub fn get_asset_resource( + &self, + handle: &Handle, + index: usize, + ) -> Option where - T: 'static, + T: Asset, { - self.get_asset_resource_untyped(handle.into(), index) + self.get_asset_resource_untyped(handle.clone_weak_untyped(), index) } - pub fn remove_asset_resource(&self, handle: Handle, index: usize) + pub fn remove_asset_resource(&self, handle: &Handle, index: usize) where - T: 'static, + T: Asset, { - self.remove_asset_resource_untyped(handle.into(), index); + self.remove_asset_resource_untyped(handle.clone_weak_untyped(), index); } } diff --git a/crates/bevy_render/src/shader/shader.rs b/crates/bevy_render/src/shader/shader.rs index 1581242617b97..271487f8dc278 100644 --- a/crates/bevy_render/src/shader/shader.rs +++ b/crates/bevy_render/src/shader/shader.rs @@ -1,5 +1,6 @@ use super::ShaderLayout; use bevy_asset::Handle; +use bevy_type_registry::TypeUuid; use std::marker::Copy; /// The stage of a shader @@ -98,7 +99,8 @@ impl ShaderSource { } /// A shader, as defined by its [ShaderSource] and [ShaderStage] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, TypeUuid)] +#[uuid = "d95bc916-6c55-4de3-9622-37e7b6969fda"] pub struct Shader { pub source: ShaderSource, pub stage: ShaderStage, @@ -164,8 +166,8 @@ impl<'a> Iterator for ShaderStagesIterator<'a> { fn next(&mut self) -> Option { let ret = match self.state { - 0 => Some(self.shader_stages.vertex), - 1 => self.shader_stages.fragment, + 0 => Some(self.shader_stages.vertex.clone_weak()), + 1 => self.shader_stages.fragment.as_ref().map(|h| h.clone_weak()), _ => None, }; self.state += 1; diff --git a/crates/bevy_render/src/shader/shader_defs.rs b/crates/bevy_render/src/shader/shader_defs.rs index 2a5f4b1a5010e..8224417736132 100644 --- a/crates/bevy_render/src/shader/shader_defs.rs +++ b/crates/bevy_render/src/shader/shader_defs.rs @@ -1,4 +1,4 @@ -use bevy_asset::{Assets, Handle}; +use bevy_asset::{Asset, Assets, Handle}; use crate::{pipeline::RenderPipelines, Texture}; pub use bevy_derive::ShaderDefs; @@ -91,14 +91,14 @@ pub fn clear_shader_defs_system(mut query: Query<&mut RenderPipelines>) { } /// Updates [RenderPipelines] with the latest [ShaderDefs] from a given asset type -pub fn asset_shader_defs_system( +pub fn asset_shader_defs_system( assets: Res>, mut query: Query<(&Handle, &mut RenderPipelines)>, ) where T: ShaderDefs + Send + Sync + 'static, { for (asset_handle, mut render_pipelines) in &mut query.iter() { - let shader_defs = assets.get(&asset_handle).unwrap(); + let shader_defs = assets.get(asset_handle).unwrap(); for shader_def in shader_defs.iter_shader_defs() { for render_pipeline in render_pipelines.pipelines.iter_mut() { render_pipeline diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index 013c9c3c081e2..2c3bbc3c2d8f7 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -1,15 +1,14 @@ use super::{Texture, TextureFormat}; use anyhow::Result; -use bevy_asset::AssetLoader; +use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_math::Vec2; -use std::path::Path; /// Loads HDR textures as Texture assets #[derive(Clone, Default)] pub struct HdrTextureLoader; -impl AssetLoader for HdrTextureLoader { - fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { +impl AssetLoader for HdrTextureLoader { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { let format = TextureFormat::Rgba32Float; debug_assert_eq!( format.pixel_size(), @@ -17,7 +16,7 @@ impl AssetLoader for HdrTextureLoader { "Format should have 32bit x 4 size" ); - let decoder = image::hdr::HdrDecoder::new(bytes.as_slice())?; + let decoder = image::hdr::HdrDecoder::new(bytes)?; let info = decoder.metadata(); let rgb_data = decoder.read_image_hdr()?; let mut rgba_data = Vec::with_capacity(rgb_data.len() * format.pixel_size()); @@ -31,11 +30,14 @@ impl AssetLoader for HdrTextureLoader { rgba_data.extend_from_slice(&alpha.to_ne_bytes()); } - Ok(Texture::new( + let texture = Texture::new( Vec2::new(info.width as f32, info.height as f32), rgba_data, format, - )) + ); + + load_context.set_default_asset(LoadedAsset::new(texture)); + Ok(()) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs index 06b1b04be2c40..6f6f10032378e 100644 --- a/crates/bevy_render/src/texture/image_texture_loader.rs +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -1,8 +1,7 @@ use super::{Texture, TextureFormat}; use anyhow::Result; -use bevy_asset::AssetLoader; +use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_math::Vec2; -use std::path::Path; /// Loader for images that can be read by the `image` crate. /// @@ -10,14 +9,14 @@ use std::path::Path; #[derive(Clone, Default)] pub struct ImageTextureLoader; -impl AssetLoader for ImageTextureLoader { - fn from_bytes(&self, asset_path: &Path, bytes: Vec) -> Result { +impl AssetLoader for ImageTextureLoader { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { use bevy_core::AsBytes; // Find the image type we expect. A file with the extension "png" should // probably load as a PNG. - let ext = asset_path.extension().unwrap().to_str().unwrap(); + let ext = load_context.path().extension().unwrap().to_str().unwrap(); // NOTE: If more formats are added they can be added here. let img_format = if ext.eq_ignore_ascii_case("png") { @@ -26,7 +25,7 @@ impl AssetLoader for ImageTextureLoader { panic!( "Unexpected image format {:?} for file {}, this is an error in `bevy_render`.", ext, - asset_path.display() + load_context.path().display() ) }; @@ -36,7 +35,7 @@ impl AssetLoader for ImageTextureLoader { // needs to be added, so the image data needs to be converted in those // cases. - let dyn_img = image::load_from_memory_with_format(bytes.as_slice(), img_format)?; + let dyn_img = image::load_from_memory_with_format(bytes, img_format)?; let width; let height; @@ -143,11 +142,9 @@ impl AssetLoader for ImageTextureLoader { } } - Ok(Texture::new( - Vec2::new(width as f32, height as f32), - data, - format, - )) + let texture = Texture::new(Vec2::new(width as f32, height as f32), data, format); + load_context.set_default_asset(LoadedAsset::new(texture)); + Ok(()) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_render/src/texture/texture.rs b/crates/bevy_render/src/texture/texture.rs index 46884ccea8fa1..23a6ffc1ebe73 100644 --- a/crates/bevy_render/src/texture/texture.rs +++ b/crates/bevy_render/src/texture/texture.rs @@ -6,12 +6,14 @@ use bevy_app::prelude::{EventReader, Events}; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::{Res, ResMut}; use bevy_math::Vec2; +use bevy_type_registry::TypeUuid; use bevy_utils::HashSet; pub const TEXTURE_ASSET_INDEX: usize = 0; pub const SAMPLER_ASSET_INDEX: usize = 1; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"] pub struct Texture { pub data: Vec, pub size: Vec2, @@ -82,14 +84,14 @@ impl Texture { for event in state.event_reader.iter(&texture_events) { match event { AssetEvent::Created { handle } => { - changed_textures.insert(*handle); + changed_textures.insert(handle); } AssetEvent::Modified { handle } => { - changed_textures.insert(*handle); - Self::remove_current_texture_resources(render_resource_context, *handle); + changed_textures.insert(handle); + Self::remove_current_texture_resources(render_resource_context, handle); } AssetEvent::Removed { handle } => { - Self::remove_current_texture_resources(render_resource_context, *handle); + Self::remove_current_texture_resources(render_resource_context, handle); // if texture was modified and removed in the same update, ignore the modification // events are ordered so future modification events are ok changed_textures.remove(handle); @@ -98,7 +100,7 @@ impl Texture { } for texture_handle in changed_textures.iter() { - if let Some(texture) = textures.get(texture_handle) { + if let Some(texture) = textures.get(*texture_handle) { let texture_descriptor: TextureDescriptor = texture.into(); let texture_resource = render_resource_context.create_texture(texture_descriptor); @@ -106,12 +108,12 @@ impl Texture { let sampler_resource = render_resource_context.create_sampler(&sampler_descriptor); render_resource_context.set_asset_resource( - *texture_handle, + texture_handle, RenderResourceId::Texture(texture_resource), TEXTURE_ASSET_INDEX, ); render_resource_context.set_asset_resource( - *texture_handle, + texture_handle, RenderResourceId::Sampler(sampler_resource), SAMPLER_ASSET_INDEX, ); @@ -121,7 +123,7 @@ impl Texture { fn remove_current_texture_resources( render_resource_context: &dyn RenderResourceContext, - handle: Handle, + handle: &Handle, ) { if let Some(RenderResourceId::Texture(resource)) = render_resource_context.get_asset_resource(handle, TEXTURE_ASSET_INDEX) @@ -145,7 +147,7 @@ pub struct TextureResourceSystemState { impl RenderResource for Option> { fn resource_type(&self) -> Option { - self.map(|_texture| RenderResourceType::Texture) + self.as_ref().map(|_texture| RenderResourceType::Texture) } fn write_buffer_bytes(&self, _buffer: &mut [u8]) {} @@ -154,8 +156,8 @@ impl RenderResource for Option> { None } - fn texture(&self) -> Option> { - *self + fn texture(&self) -> Option<&Handle> { + self.as_ref() } } @@ -170,7 +172,7 @@ impl RenderResource for Handle { None } - fn texture(&self) -> Option> { - Some(*self) + fn texture(&self) -> Option<&Handle> { + Some(self) } } diff --git a/crates/bevy_scene/src/command.rs b/crates/bevy_scene/src/command.rs new file mode 100644 index 0000000000000..3c9c6ec2526ee --- /dev/null +++ b/crates/bevy_scene/src/command.rs @@ -0,0 +1,25 @@ +use bevy_asset::Handle; +use bevy_ecs::{Command, Commands, Resources, World}; + +use crate::{Scene, SceneSpawner}; + +pub struct SpawnScene { + scene_handle: Handle, +} + +impl Command for SpawnScene { + fn write(self: Box, _world: &mut World, resources: &mut Resources) { + let mut spawner = resources.get_mut::().unwrap(); + spawner.spawn(self.scene_handle); + } +} + +pub trait SpawnSceneCommands { + fn spawn_scene(&mut self, scene: Handle) -> &mut Self; +} + +impl SpawnSceneCommands for Commands { + fn spawn_scene(&mut self, scene_handle: Handle) -> &mut Self { + self.add_command(SpawnScene { scene_handle }) + } +} diff --git a/crates/bevy_scene/src/dynamic_scene.rs b/crates/bevy_scene/src/dynamic_scene.rs new file mode 100644 index 0000000000000..f8ed4bbb0d55a --- /dev/null +++ b/crates/bevy_scene/src/dynamic_scene.rs @@ -0,0 +1,117 @@ +use crate::{serde::SceneSerializer, Scene}; +use anyhow::Result; +use bevy_ecs::{EntityMap, Resources, World}; +use bevy_property::{DynamicProperties, PropertyTypeRegistry}; +use bevy_type_registry::{ComponentRegistry, TypeRegistry, TypeUuid}; +use serde::Serialize; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DynamicSceneToWorldError { + #[error("Scene contains an unregistered component.")] + UnregisteredComponent { type_name: String }, +} + +#[derive(Default, TypeUuid)] +#[uuid = "749479b1-fb8c-4ff8-a775-623aa76014f5"] +pub struct DynamicScene { + pub entities: Vec, +} + +pub struct Entity { + pub entity: u32, + pub components: Vec, +} + +impl DynamicScene { + pub fn from_scene(scene: &Scene, component_registry: &ComponentRegistry) -> Self { + Self::from_world(&scene.world, component_registry) + } + + pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self { + let mut scene = DynamicScene::default(); + for archetype in world.archetypes() { + let mut entities = Vec::new(); + for (index, entity) in archetype.iter_entities().enumerate() { + if index == entities.len() { + entities.push(Entity { + entity: entity.id(), + components: Vec::new(), + }) + } + for type_info in archetype.types() { + if let Some(component_registration) = component_registry.get(&type_info.id()) { + let properties = + component_registration.get_component_properties(&archetype, index); + + entities[index].components.push(properties.to_dynamic()); + } + } + } + + scene.entities.extend(entities.drain(..)); + } + + scene + } + + pub fn write_to_world( + &self, + world: &mut World, + resources: &Resources, + ) -> Result<(), DynamicSceneToWorldError> { + let type_registry = resources.get::().unwrap(); + let component_registry = type_registry.component.read(); + let mut entity_map = EntityMap::default(); + for scene_entity in self.entities.iter() { + let new_entity = world.reserve_entity(); + entity_map.insert(bevy_ecs::Entity::new(scene_entity.entity), new_entity); + for component in scene_entity.components.iter() { + let component_registration = component_registry + .get_with_name(&component.type_name) + .ok_or_else(|| DynamicSceneToWorldError::UnregisteredComponent { + type_name: component.type_name.to_string(), + })?; + if world.has_component_type(new_entity, component_registration.ty) { + component_registration.apply_property_to_entity(world, new_entity, component); + } else { + component_registration + .add_property_to_entity(world, resources, new_entity, component); + } + } + } + + for component_registration in component_registry.iter() { + component_registration + .map_entities(world, &entity_map) + .unwrap(); + } + + Ok(()) + } + + // TODO: move to AssetSaver when it is implemented + pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result { + serialize_ron(SceneSerializer::new(self, registry)) + } + + pub fn get_scene(&self, resources: &Resources) -> Result { + let mut world = World::default(); + self.write_to_world(&mut world, resources)?; + Ok(Scene::new(world)) + } +} + +pub fn serialize_ron(serialize: S) -> Result +where + S: Serialize, +{ + let pretty_config = ron::ser::PrettyConfig::default() + .with_decimal_floats(true) + .with_indentor(" ".to_string()) + .with_new_line("\n".to_string()); + let mut buf = Vec::new(); + let mut ron_serializer = ron::ser::Serializer::new(&mut buf, Some(pretty_config), false)?; + serialize.serialize(&mut ron_serializer)?; + Ok(String::from_utf8(buf).unwrap()) +} diff --git a/crates/bevy_scene/src/lib.rs b/crates/bevy_scene/src/lib.rs index a6698398a452b..7be0a4e346a44 100644 --- a/crates/bevy_scene/src/lib.rs +++ b/crates/bevy_scene/src/lib.rs @@ -1,14 +1,18 @@ -mod loaded_scenes; +mod command; +mod dynamic_scene; mod scene; +mod scene_loader; mod scene_spawner; pub mod serde; -pub use loaded_scenes::*; +pub use command::*; +pub use dynamic_scene::*; pub use scene::*; +pub use scene_loader::*; pub use scene_spawner::*; pub mod prelude { - pub use crate::{Scene, SceneSpawner}; + pub use crate::{DynamicScene, Scene, SceneSpawner, SpawnSceneCommands}; } use bevy_app::prelude::*; @@ -22,8 +26,9 @@ pub const SCENE_STAGE: &str = "scene"; impl Plugin for ScenePlugin { fn build(&self, app: &mut AppBuilder) { - app.add_asset::() - .add_asset_loader::() + app.add_asset::() + .add_asset::() + .init_asset_loader::() .init_resource::() .add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE) .add_system_to_stage(SCENE_STAGE, scene_spawner_system.thread_local_system()); diff --git a/crates/bevy_scene/src/scene.rs b/crates/bevy_scene/src/scene.rs index 61692ca299084..29b67211e8541 100644 --- a/crates/bevy_scene/src/scene.rs +++ b/crates/bevy_scene/src/scene.rs @@ -1,65 +1,14 @@ -use crate::serde::SceneSerializer; -use anyhow::Result; use bevy_ecs::World; -use bevy_property::{DynamicProperties, PropertyTypeRegistry}; -use bevy_type_registry::ComponentRegistry; -use serde::Serialize; +use bevy_type_registry::TypeUuid; -#[derive(Debug, Default)] +#[derive(Debug, TypeUuid)] +#[uuid = "c156503c-edd9-4ec7-8d33-dab392df03cd"] pub struct Scene { - pub entities: Vec, -} - -#[derive(Debug)] -pub struct Entity { - pub entity: u32, - pub components: Vec, + pub world: World, } impl Scene { - pub fn from_world(world: &World, component_registry: &ComponentRegistry) -> Self { - let mut scene = Scene::default(); - for archetype in world.archetypes() { - let mut entities = Vec::new(); - for (index, entity) in archetype.iter_entities().enumerate() { - if index == entities.len() { - entities.push(Entity { - entity: entity.id(), - components: Vec::new(), - }) - } - for type_info in archetype.types() { - if let Some(component_registration) = component_registry.get(&type_info.id()) { - let properties = - component_registration.get_component_properties(&archetype, index); - - entities[index].components.push(properties.to_dynamic()); - } - } - } - - scene.entities.extend(entities.drain(..)); - } - - scene + pub fn new(world: World) -> Self { + Self { world } } - - // TODO: move to AssetSaver when it is implemented - pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result { - serialize_ron(SceneSerializer::new(self, registry)) - } -} - -pub fn serialize_ron(serialize: S) -> Result -where - S: Serialize, -{ - let pretty_config = ron::ser::PrettyConfig::default() - .with_decimal_floats(true) - .with_indentor(" ".to_string()) - .with_new_line("\n".to_string()); - let mut buf = Vec::new(); - let mut ron_serializer = ron::ser::Serializer::new(&mut buf, Some(pretty_config), false)?; - serialize.serialize(&mut ron_serializer)?; - Ok(String::from_utf8(buf).unwrap()) } diff --git a/crates/bevy_scene/src/loaded_scenes.rs b/crates/bevy_scene/src/scene_loader.rs similarity index 76% rename from crates/bevy_scene/src/loaded_scenes.rs rename to crates/bevy_scene/src/scene_loader.rs index dcc80027d4737..b3d8382acf5a4 100644 --- a/crates/bevy_scene/src/loaded_scenes.rs +++ b/crates/bevy_scene/src/scene_loader.rs @@ -1,12 +1,12 @@ -use crate::{serde::SceneDeserializer, Scene}; +use crate::serde::SceneDeserializer; use anyhow::Result; -use bevy_asset::AssetLoader; +use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_ecs::{FromResources, Resources}; use bevy_property::PropertyTypeRegistry; use bevy_type_registry::TypeRegistry; use parking_lot::RwLock; use serde::de::DeserializeSeed; -use std::{path::Path, sync::Arc}; +use std::sync::Arc; #[derive(Debug)] pub struct SceneLoader { @@ -22,15 +22,16 @@ impl FromResources for SceneLoader { } } -impl AssetLoader for SceneLoader { - fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { +impl AssetLoader for SceneLoader { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { let registry = self.property_type_registry.read(); let mut deserializer = ron::de::Deserializer::from_bytes(&bytes)?; let scene_deserializer = SceneDeserializer { property_type_registry: ®istry, }; let scene = scene_deserializer.deserialize(&mut deserializer)?; - Ok(scene) + load_context.set_default_asset(LoadedAsset::new(scene)); + Ok(()) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 1810e9f88d1ee..48eb32e8071d2 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -1,7 +1,7 @@ -use crate::Scene; +use crate::{DynamicScene, Scene}; use bevy_app::prelude::*; use bevy_asset::{AssetEvent, Assets, Handle}; -use bevy_ecs::{Resources, World}; +use bevy_ecs::{EntityMap, Resources, World}; use bevy_type_registry::TypeRegistry; use bevy_utils::HashMap; use thiserror::Error; @@ -9,7 +9,7 @@ use uuid::Uuid; #[derive(Debug)] struct InstanceInfo { - entity_map: HashMap, + entity_map: EntityMap, } #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] @@ -24,10 +24,12 @@ impl InstanceId { #[derive(Default)] pub struct SceneSpawner { spawned_scenes: HashMap, Vec>, + spawned_dynamic_scenes: HashMap, Vec>, spawned_instances: HashMap, - scene_asset_event_reader: EventReader>, - scenes_to_instance: Vec>, - scenes_to_despawn: Vec>, + scene_asset_event_reader: EventReader>, + dynamic_scenes_to_spawn: Vec>, + scenes_to_spawn: Vec>, + scenes_to_despawn: Vec>, } #[derive(Error, Debug)] @@ -35,76 +37,83 @@ pub enum SceneSpawnError { #[error("Scene contains an unregistered component.")] UnregisteredComponent { type_name: String }, #[error("Scene does not exist. Perhaps it is still loading?")] - NonExistentScene { handle: Handle }, + NonExistentScene { handle: Handle }, + #[error("Scene does not exist. Perhaps it is still loading?")] + NonExistentRealScene { handle: Handle }, } impl SceneSpawner { + pub fn spawn_dynamic(&mut self, scene_handle: Handle) { + self.dynamic_scenes_to_spawn.push(scene_handle); + } + pub fn spawn(&mut self, scene_handle: Handle) { - self.scenes_to_instance.push(scene_handle); + self.scenes_to_spawn.push(scene_handle); } - pub fn despawn(&mut self, scene_handle: Handle) { + pub fn despawn(&mut self, scene_handle: Handle) { self.scenes_to_despawn.push(scene_handle); } pub fn despawn_sync( &mut self, world: &mut World, - scene_handle: Handle, + scene_handle: Handle, ) -> Result<(), SceneSpawnError> { - if let Some(instance_ids) = self.spawned_scenes.get(&scene_handle) { + if let Some(instance_ids) = self.spawned_dynamic_scenes.get(&scene_handle) { for instance_id in instance_ids { if let Some(instance) = self.spawned_instances.get(&instance_id) { for entity in instance.entity_map.values() { - let _ = world.despawn(*entity); // Ignore the result, despawn only cares if it exists. + let _ = world.despawn(entity); // Ignore the result, despawn only cares if it exists. } } } - self.spawned_scenes.remove(&scene_handle); + self.spawned_dynamic_scenes.remove(&scene_handle); } Ok(()) } - pub fn spawn_sync( + pub fn spawn_dynamic_sync( &mut self, world: &mut World, resources: &Resources, - scene_handle: Handle, + scene_handle: &Handle, ) -> Result<(), SceneSpawnError> { let instance_id = InstanceId::new(); let mut instance_info = InstanceInfo { - entity_map: HashMap::default(), + entity_map: EntityMap::default(), }; - Self::spawn_internal(world, resources, scene_handle, &mut instance_info)?; + Self::spawn_dynamic_internal(world, resources, scene_handle, &mut instance_info)?; self.spawned_instances.insert(instance_id, instance_info); let spawned = self - .spawned_scenes - .entry(scene_handle) + .spawned_dynamic_scenes + .entry(scene_handle.clone()) .or_insert_with(Vec::new); spawned.push(instance_id); Ok(()) } - fn spawn_internal( + fn spawn_dynamic_internal( world: &mut World, resources: &Resources, - scene_handle: Handle, + scene_handle: &Handle, instance_info: &mut InstanceInfo, ) -> Result<(), SceneSpawnError> { let type_registry = resources.get::().unwrap(); let component_registry = type_registry.component.read(); - let scenes = resources.get::>().unwrap(); + let scenes = resources.get::>().unwrap(); let scene = scenes - .get(&scene_handle) - .ok_or(SceneSpawnError::NonExistentScene { - handle: scene_handle, + .get(scene_handle) + .ok_or_else(|| SceneSpawnError::NonExistentScene { + handle: scene_handle.clone_weak(), })?; for scene_entity in scene.entities.iter() { let entity = *instance_info .entity_map - .entry(scene_entity.entity) + // TODO: use Entity type directly in scenes to properly encode generation / avoid the need to patch things up? + .entry(bevy_ecs::Entity::new(scene_entity.entity)) .or_insert_with(|| world.reserve_entity()); for component in scene_entity.components.iter() { let component_registration = component_registry @@ -114,28 +123,86 @@ impl SceneSpawner { })?; if world.has_component_type(entity, component_registration.ty) { if component.type_name != "Camera" { - component_registration.apply_component_to_entity(world, entity, component); + component_registration.apply_property_to_entity(world, entity, component); } } else { component_registration - .add_component_to_entity(world, resources, entity, component); + .add_property_to_entity(world, resources, entity, component); } } } Ok(()) } + pub fn spawn_sync( + &mut self, + world: &mut World, + resources: &Resources, + scene_handle: Handle, + ) -> Result<(), SceneSpawnError> { + let instance_id = InstanceId::new(); + let mut instance_info = InstanceInfo { + entity_map: EntityMap::default(), + }; + let type_registry = resources.get::().unwrap(); + let component_registry = type_registry.component.read(); + let scenes = resources.get::>().unwrap(); + let scene = + scenes + .get(&scene_handle) + .ok_or_else(|| SceneSpawnError::NonExistentRealScene { + handle: scene_handle.clone(), + })?; + + for archetype in scene.world.archetypes() { + for scene_entity in archetype.iter_entities() { + let entity = *instance_info + .entity_map + .entry(*scene_entity) + .or_insert_with(|| world.reserve_entity()); + for type_info in archetype.types() { + if let Some(component_registration) = component_registry.get(&type_info.id()) { + component_registration.component_copy( + &scene.world, + world, + resources, + *scene_entity, + entity, + ); + } + } + } + } + for component_registration in component_registry.iter() { + component_registration + .map_entities(world, &instance_info.entity_map) + .unwrap(); + } + self.spawned_instances.insert(instance_id, instance_info); + let spawned = self + .spawned_scenes + .entry(scene_handle) + .or_insert_with(Vec::new); + spawned.push(instance_id); + Ok(()) + } + pub fn update_spawned_scenes( &mut self, world: &mut World, resources: &Resources, - scene_handles: &[Handle], + scene_handles: &[Handle], ) -> Result<(), SceneSpawnError> { for scene_handle in scene_handles { - if let Some(spawned_instances) = self.spawned_scenes.get(scene_handle) { + if let Some(spawned_instances) = self.spawned_dynamic_scenes.get(scene_handle) { for instance_id in spawned_instances.iter() { if let Some(instance_info) = self.spawned_instances.get_mut(instance_id) { - Self::spawn_internal(world, resources, *scene_handle, instance_info)?; + Self::spawn_dynamic_internal( + world, + resources, + scene_handle, + instance_info, + )?; } } } @@ -157,13 +224,25 @@ impl SceneSpawner { world: &mut World, resources: &Resources, ) -> Result<(), SceneSpawnError> { - let scenes_to_spawn = std::mem::take(&mut self.scenes_to_instance); + let scenes_to_spawn = std::mem::take(&mut self.dynamic_scenes_to_spawn); for scene_handle in scenes_to_spawn { - match self.spawn_sync(world, resources, scene_handle) { + match self.spawn_dynamic_sync(world, resources, &scene_handle) { Ok(_) => {} Err(SceneSpawnError::NonExistentScene { .. }) => { - self.scenes_to_instance.push(scene_handle) + self.dynamic_scenes_to_spawn.push(scene_handle) + } + Err(err) => return Err(err), + } + } + + let scenes_to_spawn = std::mem::take(&mut self.scenes_to_spawn); + + for scene_handle in scenes_to_spawn { + match self.spawn_sync(world, resources, scene_handle) { + Ok(_) => {} + Err(SceneSpawnError::NonExistentRealScene { handle }) => { + self.scenes_to_spawn.push(handle) } Err(err) => return Err(err), } @@ -175,7 +254,7 @@ impl SceneSpawner { pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) { let mut scene_spawner = resources.get_mut::().unwrap(); - let scene_asset_events = resources.get::>>().unwrap(); + let scene_asset_events = resources.get::>>().unwrap(); let mut updated_spawned_scenes = Vec::new(); for event in scene_spawner @@ -183,8 +262,8 @@ pub fn scene_spawner_system(world: &mut World, resources: &mut Resources) { .iter(&scene_asset_events) { if let AssetEvent::Modified { handle } = event { - if scene_spawner.spawned_scenes.contains_key(handle) { - updated_spawned_scenes.push(*handle); + if scene_spawner.spawned_dynamic_scenes.contains_key(handle) { + updated_spawned_scenes.push(handle.clone_weak()); } } } diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index 4191a0b0fd5b5..2d021ed6e7c47 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -1,4 +1,4 @@ -use crate::{Entity, Scene}; +use crate::{DynamicScene, Entity}; use anyhow::Result; use bevy_property::{ property_serde::{DynamicPropertiesDeserializer, DynamicPropertiesSerializer}, @@ -11,12 +11,12 @@ use serde::{ }; pub struct SceneSerializer<'a> { - pub scene: &'a Scene, + pub scene: &'a DynamicScene, pub registry: &'a PropertyTypeRegistry, } impl<'a> SceneSerializer<'a> { - pub fn new(scene: &'a Scene, registry: &'a PropertyTypeRegistry) -> Self { + pub fn new(scene: &'a DynamicScene, registry: &'a PropertyTypeRegistry) -> Self { SceneSerializer { scene, registry } } } @@ -86,13 +86,13 @@ pub struct SceneDeserializer<'a> { } impl<'a, 'de> DeserializeSeed<'de> for SceneDeserializer<'a> { - type Value = Scene; + type Value = DynamicScene; fn deserialize(self, deserializer: D) -> Result where D: serde::Deserializer<'de>, { - let mut scene = Scene::default(); + let mut scene = DynamicScene::default(); scene.entities = deserializer.deserialize_seq(SceneEntitySeqVisiter { property_type_registry: self.property_type_registry, })?; diff --git a/crates/bevy_sprite/src/color_material.rs b/crates/bevy_sprite/src/color_material.rs index 975515966647e..28a618b03b2fb 100644 --- a/crates/bevy_sprite/src/color_material.rs +++ b/crates/bevy_sprite/src/color_material.rs @@ -1,7 +1,9 @@ use bevy_asset::{self, Handle}; use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture}; +use bevy_type_registry::TypeUuid; -#[derive(Debug, RenderResources, ShaderDefs)] +#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)] +#[uuid = "506cff92-a9f3-4543-862d-6851c7fdfc99"] pub struct ColorMaterial { pub color: Color, #[shader_def] diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index e2abe5a36f135..cea0da2285d48 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -33,12 +33,13 @@ use bevy_render::{ render_graph::RenderGraph, shader::asset_shader_defs_system, }; +use bevy_type_registry::TypeUuid; use sprite::sprite_system; #[derive(Default)] pub struct SpritePlugin; -pub const QUAD_HANDLE: Handle = Handle::from_u128(142404619811301375266013514540294236421); +pub const QUAD_HANDLE: Handle = Handle::weak_from_u64(Mesh::TYPE_UUID, 14240461981130137526); impl Plugin for SpritePlugin { fn build(&self, app: &mut AppBuilder) { @@ -50,18 +51,18 @@ impl Plugin for SpritePlugin { asset_shader_defs_system::.system(), ); - let resources = app.resources(); + let resources = app.resources_mut(); let mut render_graph = resources.get_mut::().unwrap(); render_graph.add_sprite_graph(resources); let mut meshes = resources.get_mut::>().unwrap(); - meshes.set( + + let mut color_materials = resources.get_mut::>().unwrap(); + color_materials.set_untracked(Handle::::default(), ColorMaterial::default()); + meshes.set_untracked( QUAD_HANDLE, // Use a flipped quad because the camera is facing "forward" but quads should face backward Mesh::from(shape::Quad::new(Vec2::new(1.0, 1.0))), - ); - - let mut color_materials = resources.get_mut::>().unwrap(); - color_materials.add_default(ColorMaterial::default()); + ) } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 8c17a794a1d11..c4cd96a5d3dad 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -11,12 +11,13 @@ use bevy_render::{ shader::{Shader, ShaderStage, ShaderStages}, texture::TextureFormat, }; +use bevy_type_registry::TypeUuid; pub const SPRITE_PIPELINE_HANDLE: Handle = - Handle::from_u128(278534784033876544639935131272264723170); + Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 2785347840338765446); pub const SPRITE_SHEET_PIPELINE_HANDLE: Handle = - Handle::from_u128(90168858051802816124217444474933884151); + Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 9016885805180281612); pub fn build_sprite_sheet_pipeline(shaders: &mut Assets) -> PipelineDescriptor { PipelineDescriptor { @@ -150,8 +151,8 @@ impl SpriteRenderGraphBuilder for RenderGraph { let mut pipelines = resources.get_mut::>().unwrap(); let mut shaders = resources.get_mut::>().unwrap(); - pipelines.set(SPRITE_PIPELINE_HANDLE, build_sprite_pipeline(&mut shaders)); - pipelines.set( + pipelines.set_untracked(SPRITE_PIPELINE_HANDLE, build_sprite_pipeline(&mut shaders)); + pipelines.set_untracked( SPRITE_SHEET_PIPELINE_HANDLE, build_sprite_sheet_pipeline(&mut shaders), ); diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 49b40432680ca..048123b0c1352 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -3,8 +3,10 @@ use bevy_asset::{Assets, Handle}; use bevy_ecs::{Query, Res}; use bevy_math::Vec2; use bevy_render::{renderer::RenderResources, texture::Texture}; +use bevy_type_registry::TypeUuid; -#[derive(Debug, Default, RenderResources)] +#[derive(Debug, Default, RenderResources, TypeUuid)] +#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"] pub struct Sprite { pub size: Vec2, #[render_resources(ignore)] @@ -43,9 +45,9 @@ pub fn sprite_system( match sprite.resize_mode { SpriteResizeMode::Manual => continue, SpriteResizeMode::Automatic => { - let material = materials.get(&handle).unwrap(); - if let Some(texture_handle) = material.texture { - if let Some(texture) = textures.get(&texture_handle) { + let material = materials.get(handle).unwrap(); + if let Some(ref texture_handle) = material.texture { + if let Some(texture) = textures.get(texture_handle) { sprite.size = texture.size; } } diff --git a/crates/bevy_sprite/src/texture_atlas.rs b/crates/bevy_sprite/src/texture_atlas.rs index 2cee26d7db4fc..e798c61e1e3d8 100644 --- a/crates/bevy_sprite/src/texture_atlas.rs +++ b/crates/bevy_sprite/src/texture_atlas.rs @@ -7,10 +7,12 @@ use bevy_render::{ renderer::{RenderResource, RenderResources}, texture::Texture, }; +use bevy_type_registry::TypeUuid; use bevy_utils::HashMap; /// An atlas containing multiple textures (like a spritesheet or a tilemap) -#[derive(Debug, RenderResources)] +#[derive(Debug, RenderResources, TypeUuid)] +#[uuid = "946dacc5-c2b2-4b30-b81d-af77d79d1db7"] pub struct TextureAtlas { /// The handle to the texture in which the sprites are stored pub texture: Handle, @@ -138,9 +140,9 @@ impl TextureAtlas { self.textures.is_empty() } - pub fn get_texture_index(&self, texture: Handle) -> Option { + pub fn get_texture_index(&self, texture: &Handle) -> Option { self.texture_handles .as_ref() - .and_then(|texture_handles| texture_handles.get(&texture).cloned()) + .and_then(|texture_handles| texture_handles.get(texture).cloned()) } } diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index d7a3c6b0c6d02..7c104953b308a 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -123,7 +123,7 @@ impl TextureAtlasBuilder { packed_location.width() as f32, packed_location.height() as f32, ); - texture_handles.insert(*texture_handle, texture_rects.len()); + texture_handles.insert(texture_handle.clone_weak(), texture_rects.len()); texture_rects.push(Rect { min, max }); self.place_texture(&mut atlas_texture, texture, packed_location); } diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index f4a95b74e565e..789ca95951ed5 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -20,6 +20,7 @@ bevy_core = { path = "../bevy_core", version = "0.2.1" } bevy_math = { path = "../bevy_math", version = "0.2.1" } bevy_render = { path = "../bevy_render", version = "0.2.1" } bevy_sprite = { path = "../bevy_sprite", version = "0.2.1" } +bevy_type_registry = { path = "../bevy_type_registry", version = "0.2.1" } bevy_utils = { path = "../bevy_utils", version = "0.2.1" } # other diff --git a/crates/bevy_text/src/draw.rs b/crates/bevy_text/src/draw.rs index dc8071923cbec..e09410b6642b1 100644 --- a/crates/bevy_text/src/draw.rs +++ b/crates/bevy_text/src/draw.rs @@ -47,7 +47,7 @@ impl<'a> Drawable for DrawableText<'a> { fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> { context.set_pipeline( draw, - bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE, + &bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE, &PipelineSpecialization { sample_count: self.msaa.samples, ..Default::default() @@ -56,13 +56,13 @@ impl<'a> Drawable for DrawableText<'a> { let render_resource_context = &**context.render_resource_context; if let Some(RenderResourceId::Buffer(quad_vertex_buffer)) = render_resource_context - .get_asset_resource(bevy_sprite::QUAD_HANDLE, mesh::VERTEX_BUFFER_ASSET_INDEX) + .get_asset_resource(&bevy_sprite::QUAD_HANDLE, mesh::VERTEX_BUFFER_ASSET_INDEX) { draw.set_vertex_buffer(0, quad_vertex_buffer, 0); } let mut indices = 0..0; if let Some(RenderResourceId::Buffer(quad_index_buffer)) = render_resource_context - .get_asset_resource(bevy_sprite::QUAD_HANDLE, mesh::INDEX_BUFFER_ASSET_INDEX) + .get_asset_resource(&bevy_sprite::QUAD_HANDLE, mesh::INDEX_BUFFER_ASSET_INDEX) { draw.set_index_buffer(quad_index_buffer, 0); if let Some(buffer_info) = render_resource_context.get_buffer_info(quad_index_buffer) { @@ -111,7 +111,7 @@ impl<'a> Drawable for DrawableText<'a> { let glyph_height = glyph_rect.height(); let atlas_render_resource_bindings = self .asset_render_resource_bindings - .get_mut(glyph_atlas_info.texture_atlas) + .get_mut(&glyph_atlas_info.texture_atlas) .unwrap(); context.set_bind_groups_from_bindings( draw, diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index d7afae8f6bd48..e89cd698ad1a6 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -4,8 +4,10 @@ use bevy_render::{ color::Color, texture::{Texture, TextureFormat}, }; +use bevy_type_registry::TypeUuid; -#[derive(Debug)] +#[derive(Debug, TypeUuid)] +#[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"] pub struct Font { pub font: FontVec, } diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index d45f3b0d28c0a..cc6f30b452555 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -5,12 +5,14 @@ use bevy_core::FloatOrd; use bevy_math::Vec2; use bevy_render::texture::Texture; use bevy_sprite::TextureAtlas; +use bevy_type_registry::TypeUuid; use bevy_utils::HashMap; // work around rust's f32 order/hash limitations type FontSizeKey = FloatOrd; -#[derive(Default)] +#[derive(Default, TypeUuid)] +#[uuid = "73ba778b-b6b5-4f45-982d-d21b6b86ace2"] pub struct FontAtlasSet { font: Handle, font_atlases: HashMap>, @@ -117,7 +119,7 @@ impl FontAtlasSet { .find_map(|atlas| { atlas .get_char_index(character) - .map(|char_index| (char_index, atlas.texture_atlas)) + .map(|char_index| (char_index, atlas.texture_atlas.clone_weak())) }) .map(|(char_index, texture_atlas)| GlyphAtlasInfo { texture_atlas, diff --git a/crates/bevy_text/src/font_loader.rs b/crates/bevy_text/src/font_loader.rs index c1e8db7d9f4cf..41a1503e785cc 100644 --- a/crates/bevy_text/src/font_loader.rs +++ b/crates/bevy_text/src/font_loader.rs @@ -1,14 +1,15 @@ use crate::Font; use anyhow::Result; -use bevy_asset::AssetLoader; -use std::path::Path; +use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; #[derive(Default)] pub struct FontLoader; -impl AssetLoader for FontLoader { - fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { - Ok(Font::try_from_bytes(bytes)?) +impl AssetLoader for FontLoader { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<()> { + let font = Font::try_from_bytes(bytes.into())?; + load_context.set_default_asset(LoadedAsset::new(font)); + Ok(()) } fn extensions(&self) -> &[&str] { diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index d74d0164e5855..44d4d5c2841ed 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -24,6 +24,6 @@ impl Plugin for TextPlugin { fn build(&self, app: &mut AppBuilder) { app.add_asset::() .add_asset::() - .add_asset_loader::(); + .init_asset_loader::(); } } diff --git a/crates/bevy_transform/src/components/children.rs b/crates/bevy_transform/src/components/children.rs index 9868bbf9cfd63..5b08a41b48f6d 100644 --- a/crates/bevy_transform/src/components/children.rs +++ b/crates/bevy_transform/src/components/children.rs @@ -1,4 +1,4 @@ -use bevy_ecs::Entity; +use bevy_ecs::{Entity, MapEntities}; use bevy_property::Properties; use smallvec::SmallVec; use std::ops::{Deref, DerefMut}; @@ -6,6 +6,19 @@ use std::ops::{Deref, DerefMut}; #[derive(Default, Clone, Properties, Debug)] pub struct Children(pub SmallVec<[Entity; 8]>); +impl MapEntities for Children { + fn map_entities( + &mut self, + entity_map: &bevy_ecs::EntityMap, + ) -> Result<(), bevy_ecs::MapEntitiesError> { + for entity in self.0.iter_mut() { + *entity = entity_map.get(*entity)?; + } + + Ok(()) + } +} + impl Children { pub fn with(entity: &[Entity]) -> Self { Self(SmallVec::from_slice(entity)) diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 71eb9af3e0a7b..4292372081e7c 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -56,12 +56,6 @@ impl GlobalTransform { } } - /// Returns transform with the same translation and scale, but rotation so that transform.forward() points at the origin - #[inline] - pub fn looking_at_origin(self) -> Self { - self.looking_at(Vec3::zero(), Vec3::unit_y()) - } - /// Returns transform with the same translation and scale, but rotation so that transform.forward() points at target #[inline] pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self { diff --git a/crates/bevy_transform/src/components/parent.rs b/crates/bevy_transform/src/components/parent.rs index 05f30b4a43f9d..c18b5342bfbec 100644 --- a/crates/bevy_transform/src/components/parent.rs +++ b/crates/bevy_transform/src/components/parent.rs @@ -1,4 +1,4 @@ -use bevy_ecs::{Entity, FromResources}; +use bevy_ecs::{Entity, FromResources, MapEntities}; use bevy_property::Properties; use std::ops::{Deref, DerefMut}; @@ -15,6 +15,16 @@ impl FromResources for Parent { } } +impl MapEntities for Parent { + fn map_entities( + &mut self, + entity_map: &bevy_ecs::EntityMap, + ) -> Result<(), bevy_ecs::MapEntitiesError> { + self.0 = entity_map.get(self.0)?; + Ok(()) + } +} + #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct PreviousParent(pub Option); diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index 70002bbe2674c..85876ff6005af 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -56,12 +56,6 @@ impl Transform { } } - /// Returns transform with the same translation and scale, but rotation so that transform.forward() points at the origin - #[inline] - pub fn looking_at_origin(self) -> Self { - self.looking_at(Vec3::zero(), Vec3::unit_y()) - } - /// Returns transform with the same translation and scale, but rotation so that transform.forward() points at target #[inline] pub fn looking_at(mut self, target: Vec3, up: Vec3) -> Self { diff --git a/crates/bevy_transform/src/hierarchy/child_builder.rs b/crates/bevy_transform/src/hierarchy/child_builder.rs index 5fdd0410b9032..db77fe474527e 100644 --- a/crates/bevy_transform/src/hierarchy/child_builder.rs +++ b/crates/bevy_transform/src/hierarchy/child_builder.rs @@ -1,5 +1,7 @@ use crate::prelude::{Children, Parent, PreviousParent}; -use bevy_ecs::{Commands, CommandsInternal, Component, DynamicBundle, Entity, WorldWriter}; +use bevy_ecs::{ + Command, Commands, CommandsInternal, Component, DynamicBundle, Entity, Resources, World, +}; use smallvec::SmallVec; #[derive(Debug)] @@ -9,8 +11,8 @@ pub struct InsertChildren { index: usize, } -impl WorldWriter for InsertChildren { - fn write(self: Box, world: &mut bevy_ecs::World) { +impl Command for InsertChildren { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { for child in self.children.iter() { world .insert( @@ -42,14 +44,13 @@ pub struct PushChildren { children: SmallVec<[Entity; 8]>, } -#[derive(Debug)] pub struct ChildBuilder<'a> { commands: &'a mut CommandsInternal, push_children: PushChildren, } -impl WorldWriter for PushChildren { - fn write(self: Box, world: &mut bevy_ecs::World) { +impl Command for PushChildren { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { for child in self.children.iter() { world .insert( @@ -136,7 +137,7 @@ impl BuildChildren for Commands { }; commands.current_entity = Some(current_entity); - commands.write_world(push_children); + commands.add_command(push_children); } self } @@ -144,7 +145,7 @@ impl BuildChildren for Commands { fn push_children(&mut self, parent: Entity, children: &[Entity]) -> &mut Self { { let mut commands = self.commands.lock(); - commands.write_world(PushChildren { + commands.add_command(PushChildren { children: SmallVec::from(children), parent, }); @@ -155,7 +156,7 @@ impl BuildChildren for Commands { fn insert_children(&mut self, parent: Entity, index: usize, children: &[Entity]) -> &mut Self { { let mut commands = self.commands.lock(); - commands.write_world(InsertChildren { + commands.add_command(InsertChildren { children: SmallVec::from(children), index, parent, @@ -183,12 +184,12 @@ impl<'a> BuildChildren for ChildBuilder<'a> { }; self.commands.current_entity = Some(current_entity); - self.commands.write_world(push_children); + self.commands.add_command(push_children); self } fn push_children(&mut self, parent: Entity, children: &[Entity]) -> &mut Self { - self.commands.write_world(PushChildren { + self.commands.add_command(PushChildren { children: SmallVec::from(children), parent, }); @@ -196,7 +197,7 @@ impl<'a> BuildChildren for ChildBuilder<'a> { } fn insert_children(&mut self, parent: Entity, index: usize, children: &[Entity]) -> &mut Self { - self.commands.write_world(InsertChildren { + self.commands.add_command(InsertChildren { children: SmallVec::from(children), index, parent, diff --git a/crates/bevy_transform/src/hierarchy/hierarchy.rs b/crates/bevy_transform/src/hierarchy/hierarchy.rs index 089b9fe9e6e5b..facd85b38f92c 100644 --- a/crates/bevy_transform/src/hierarchy/hierarchy.rs +++ b/crates/bevy_transform/src/hierarchy/hierarchy.rs @@ -1,5 +1,5 @@ use crate::components::{Children, Parent}; -use bevy_ecs::{Commands, Entity, Query, World, WorldWriter}; +use bevy_ecs::{Command, Commands, Entity, Query, Resources, World}; pub fn run_on_hierarchy( children_query: &Query<&Children>, @@ -72,8 +72,8 @@ fn despawn_with_children_recursive_inner(world: &mut World, entity: Entity) { } } -impl WorldWriter for DespawnRecursive { - fn write(self: Box, world: &mut World) { +impl Command for DespawnRecursive { + fn write(self: Box, world: &mut World, _resources: &mut Resources) { despawn_with_children_recursive(world, self.entity); } } @@ -86,7 +86,7 @@ pub trait DespawnRecursiveExt { impl DespawnRecursiveExt for Commands { /// Despawns the provided entity and its children. fn despawn_recursive(&mut self, entity: Entity) -> &mut Self { - self.write_world(DespawnRecursive { entity }) + self.add_command(DespawnRecursive { entity }) } } diff --git a/crates/bevy_transform/src/hierarchy/world_child_builder.rs b/crates/bevy_transform/src/hierarchy/world_child_builder.rs index 119deb7113094..98fa07ee0ce75 100644 --- a/crates/bevy_transform/src/hierarchy/world_child_builder.rs +++ b/crates/bevy_transform/src/hierarchy/world_child_builder.rs @@ -48,6 +48,10 @@ impl<'a, 'b> WorldChildBuilder<'a, 'b> { self.world_builder.with(component); self } + + pub fn current_entity(&self) -> Option { + self.world_builder.current_entity + } } pub trait BuildWorldChildren { diff --git a/crates/bevy_transform/src/lib.rs b/crates/bevy_transform/src/lib.rs index 90ddb3ac8bafe..98582a8b0d205 100644 --- a/crates/bevy_transform/src/lib.rs +++ b/crates/bevy_transform/src/lib.rs @@ -9,7 +9,7 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_type_registry::RegisterType; -use prelude::{Children, Parent, Transform}; +use prelude::{Children, GlobalTransform, Parent, Transform}; pub(crate) fn transform_systems() -> Vec> { let mut systems = Vec::with_capacity(5); @@ -25,9 +25,10 @@ pub struct TransformPlugin; impl Plugin for TransformPlugin { fn build(&self, app: &mut AppBuilder) { - app.register_component::() - .register_component::() + app.register_component_with::(|reg| reg.map_entities()) + .register_component_with::(|reg| reg.map_entities()) .register_component::() + .register_component::() // add transform systems to startup so the first update is "correct" .add_startup_systems(transform_systems()) .add_systems_to_stage(stage::POST_UPDATE, transform_systems()); diff --git a/crates/bevy_type_registry/Cargo.toml b/crates/bevy_type_registry/Cargo.toml index 900854080b37c..ca1a396c14220 100644 --- a/crates/bevy_type_registry/Cargo.toml +++ b/crates/bevy_type_registry/Cargo.toml @@ -15,10 +15,12 @@ keywords = ["bevy"] [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.2.1" } +bevy_derive = { path = "../bevy_derive", version = "0.2.1" } bevy_ecs = { path = "../bevy_ecs", version = "0.2.1" } bevy_property = { path = "../bevy_property", version = "0.2.1" } bevy_utils = { path = "../bevy_utils", version = "0.2.1" } # other +uuid = { version = "0.8", features = ["v4", "serde"] } serde = { version = "1", features = ["derive"] } parking_lot = "0.11.0" diff --git a/crates/bevy_type_registry/src/lib.rs b/crates/bevy_type_registry/src/lib.rs index 9b84ae1b43ab8..2f0fbe88cea53 100644 --- a/crates/bevy_type_registry/src/lib.rs +++ b/crates/bevy_type_registry/src/lib.rs @@ -1,8 +1,11 @@ mod register_type; mod type_registry; +mod type_uuid; pub use register_type::*; pub use type_registry::*; +pub use type_uuid::*; +pub use uuid::Uuid; use bevy_app::prelude::*; use bevy_property::DynamicProperties; diff --git a/crates/bevy_type_registry/src/register_type.rs b/crates/bevy_type_registry/src/register_type.rs index 1e03a83d2a76a..40f1258a8f9ff 100644 --- a/crates/bevy_type_registry/src/register_type.rs +++ b/crates/bevy_type_registry/src/register_type.rs @@ -1,10 +1,16 @@ -use crate::TypeRegistry; +use crate::{ComponentRegistration, ComponentRegistrationBuilder, TypeRegistry}; use bevy_app::AppBuilder; use bevy_ecs::{Component, FromResources}; use bevy_property::{DeserializeProperty, Properties, Property}; pub trait RegisterType { fn register_component(&mut self) -> &mut Self + where + T: Properties + DeserializeProperty + Component + FromResources; + fn register_component_with( + &mut self, + build: fn(ComponentRegistrationBuilder) -> ComponentRegistrationBuilder, + ) -> &mut Self where T: Properties + DeserializeProperty + Component + FromResources; fn register_properties(&mut self) -> &mut Self @@ -49,4 +55,24 @@ impl RegisterType for AppBuilder { } self } + + fn register_component_with( + &mut self, + build: fn(ComponentRegistrationBuilder) -> ComponentRegistrationBuilder, + ) -> &mut Self + where + T: Properties + DeserializeProperty + Component + FromResources, + { + { + let mut builder = ComponentRegistration::build::(); + builder = build(builder); + let type_registry = self.app.resources.get::().unwrap(); + type_registry + .component + .write() + .add_registration(builder.finish()); + type_registry.property.write().register::(); + } + self + } } diff --git a/crates/bevy_type_registry/src/type_registry.rs b/crates/bevy_type_registry/src/type_registry.rs index 2e6485da1da5c..494612dd575bd 100644 --- a/crates/bevy_type_registry/src/type_registry.rs +++ b/crates/bevy_type_registry/src/type_registry.rs @@ -1,8 +1,13 @@ -use bevy_ecs::{Archetype, Component, Entity, FromResources, Resources, World}; -use bevy_property::{Properties, Property, PropertyTypeRegistration, PropertyTypeRegistry}; +use bevy_ecs::{ + Archetype, Component, Entity, EntityMap, FromResources, MapEntities, MapEntitiesError, + Resources, World, +}; +use bevy_property::{ + DeserializeProperty, Properties, Property, PropertyTypeRegistration, PropertyTypeRegistry, +}; use bevy_utils::{HashMap, HashSet}; use parking_lot::RwLock; -use std::{any::TypeId, sync::Arc}; +use std::{any::TypeId, marker::PhantomData, sync::Arc}; #[derive(Clone, Default)] pub struct TypeRegistry { @@ -23,7 +28,10 @@ impl ComponentRegistry { where T: Properties + Component + FromResources, { - let registration = ComponentRegistration::of::(); + self.add_registration(ComponentRegistration::of::()); + } + + pub fn add_registration(&mut self, registration: ComponentRegistration) { let short_name = registration.short_name.to_string(); self.full_names .insert(registration.long_name.to_string(), registration.ty); @@ -63,48 +71,108 @@ impl ComponentRegistry { } registration } + + pub fn iter(&self) -> impl Iterator { + self.registrations.values() + } } #[derive(Clone)] pub struct ComponentRegistration { pub ty: TypeId, + pub short_name: String, + pub long_name: &'static str, component_add_fn: fn(&mut World, resources: &Resources, Entity, &dyn Property), component_apply_fn: fn(&mut World, Entity, &dyn Property), component_properties_fn: fn(&Archetype, usize) -> &dyn Properties, - pub short_name: String, - pub long_name: &'static str, + component_copy_fn: fn(&World, &mut World, &Resources, Entity, Entity), + copy_to_scene_fn: fn(&World, &mut World, &Resources, Entity, Entity), + copy_from_scene_fn: fn(&World, &mut World, &Resources, Entity, Entity), + map_entities_fn: fn(&mut World, &EntityMap) -> Result<(), MapEntitiesError>, +} + +struct ComponentRegistrationDefaults; + +impl ComponentRegistrationDefaults { + pub fn component_add( + world: &mut World, + resources: &Resources, + entity: Entity, + property: &dyn Property, + ) { + let mut component = T::from_resources(resources); + component.apply(property); + world.insert_one(entity, component).unwrap(); + } + + fn component_apply( + world: &mut World, + entity: Entity, + property: &dyn Property, + ) { + let mut component = world.get_mut::(entity).unwrap(); + component.apply(property); + } + + fn component_copy( + source_world: &World, + destination_world: &mut World, + resources: &Resources, + source_entity: Entity, + destination_entity: Entity, + ) { + let source_component = source_world.get::(source_entity).unwrap(); + let mut destination_component = T::from_resources(resources); + destination_component.apply(source_component); + destination_world + .insert_one(destination_entity, destination_component) + .unwrap(); + } + + fn component_properties( + archetype: &Archetype, + index: usize, + ) -> &dyn Properties { + // the type has been looked up by the caller, so this is safe + unsafe { + let ptr = archetype.get::().unwrap().as_ptr().add(index); + ptr.as_ref().unwrap() + } + } + + fn map_entities(_world: &mut World, _entity_map: &EntityMap) -> Result<(), MapEntitiesError> { + Ok(()) + } } impl ComponentRegistration { + pub fn build() -> ComponentRegistrationBuilder + where + T: Properties + DeserializeProperty + Component + FromResources, + { + ComponentRegistrationBuilder { + registration: ComponentRegistration::of::(), + marker: PhantomData::default(), + } + } + pub fn of() -> Self { let ty = TypeId::of::(); Self { ty, - component_add_fn: |world: &mut World, - resources: &Resources, - entity: Entity, - property: &dyn Property| { - let mut component = T::from_resources(resources); - component.apply(property); - world.insert_one(entity, component).unwrap(); - }, - component_apply_fn: |world: &mut World, entity: Entity, property: &dyn Property| { - let mut component = world.get_mut::(entity).unwrap(); - component.apply(property); - }, - component_properties_fn: |archetype: &Archetype, index: usize| { - // the type has been looked up by the caller, so this is safe - unsafe { - let ptr = archetype.get::().unwrap().as_ptr().add(index); - ptr.as_ref().unwrap() - } - }, + component_add_fn: ComponentRegistrationDefaults::component_add::, + component_apply_fn: ComponentRegistrationDefaults::component_apply::, + component_copy_fn: ComponentRegistrationDefaults::component_copy::, + component_properties_fn: ComponentRegistrationDefaults::component_properties::, + copy_from_scene_fn: ComponentRegistrationDefaults::component_copy::, + copy_to_scene_fn: ComponentRegistrationDefaults::component_copy::, + map_entities_fn: ComponentRegistrationDefaults::map_entities, short_name: PropertyTypeRegistration::get_short_name(std::any::type_name::()), long_name: std::any::type_name::(), } } - pub fn add_component_to_entity( + pub fn add_property_to_entity( &self, world: &mut World, resources: &Resources, @@ -114,7 +182,7 @@ impl ComponentRegistration { (self.component_add_fn)(world, resources, entity, property); } - pub fn apply_component_to_entity( + pub fn apply_property_to_entity( &self, world: &mut World, entity: Entity, @@ -130,4 +198,136 @@ impl ComponentRegistration { ) -> &'a dyn Properties { (self.component_properties_fn)(archetype, entity_index) } + + pub fn component_copy( + &self, + source_world: &World, + destination_world: &mut World, + resources: &Resources, + source_entity: Entity, + destination_entity: Entity, + ) { + (self.component_copy_fn)( + source_world, + destination_world, + resources, + source_entity, + destination_entity, + ); + } + + pub fn copy_from_scene( + &self, + scene_world: &World, + destination_world: &mut World, + resources: &Resources, + source_entity: Entity, + destination_entity: Entity, + ) { + (self.component_copy_fn)( + scene_world, + destination_world, + resources, + source_entity, + destination_entity, + ); + } + + pub fn copy_to_scene( + &self, + source_world: &World, + scene_world: &mut World, + resources: &Resources, + source_entity: Entity, + destination_entity: Entity, + ) { + (self.copy_to_scene_fn)( + source_world, + scene_world, + resources, + source_entity, + destination_entity, + ); + } + + pub fn map_entities( + &self, + world: &mut World, + entity_map: &EntityMap, + ) -> Result<(), MapEntitiesError> { + (self.map_entities_fn)(world, entity_map) + } +} + +pub struct ComponentRegistrationBuilder { + registration: ComponentRegistration, + marker: PhantomData, +} + +impl ComponentRegistrationBuilder +where + T: Properties + DeserializeProperty + Component + FromResources, +{ + pub fn map_entities(mut self) -> Self + where + T: MapEntities, + { + self.registration.map_entities_fn = |world: &mut World, entity_map: &EntityMap| { + // TODO: add UntrackedMut pointer that returns &mut T. This will avoid setting the "mutated" state + for mut component in &mut world.query_mut::<&mut T>() { + component.map_entities(entity_map)?; + } + + Ok(()) + }; + self + } + + pub fn into_scene_component(mut self) -> Self + where + T: IntoComponent, + { + self.registration.copy_to_scene_fn = + |source_world: &World, + scene_world: &mut World, + resources: &Resources, + source_entity: Entity, + scene_entity: Entity| { + let source_component = source_world.get::(source_entity).unwrap(); + let scene_component = source_component.into_component(resources); + scene_world + .insert_one(scene_entity, scene_component) + .unwrap(); + }; + + self + } + + pub fn into_runtime_component(mut self) -> Self + where + T: IntoComponent, + { + self.registration.copy_from_scene_fn = + |scene_world: &World, + destination_world: &mut World, + resources: &Resources, + scene_entity: Entity, + destination_entity: Entity| { + let scene_component = scene_world.get::(scene_entity).unwrap(); + let destination_component = scene_component.into_component(resources); + destination_world + .insert_one(destination_entity, destination_component) + .unwrap(); + }; + + self + } + + pub fn finish(self) -> ComponentRegistration { + self.registration + } +} + +pub trait IntoComponent { + fn into_component(&self, resources: &Resources) -> ToComponent; } diff --git a/crates/bevy_type_registry/src/type_uuid.rs b/crates/bevy_type_registry/src/type_uuid.rs new file mode 100644 index 0000000000000..28d53c3a34e28 --- /dev/null +++ b/crates/bevy_type_registry/src/type_uuid.rs @@ -0,0 +1,19 @@ +pub use bevy_derive::TypeUuid; +use uuid::Uuid; + +pub trait TypeUuid { + const TYPE_UUID: Uuid; +} + +pub trait TypeUuidDynamic { + fn type_uuid(&self) -> Uuid; +} + +impl TypeUuidDynamic for T +where + T: TypeUuid, +{ + fn type_uuid(&self) -> Uuid { + Self::TYPE_UUID + } +} diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 6263f2246d91e..1da738af8cba7 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -16,9 +16,10 @@ use bevy_render::{ shader::{Shader, ShaderStage, ShaderStages}, texture::TextureFormat, }; +use bevy_type_registry::TypeUuid; pub const UI_PIPELINE_HANDLE: Handle = - Handle::from_u128(323432002226399387835192542539754486265); + Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 3234320022263993878); pub fn build_ui_pipeline(shaders: &mut Assets) -> PipelineDescriptor { PipelineDescriptor { @@ -87,7 +88,7 @@ impl UiRenderGraphBuilder for RenderGraph { let mut pipelines = resources.get_mut::>().unwrap(); let mut shaders = resources.get_mut::>().unwrap(); let msaa = resources.get::().unwrap(); - pipelines.set(UI_PIPELINE_HANDLE, build_ui_pipeline(&mut shaders)); + pipelines.set_untracked(UI_PIPELINE_HANDLE, build_ui_pipeline(&mut shaders)); let mut ui_pass_node = PassNode::<&Node>::new(PassDescriptor { color_attachments: vec![msaa.color_attachment_descriptor( diff --git a/crates/bevy_ui/src/widget/image.rs b/crates/bevy_ui/src/widget/image.rs index 2010cf2fb065e..2772666e9c10c 100644 --- a/crates/bevy_ui/src/widget/image.rs +++ b/crates/bevy_ui/src/widget/image.rs @@ -24,8 +24,8 @@ pub fn image_node_system( for (_image, mut calculated_size, material_handle) in &mut query.iter() { if let Some(texture) = materials .get(material_handle) - .and_then(|material| material.texture) - .and_then(|texture_handle| textures.get(&texture_handle)) + .and_then(|material| material.texture.as_ref()) + .and_then(|texture_handle| textures.get(texture_handle)) { calculated_size.size = Size { width: texture.size.x(), diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index 3ac715e166495..0b9c155c8bdef 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -39,9 +39,7 @@ pub fn text_system( if let Ok(mut result) = text_query.entity(entity) { if let Some((text, mut calculated_size)) = result.get() { let font_atlases = font_atlas_sets - .get_or_insert_with(Handle::from_id(text.font.id), || { - FontAtlasSet::new(text.font) - }); + .get_or_insert_with(text.font.id, || FontAtlasSet::new(text.font.clone_weak())); // TODO: this call results in one or more TextureAtlases, whose render resources are created in the RENDER_GRAPH_SYSTEMS // stage. That logic runs _before_ the DRAW stage, which means we cant call add_glyphs_to_atlas in the draw stage // without our render resources being a frame behind. Therefore glyph atlasing either needs its own system or the TextureAtlas @@ -68,9 +66,7 @@ pub fn text_system( // add changed text to atlases for (entity, text, mut calculated_size) in &mut query.iter() { let font_atlases = font_atlas_sets - .get_or_insert_with(Handle::from_id(text.font.id), || { - FontAtlasSet::new(text.font) - }); + .get_or_insert_with(text.font.id, || FontAtlasSet::new(text.font.clone_weak())); // TODO: this call results in one or more TextureAtlases, whose render resources are created in the RENDER_GRAPH_SYSTEMS // stage. That logic runs _before_ the DRAW stage, which means we cant call add_glyphs_to_atlas in the draw stage // without our render resources being a frame behind. Therefore glyph atlasing either needs its own system or the TextureAtlas @@ -107,9 +103,7 @@ pub fn draw_text_system( let position = global_transform.translation - (node.size / 2.0).extend(0.0); let mut drawable_text = DrawableText { font, - font_atlas_set: font_atlas_sets - .get(&text.font.as_handle::()) - .unwrap(), + font_atlas_set: font_atlas_sets.get(text.font.id).unwrap(), texture_atlases: &texture_atlases, render_resource_bindings: &mut render_resource_bindings, asset_render_resource_bindings: &mut asset_render_resource_bindings, diff --git a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs index d6b1f70d176d5..554f73b80c070 100644 --- a/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs +++ b/crates/bevy_wgpu/src/renderer/wgpu_render_resource_context.rs @@ -245,16 +245,16 @@ impl RenderResourceContext for WgpuRenderResourceContext { samplers.remove(&sampler); } - fn create_shader_module_from_source(&self, shader_handle: Handle, shader: &Shader) { + fn create_shader_module_from_source(&self, shader_handle: &Handle, shader: &Shader) { let mut shader_modules = self.resources.shader_modules.write(); let spirv: Cow<[u32]> = shader.get_spirv(None).into(); let shader_module = self .device .create_shader_module(wgpu::ShaderModuleSource::SpirV(spirv)); - shader_modules.insert(shader_handle, shader_module); + shader_modules.insert(shader_handle.clone_weak(), shader_module); } - fn create_shader_module(&self, shader_handle: Handle, shaders: &Assets) { + fn create_shader_module(&self, shader_handle: &Handle, shaders: &Assets) { if self .resources .shader_modules @@ -264,7 +264,7 @@ impl RenderResourceContext for WgpuRenderResourceContext { { return; } - let shader = shaders.get(&shader_handle).unwrap(); + let shader = shaders.get(shader_handle).unwrap(); self.create_shader_module_from_source(shader_handle, shader); } @@ -380,9 +380,9 @@ impl RenderResourceContext for WgpuRenderResourceContext { .map(|c| c.wgpu_into()) .collect::>(); - self.create_shader_module(pipeline_descriptor.shader_stages.vertex, shaders); + self.create_shader_module(&pipeline_descriptor.shader_stages.vertex, shaders); - if let Some(fragment_handle) = pipeline_descriptor.shader_stages.fragment { + if let Some(ref fragment_handle) = pipeline_descriptor.shader_stages.fragment { self.create_shader_module(fragment_handle, shaders); } @@ -392,7 +392,7 @@ impl RenderResourceContext for WgpuRenderResourceContext { .unwrap(); let fragment_shader_module = match pipeline_descriptor.shader_stages.fragment { - Some(fragment_handle) => Some(shader_modules.get(&fragment_handle).unwrap()), + Some(ref fragment_handle) => Some(shader_modules.get(fragment_handle).unwrap()), None => None, }; diff --git a/crates/bevy_wgpu/src/wgpu_render_pass.rs b/crates/bevy_wgpu/src/wgpu_render_pass.rs index 66f1c15518c43..a27ff795f8942 100644 --- a/crates/bevy_wgpu/src/wgpu_render_pass.rs +++ b/crates/bevy_wgpu/src/wgpu_render_pass.rs @@ -82,11 +82,11 @@ impl<'a> RenderPass for WgpuRenderPass<'a> { } } - fn set_pipeline(&mut self, pipeline_handle: Handle) { + fn set_pipeline(&mut self, pipeline_handle: &Handle) { let pipeline = self .wgpu_resources .render_pipelines - .get(&pipeline_handle) + .get(pipeline_handle) .expect( "Attempted to use a pipeline that does not exist in this RenderPass's RenderContext", ); diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 7d2212667cc30..bcbc720299e60 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -214,7 +214,6 @@ impl Window { } #[derive(Debug, Clone)] -#[allow(clippy::manual_non_exhaustive)] pub struct WindowDescriptor { pub width: u32, pub height: u32, @@ -227,11 +226,6 @@ pub struct WindowDescriptor { pub mode: WindowMode, #[cfg(target_arch = "wasm32")] pub canvas: Option, - - // this is a manual implementation of the non exhaustive pattern, - // especially made to allow ..Default::default() - #[doc(hidden)] - pub __non_exhaustive: (), } impl Default for WindowDescriptor { @@ -248,7 +242,6 @@ impl Default for WindowDescriptor { mode: WindowMode::Windowed, #[cfg(target_arch = "wasm32")] canvas: None, - __non_exhaustive: (), } } } diff --git a/examples/2d/sprite.rs b/examples/2d/sprite.rs index d91caefc21282..ac7ee64e88628 100644 --- a/examples/2d/sprite.rs +++ b/examples/2d/sprite.rs @@ -12,7 +12,7 @@ fn setup( asset_server: Res, mut materials: ResMut>, ) { - let texture_handle = asset_server.load("assets/branding/icon.png").unwrap(); + let texture_handle = asset_server.load("branding/icon.png"); commands .spawn(Camera2dComponents::default()) .spawn(SpriteComponents { diff --git a/examples/2d/sprite_sheet.rs b/examples/2d/sprite_sheet.rs index a47c7476bc3a6..1d4e38f5512e7 100644 --- a/examples/2d/sprite_sheet.rs +++ b/examples/2d/sprite_sheet.rs @@ -14,7 +14,7 @@ fn animate_sprite_system( ) { for (timer, mut sprite, texture_atlas_handle) in &mut query.iter() { if timer.finished { - let texture_atlas = texture_atlases.get(&texture_atlas_handle).unwrap(); + let texture_atlas = texture_atlases.get(texture_atlas_handle).unwrap(); sprite.index = ((sprite.index as usize + 1) % texture_atlas.textures.len()) as u32; } } @@ -23,16 +23,9 @@ fn animate_sprite_system( fn setup( mut commands: Commands, asset_server: Res, - mut textures: ResMut>, mut texture_atlases: ResMut>, ) { - let texture_handle = asset_server - .load_sync( - &mut textures, - "assets/textures/rpg/chars/gabe/gabe-idle-run.png", - ) - .unwrap(); - + let texture_handle = asset_server.load("textures/rpg/chars/gabe/gabe-idle-run.png"); let texture_atlas = TextureAtlas::from_grid(texture_handle, Vec2::new(24.0, 24.0), 7, 1); let texture_atlas_handle = texture_atlases.add(texture_atlas); commands diff --git a/examples/2d/texture_atlas.rs b/examples/2d/texture_atlas.rs index fa9a95078f655..7ced5a4924d6b 100644 --- a/examples/2d/texture_atlas.rs +++ b/examples/2d/texture_atlas.rs @@ -1,8 +1,4 @@ -use bevy::{ - asset::{HandleId, LoadState}, - prelude::*, - sprite::TextureAtlasBuilder, -}; +use bevy::{asset::LoadState, prelude::*, sprite::TextureAtlasBuilder}; /// In this example we generate a new texture atlas (sprite sheet) from a folder containing individual sprites fn main() { @@ -16,14 +12,12 @@ fn main() { #[derive(Default)] pub struct RpgSpriteHandles { - handles: Vec, + handles: Vec, atlas_loaded: bool, } fn setup(mut rpg_sprite_handles: ResMut, asset_server: Res) { - rpg_sprite_handles.handles = asset_server - .load_asset_folder("assets/textures/rpg") - .unwrap(); + rpg_sprite_handles.handles = asset_server.load_folder("textures/rpg").unwrap(); } fn load_atlas( @@ -39,21 +33,19 @@ fn load_atlas( } let mut texture_atlas_builder = TextureAtlasBuilder::default(); - if let Some(LoadState::Loaded(_)) = - asset_server.get_group_load_state(&rpg_sprite_handles.handles) + if let LoadState::Loaded = + asset_server.get_group_load_state(rpg_sprite_handles.handles.iter().map(|handle| handle.id)) { - for texture_id in rpg_sprite_handles.handles.iter() { - let handle = Handle::from_id(*texture_id); - let texture = textures.get(&handle).unwrap(); - texture_atlas_builder.add_texture(handle, &texture); + for handle in rpg_sprite_handles.handles.iter() { + let texture = textures.get(handle).unwrap(); + texture_atlas_builder.add_texture(handle.clone_weak().typed::(), &texture); } let texture_atlas = texture_atlas_builder.finish(&mut textures).unwrap(); - let texture_atlas_texture = texture_atlas.texture; - let vendor_handle = asset_server - .get_handle("assets/textures/rpg/chars/vendor/generic-rpg-vendor.png") - .unwrap(); - let vendor_index = texture_atlas.get_texture_index(vendor_handle).unwrap(); + let texture_atlas_texture = texture_atlas.texture.clone(); + let vendor_handle = + asset_server.get_handle("textures/rpg/chars/vendor/generic-rpg-vendor.png"); + let vendor_index = texture_atlas.get_texture_index(&vendor_handle).unwrap(); let atlas_handle = texture_atlases.add(texture_atlas); // set up a scene to display our texture atlas diff --git a/examples/3d/3d_scene.rs b/examples/3d/3d_scene.rs index 933afc28026b0..51b73ca6425bd 100644 --- a/examples/3d/3d_scene.rs +++ b/examples/3d/3d_scene.rs @@ -36,7 +36,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(-3.0, 5.0, 8.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(-3.0, 5.0, 8.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/3d/load_gltf.rs b/examples/3d/load_gltf.rs new file mode 100644 index 0000000000000..c09517829640e --- /dev/null +++ b/examples/3d/load_gltf.rs @@ -0,0 +1,23 @@ +use bevy::prelude::*; + +fn main() { + App::build() + .add_resource(Msaa { samples: 4 }) + .add_default_plugins() + .add_startup_system(setup.system()) + .run(); +} + +fn setup(mut commands: Commands, asset_server: Res) { + commands + .spawn_scene(asset_server.load("models/FlightHelmet/FlightHelmet.gltf")) + .spawn(LightComponents { + transform: Transform::from_translation(Vec3::new(4.0, 5.0, 4.0)), + ..Default::default() + }) + .spawn(Camera3dComponents { + transform: Transform::from_translation(Vec3::new(0.7, 0.7, 1.0)) + .looking_at(Vec3::new(0.0, 0.3, 0.0), Vec3::unit_y()), + ..Default::default() + }); +} diff --git a/examples/3d/load_model.rs b/examples/3d/load_model.rs deleted file mode 100644 index d21962360699e..0000000000000 --- a/examples/3d/load_model.rs +++ /dev/null @@ -1,50 +0,0 @@ -use bevy::prelude::*; - -fn main() { - App::build() - .add_resource(Msaa { samples: 4 }) - .add_default_plugins() - .add_startup_system(setup.system()) - .run(); -} - -fn setup( - mut commands: Commands, - asset_server: Res, - mut materials: ResMut>, -) { - // add entities to the world - commands - // mesh - .spawn(PbrComponents { - // load a mesh from glTF - mesh: asset_server - .load("assets/models/monkey/Monkey.gltf") - .unwrap(), - // create a material for the mesh - material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), - transform: Transform::from_translation(Vec3::new(-1.5, 0.0, 0.0)), - ..Default::default() - }) - // mesh - .spawn(PbrComponents { - // load a mesh from binary glTF - mesh: asset_server - .load("assets/models/monkey/Monkey.glb") - .unwrap(), - // create a material for the mesh - material: materials.add(Color::rgb(0.8, 0.7, 0.6).into()), - transform: Transform::from_translation(Vec3::new(1.5, 0.0, 0.0)), - ..Default::default() - }) - // light - .spawn(LightComponents { - transform: Transform::from_translation(Vec3::new(4.0, 5.0, 4.0)), - ..Default::default() - }) - // camera - .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(-2.0, 2.0, 6.0)).looking_at_origin(), - ..Default::default() - }); -} diff --git a/examples/3d/msaa.rs b/examples/3d/msaa.rs index 629ef104c34b5..87a07ec7b8b0c 100644 --- a/examples/3d/msaa.rs +++ b/examples/3d/msaa.rs @@ -32,7 +32,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(-3.0, 3.0, 5.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(-3.0, 3.0, 5.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/3d/parenting.rs b/examples/3d/parenting.rs index 114e08599ec95..1cf5233339a6e 100644 --- a/examples/3d/parenting.rs +++ b/examples/3d/parenting.rs @@ -36,8 +36,8 @@ fn setup( commands // parent cube .spawn(PbrComponents { - mesh: cube_handle, - material: cube_material_handle, + mesh: cube_handle.clone(), + material: cube_material_handle.clone(), transform: Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), ..Default::default() }) @@ -58,7 +58,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(5.0, 10.0, 10.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(5.0, 10.0, 10.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/3d/spawner.rs b/examples/3d/spawner.rs index 68821514e5e99..d76e7ffde13e1 100644 --- a/examples/3d/spawner.rs +++ b/examples/3d/spawner.rs @@ -24,7 +24,7 @@ fn move_cubes( mut query: Query<(&mut Transform, &Handle)>, ) { for (mut transform, material_handle) in &mut query.iter() { - let material = materials.get_mut(&material_handle).unwrap(); + let material = materials.get_mut(material_handle).unwrap(); transform.translation += Vec3::new(1.0, 0.0, 0.0) * time.delta_seconds; material.albedo = Color::BLUE * Vec3::splat((3.0 * time.seconds_since_startup as f32).sin()); @@ -44,7 +44,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(0.0, 15.0, 150.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(0.0, 15.0, 150.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); @@ -52,7 +53,7 @@ fn setup( let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 1.0 })); for _ in 0..10000 { commands.spawn(PbrComponents { - mesh: cube_handle, + mesh: cube_handle.clone(), material: materials.add(StandardMaterial { albedo: Color::rgb( rng.gen_range(0.0, 1.0), diff --git a/examples/3d/texture.rs b/examples/3d/texture.rs index c4ea97b6d80e5..dcfbff0c622ad 100644 --- a/examples/3d/texture.rs +++ b/examples/3d/texture.rs @@ -13,15 +13,11 @@ fn setup( mut commands: Commands, asset_server: Res, mut meshes: ResMut>, - mut textures: ResMut>, mut materials: ResMut>, ) { // load a texture and retrieve its aspect ratio - let texture_handle = asset_server - .load_sync(&mut textures, "assets/branding/bevy_logo_dark_big.png") - .unwrap(); - let texture = textures.get(&texture_handle).unwrap(); - let aspect = texture.aspect(); + let texture_handle = asset_server.load("branding/bevy_logo_dark_big.png"); + let aspect = 0.25; // create a new quad mesh. this is what we will apply the texture to let quad_width = 8.0; @@ -32,7 +28,7 @@ fn setup( // this material renders the texture normally let material_handle = materials.add(StandardMaterial { - albedo_texture: Some(texture_handle), + albedo_texture: Some(texture_handle.clone()), shaded: false, ..Default::default() }); @@ -40,9 +36,8 @@ fn setup( // this material modulates the texture to make it red (and slightly transparent) let red_material_handle = materials.add(StandardMaterial { albedo: Color::rgba(1.0, 0.0, 0.0, 0.5), - albedo_texture: Some(texture_handle), + albedo_texture: Some(texture_handle.clone()), shaded: false, - ..Default::default() }); // and lets make this one blue! (and also slightly transparent) @@ -50,14 +45,13 @@ fn setup( albedo: Color::rgba(0.0, 0.0, 1.0, 0.5), albedo_texture: Some(texture_handle), shaded: false, - ..Default::default() }); // add entities to the world commands // textured quad - normal .spawn(PbrComponents { - mesh: quad_handle, + mesh: quad_handle.clone(), material: material_handle, transform: Transform { translation: Vec3::new(0.0, 0.0, 1.5), @@ -72,7 +66,7 @@ fn setup( }) // textured quad - modulated .spawn(PbrComponents { - mesh: quad_handle, + mesh: quad_handle.clone(), material: red_material_handle, transform: Transform { translation: Vec3::new(0.0, 0.0, 0.0), @@ -102,7 +96,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(3.0, 5.0, 8.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(3.0, 5.0, 8.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/3d/z_sort_debug.rs b/examples/3d/z_sort_debug.rs index 4dfd44b7ea674..ad98326230d99 100644 --- a/examples/3d/z_sort_debug.rs +++ b/examples/3d/z_sort_debug.rs @@ -35,7 +35,7 @@ fn camera_order_color_system( if let Ok(material_handle) = material_query.get::>(visible_entity.entity) { - let material = materials.get_mut(&material_handle).unwrap(); + let material = materials.get_mut(&*material_handle).unwrap(); let value = 1.0 - (visible_entity.order.0 - 10.0) / 7.0; material.albedo = Color::rgb(value, value, value); } @@ -52,7 +52,7 @@ fn setup( commands // parent cube .spawn(PbrComponents { - mesh: cube_handle, + mesh: cube_handle.clone(), material: materials.add(StandardMaterial { shaded: false, ..Default::default() @@ -65,7 +65,7 @@ fn setup( // child cubes parent .spawn(PbrComponents { - mesh: cube_handle, + mesh: cube_handle.clone(), material: materials.add(StandardMaterial { shaded: false, ..Default::default() @@ -85,7 +85,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(5.0, 10.0, 10.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(5.0, 10.0, 10.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/README.md b/examples/README.md index 48ede21f9d878..9e206f68520cb 100644 --- a/examples/README.md +++ b/examples/README.md @@ -24,7 +24,7 @@ Example | Main | Description Example | File | Description --- | --- | --- -`load_model` | [`3d/load_model.rs`](./3d/load_model.rs) | Loads and renders a simple model +`load_gltf` | [`3d/load_gltf.rs`](./3d/load_gltf.rs) | Loads and renders a gltf file as a scene `msaa` | [`3d/msaa.rs`](./3d/msaa.rs) | Configures MSAA (Multi-Sample Anti-Aliasing) for smoother edges `parenting` | [`3d/parenting.rs`](./3d/parenting.rs) | Demonstrates parent->child relationships and relative transformations `3d_scene` | [`3d/3d_scene.rs`](./3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting diff --git a/examples/asset/asset_loading.rs b/examples/asset/asset_loading.rs index 6fd865b49e5d0..d10f572f295b8 100644 --- a/examples/asset/asset_loading.rs +++ b/examples/asset/asset_loading.rs @@ -12,30 +12,27 @@ fn main() { fn setup( mut commands: Commands, asset_server: Res, - mut meshes: ResMut>, + meshes: Res>, mut materials: ResMut>, ) { - // You can load all assets in a folder like this. They will be loaded in parallel without blocking - asset_server - .load_asset_folder("assets/models/monkey") - .unwrap(); - - // Then any asset in the folder can be accessed like this: - let monkey_handle = asset_server - .get_handle("assets/models/monkey/Monkey.gltf") - .unwrap(); - // You can load individual assets like this: - let cube_handle = asset_server.load("assets/models/cube/cube.gltf").unwrap(); + let cube_handle = asset_server.load("models/cube/cube.gltf#Mesh0/Primitive0"); + let sphere_handle = asset_server.load("models/sphere/sphere.gltf#Mesh0/Primitive0"); - // Assets are loaded in the background by default, which means they might not be available immediately after calling load(). - // If you need immediate access you can load assets synchronously like this: - let sphere_handle = asset_server - .load_sync(&mut meshes, "assets/models/sphere/sphere.gltf") - .unwrap(); // All assets end up in their Assets collection once they are done loading: - let sphere = meshes.get(&sphere_handle).unwrap(); - println!("{:?}", sphere.primitive_topology); + if let Some(sphere) = meshes.get(&sphere_handle) { + // You might notice that this doesn't run! This is because assets load in parallel without blocking. + // When an asset has loaded, it will appear in relevant Assets collection. + println!("{:?}", sphere.primitive_topology); + } else { + println!("sphere hasn't loaded yet"); + } + + // You can load all assets in a folder like this. They will be loaded in parallel without blocking + let _scenes: Vec = asset_server.load_folder("models/monkey").unwrap(); + + // Then any asset in the folder can be accessed like this: + let monkey_handle = asset_server.get_handle("models/monkey/Monkey.gltf#Mesh0/Primitive0"); // You can also add assets directly to their Assets storage: let material_handle = materials.add(StandardMaterial { @@ -48,14 +45,14 @@ fn setup( // monkey .spawn(PbrComponents { mesh: monkey_handle, - material: material_handle, + material: material_handle.clone(), transform: Transform::from_translation(Vec3::new(-3.0, 0.0, 0.0)), ..Default::default() }) // cube .spawn(PbrComponents { mesh: cube_handle, - material: material_handle, + material: material_handle.clone(), transform: Transform::from_translation(Vec3::new(0.0, 0.0, 0.0)), ..Default::default() }) @@ -73,7 +70,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(0.0, 3.0, 10.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(0.0, 3.0, 10.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/asset/custom_asset.rs b/examples/asset/custom_asset.rs new file mode 100644 index 0000000000000..796d8b1d08b92 --- /dev/null +++ b/examples/asset/custom_asset.rs @@ -0,0 +1,58 @@ +use bevy::{ + asset::{AssetLoader, LoadContext, LoadedAsset}, + prelude::*, + type_registry::TypeUuid, +}; +use serde::Deserialize; + +#[derive(Debug, Deserialize, TypeUuid)] +#[uuid = "39cadc56-aa9c-4543-8640-a018b74b5052"] +pub struct CustomAsset { + pub value: i32, +} + +#[derive(Default)] +pub struct CustomAssetLoader; + +impl AssetLoader for CustomAssetLoader { + fn load(&self, bytes: &[u8], load_context: &mut LoadContext) -> Result<(), anyhow::Error> { + let custom_asset = ron::de::from_bytes::(bytes)?; + load_context.set_default_asset(LoadedAsset::new(custom_asset)); + Ok(()) + } + + fn extensions(&self) -> &[&str] { + &["custom"] + } +} + +fn main() { + App::build() + .add_default_plugins() + .init_resource::() + .add_asset::() + .init_asset_loader::() + .add_startup_system(setup.system()) + .add_system(print_on_load.system()) + .run(); +} + +#[derive(Default)] +struct State { + handle: Handle, + printed: bool, +} + +fn setup(mut state: ResMut, asset_server: Res) { + state.handle = asset_server.load("data/asset.custom"); +} + +fn print_on_load(mut state: ResMut, custom_assets: ResMut>) { + let custom_asset = custom_assets.get(&state.handle); + if state.printed || custom_asset.is_none() { + return; + } + + println!("Custom asset loaded: {:?}", custom_asset.unwrap()); + state.printed = true; +} diff --git a/examples/asset/custom_asset_loading.rs b/examples/asset/custom_asset_loading.rs deleted file mode 100644 index 276d2062fb8c0..0000000000000 --- a/examples/asset/custom_asset_loading.rs +++ /dev/null @@ -1,76 +0,0 @@ -use bevy::{asset::AssetLoader, prelude::*}; -use ron::de::from_bytes; -use serde::Deserialize; -use std::path::Path; - -#[derive(Deserialize)] -pub struct MyCustomData { - pub num: i32, -} - -#[derive(Deserialize)] -pub struct MySecondCustomData { - pub is_set: bool, -} - -// create a custom loader for data files -#[derive(Default)] -pub struct DataFileLoader { - matching_extensions: Vec<&'static str>, -} - -impl DataFileLoader { - pub fn from_extensions(matching_extensions: Vec<&'static str>) -> Self { - DataFileLoader { - matching_extensions, - } - } -} - -impl AssetLoader for DataFileLoader -where - for<'de> TAsset: Deserialize<'de>, -{ - fn from_bytes(&self, _asset_path: &Path, bytes: Vec) -> Result { - Ok(from_bytes::(bytes.as_slice())?) - } - - fn extensions(&self) -> &[&str] { - self.matching_extensions.as_slice() - } -} - -/// This example illustrates various ways to load assets -fn main() { - App::build() - .add_default_plugins() - .add_asset::() - .add_asset_loader_from_instance::( - DataFileLoader::from_extensions(vec!["data1"]), - ) - .add_asset::() - .add_asset_loader_from_instance::( - DataFileLoader::from_extensions(vec!["data2"]), - ) - .add_startup_system(setup.system()) - .run(); -} - -fn setup( - asset_server: Res, - mut data1s: ResMut>, - mut data2s: ResMut>, -) { - let data1_handle = asset_server - .load_sync(&mut data1s, "assets/data/test_data.data1") - .unwrap(); - let data2_handle = asset_server - .load_sync(&mut data2s, "assets/data/test_data.data2") - .unwrap(); - - let data1 = data1s.get(&data1_handle).unwrap(); - println!("Data 1 loaded with value {}", data1.num); - - let data2 = data2s.get(&data2_handle).unwrap(); - println!("Data 2 loaded with value {}", data2.is_set); -} diff --git a/examples/asset/hot_asset_reloading.rs b/examples/asset/hot_asset_reloading.rs index 5bd196d3cf0cd..6e8adb787aa71 100644 --- a/examples/asset/hot_asset_reloading.rs +++ b/examples/asset/hot_asset_reloading.rs @@ -10,15 +10,9 @@ fn main() { .run(); } -fn setup( - mut commands: Commands, - asset_server: Res, - mut materials: ResMut>, -) { +fn setup(mut commands: Commands, asset_server: Res) { // Load our mesh: - let mesh_handle = asset_server - .load("assets/models/monkey/Monkey.gltf") - .unwrap(); + let scene_handle = asset_server.load("models/monkey/Monkey.gltf"); // Tell the asset server to watch for asset changes on disk: asset_server.watch_for_changes().unwrap(); @@ -26,20 +20,10 @@ fn setup( // Any changes to the mesh will be reloaded automatically! Try making a change to Monkey.gltf. // You should see the changes immediately show up in your app. - // Create a material for the mesh: - let material_handle = materials.add(StandardMaterial { - albedo: Color::rgb(0.8, 0.7, 0.6), - ..Default::default() - }); - // Add entities to the world: commands // mesh - .spawn(PbrComponents { - mesh: mesh_handle, - material: material_handle, - ..Default::default() - }) + .spawn_scene(scene_handle) // light .spawn(LightComponents { transform: Transform::from_translation(Vec3::new(4.0, 5.0, 4.0)), @@ -47,7 +31,8 @@ fn setup( }) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(2.0, 2.0, 6.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(2.0, 2.0, 6.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/audio/audio.rs b/examples/audio/audio.rs index 21744477c8ce1..d560010ae29ff 100644 --- a/examples/audio/audio.rs +++ b/examples/audio/audio.rs @@ -9,8 +9,6 @@ fn main() { } fn setup(asset_server: Res, audio_output: Res) { - let music = asset_server - .load("assets/sounds/Windless Slopes.mp3") - .unwrap(); + let music = asset_server.load("sounds/Windless Slopes.mp3"); audio_output.play(music); } diff --git a/examples/ecs/hierarchy.rs b/examples/ecs/hierarchy.rs index 221e062f839c7..47733dfd0a044 100644 --- a/examples/ecs/hierarchy.rs +++ b/examples/ecs/hierarchy.rs @@ -14,7 +14,7 @@ fn setup( mut materials: ResMut>, ) { commands.spawn(Camera2dComponents::default()); - let texture = asset_server.load("assets/branding/icon.png").unwrap(); + let texture = asset_server.load("branding/icon.png"); // Spawn a root entity with no parent let parent = commands @@ -22,7 +22,7 @@ fn setup( transform: Transform::from_scale(Vec3::splat(0.75)), material: materials.add(ColorMaterial { color: Color::WHITE, - texture: Some(texture), + texture: Some(texture.clone()), }), ..Default::default() }) @@ -37,7 +37,7 @@ fn setup( }, material: materials.add(ColorMaterial { color: Color::BLUE, - texture: Some(texture), + texture: Some(texture.clone()), }), ..Default::default() }); @@ -58,7 +58,7 @@ fn setup( }, material: materials.add(ColorMaterial { color: Color::RED, - texture: Some(texture), + texture: Some(texture.clone()), }), ..Default::default() }) diff --git a/examples/ecs/parallel_query.rs b/examples/ecs/parallel_query.rs index dbbe980b1847e..50a0be44014ee 100644 --- a/examples/ecs/parallel_query.rs +++ b/examples/ecs/parallel_query.rs @@ -9,12 +9,12 @@ fn spawn_system( mut materials: ResMut>, ) { commands.spawn(Camera2dComponents::default()); - let texture_handle = asset_server.load("assets/branding/icon.png").unwrap(); + let texture_handle = asset_server.load("branding/icon.png"); let material = materials.add(texture_handle.into()); for _ in 0..128 { commands .spawn(SpriteComponents { - material, + material: material.clone(), transform: Transform::from_scale(Vec3::splat(0.1)), ..Default::default() }) diff --git a/examples/game/breakout.rs b/examples/game/breakout.rs index 3c4f893d765a9..6bfaf149fc59a 100644 --- a/examples/game/breakout.rs +++ b/examples/game/breakout.rs @@ -68,7 +68,7 @@ fn setup( // scoreboard .spawn(TextComponents { text: Text { - font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(), + font: asset_server.load("fonts/FiraSans-Bold.ttf"), value: "Score:".to_string(), style: TextStyle { color: Color::rgb(0.5, 0.5, 1.0), @@ -95,7 +95,7 @@ fn setup( commands // left .spawn(SpriteComponents { - material: wall_material, + material: wall_material.clone(), transform: Transform::from_translation(Vec3::new(-bounds.x() / 2.0, 0.0, 0.0)), sprite: Sprite::new(Vec2::new(wall_thickness, bounds.y() + wall_thickness)), ..Default::default() @@ -103,7 +103,7 @@ fn setup( .with(Collider::Solid) // right .spawn(SpriteComponents { - material: wall_material, + material: wall_material.clone(), transform: Transform::from_translation(Vec3::new(bounds.x() / 2.0, 0.0, 0.0)), sprite: Sprite::new(Vec2::new(wall_thickness, bounds.y() + wall_thickness)), ..Default::default() @@ -111,7 +111,7 @@ fn setup( .with(Collider::Solid) // bottom .spawn(SpriteComponents { - material: wall_material, + material: wall_material.clone(), transform: Transform::from_translation(Vec3::new(0.0, -bounds.y() / 2.0, 0.0)), sprite: Sprite::new(Vec2::new(bounds.x() + wall_thickness, wall_thickness)), ..Default::default() @@ -146,7 +146,7 @@ fn setup( commands // brick .spawn(SpriteComponents { - material: brick_material, + material: brick_material.clone(), sprite: Sprite::new(brick_size), transform: Transform::from_translation(brick_position), ..Default::default() diff --git a/examples/scene/scene.rs b/examples/scene/scene.rs index 9c8c8817c0831..eb2a5c9727742 100644 --- a/examples/scene/scene.rs +++ b/examples/scene/scene.rs @@ -52,37 +52,17 @@ impl FromResources for ComponentB { fn load_scene_system(asset_server: Res, mut scene_spawner: ResMut) { // Scenes are loaded just like any other asset. - let scene_handle: Handle = asset_server - .load("assets/scenes/load_scene_example.scn") - .unwrap(); + let scene_handle: Handle = asset_server.load("scenes/load_scene_example.scn"); // SceneSpawner can "spawn" scenes. "Spawning" a scene creates a new instance of the scene in the World with new entity ids. // This guarantees that it will not overwrite existing entities. - scene_spawner.spawn(scene_handle); + scene_spawner.spawn_dynamic(scene_handle); // This tells the AssetServer to watch for changes to assets. // It enables our scenes to automatically reload in game when we modify their files asset_server.watch_for_changes().unwrap(); } -// Using SceneSpawner.spawn() queues up the scene to be spawned. It will be added to the World at the beginning of the next update. However if -// you need scenes to load immediately, you can use the following approach. But be aware that this takes full control of the ECS world -// and therefore blocks other parallel systems from executing until it finishes. In most cases you should use the SceneSpawner.spawn() method. -#[allow(dead_code)] -fn load_scene_right_now_system(world: &mut World, resources: &mut Resources) { - let scene_handle: Handle = { - let asset_server = resources.get::().unwrap(); - let mut scenes = resources.get_mut::>().unwrap(); - asset_server - .load_sync(&mut scenes, "assets/scenes/load_scene_example.scn") - .unwrap() - }; - let mut scene_spawner = resources.get_mut::().unwrap(); - scene_spawner - .spawn_sync(world, resources, scene_handle) - .unwrap(); -} - // This system prints all ComponentA components in our world. Try making a change to a ComponentA in load_scene_example.scn. // You should immediately see the changes appear in the console. fn print_system(mut query: Query<(Entity, Changed)>) { @@ -109,7 +89,7 @@ fn save_scene_system(_world: &mut World, resources: &mut Resources) { // The component registry resource contains information about all registered components. This is used to construct scenes. let type_registry = resources.get::().unwrap(); - let scene = Scene::from_world(&world, &type_registry.component.read()); + let scene = DynamicScene::from_world(&world, &type_registry.component.read()); // Scenes can be serialized like this: println!( @@ -122,7 +102,6 @@ fn save_scene_system(_world: &mut World, resources: &mut Resources) { // This is only necessary for the info message in the UI. See examples/ui/text.rs for a standalone text example. fn infotext_system(mut commands: Commands, asset_server: Res) { - let font_handle = asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(); commands .spawn(UiCameraComponents::default()) .spawn(TextComponents { @@ -132,7 +111,7 @@ fn infotext_system(mut commands: Commands, asset_server: Res) { }, text: Text { value: "Nothing to see in this window! Check the console output!".to_string(), - font: font_handle, + font: asset_server.load("fonts/FiraSans-Bold.ttf"), style: TextStyle { font_size: 50.0, color: Color::WHITE, diff --git a/examples/shader/shader_custom_material.rs b/examples/shader/shader_custom_material.rs index 4c6bcafeecd0c..95c98415720d2 100644 --- a/examples/shader/shader_custom_material.rs +++ b/examples/shader/shader_custom_material.rs @@ -7,6 +7,7 @@ use bevy::{ renderer::RenderResources, shader::{ShaderStage, ShaderStages}, }, + type_registry::TypeUuid, }; /// This example illustrates how to create a custom material asset and a shader that uses that material @@ -18,7 +19,8 @@ fn main() { .run(); } -#[derive(RenderResources, Default)] +#[derive(RenderResources, Default, TypeUuid)] +#[uuid = "1e08866c-0b8a-437e-8bce-37733b25127e"] struct MyMaterial { pub color: Color, } @@ -108,7 +110,8 @@ fn setup( .with(material) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(3.0, 5.0, -8.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(3.0, 5.0, -8.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/shader/shader_defs.rs b/examples/shader/shader_defs.rs index 31b6d0373ebea..e452bd8b2055c 100644 --- a/examples/shader/shader_defs.rs +++ b/examples/shader/shader_defs.rs @@ -7,6 +7,7 @@ use bevy::{ renderer::RenderResources, shader::{asset_shader_defs_system, ShaderDefs, ShaderStage, ShaderStages}, }, + type_registry::TypeUuid, }; /// This example illustrates how to create a custom material asset that uses "shader defs" and a shader that uses that material. @@ -23,7 +24,8 @@ fn main() { .run(); } -#[derive(RenderResources, ShaderDefs, Default)] +#[derive(RenderResources, ShaderDefs, Default, TypeUuid)] +#[uuid = "620f651b-adbe-464b-b740-ba0e547282ba"] struct MyMaterial { pub color: Color, #[render_resources(ignore)] @@ -103,9 +105,9 @@ fn setup( commands // cube .spawn(MeshComponents { - mesh: cube_handle, + mesh: cube_handle.clone(), render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::specialized( - pipeline_handle, + pipeline_handle.clone(), // NOTE: in the future you wont need to manually declare dynamic bindings PipelineSpecialization { dynamic_bindings: vec![ @@ -155,7 +157,8 @@ fn setup( .with(blue_material) // camera .spawn(Camera3dComponents { - transform: Transform::from_translation(Vec3::new(3.0, 5.0, -8.0)).looking_at_origin(), + transform: Transform::from_translation(Vec3::new(3.0, 5.0, -8.0)) + .looking_at(Vec3::default(), Vec3::unit_y()), ..Default::default() }); } diff --git a/examples/ui/button.rs b/examples/ui/button.rs index 67421ae44a551..5bb2f7d989361 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -42,15 +42,15 @@ fn button_system( match *interaction { Interaction::Clicked => { text.value = "Press".to_string(); - *material = button_materials.pressed; + *material = button_materials.pressed.clone(); } Interaction::Hovered => { text.value = "Hover".to_string(); - *material = button_materials.hovered; + *material = button_materials.hovered.clone(); } Interaction::None => { text.value = "Button".to_string(); - *material = button_materials.normal; + *material = button_materials.normal.clone(); } } } @@ -75,14 +75,14 @@ fn setup( align_items: AlignItems::Center, ..Default::default() }, - material: button_materials.normal, + material: button_materials.normal.clone(), ..Default::default() }) .with_children(|parent| { parent.spawn(TextComponents { text: Text { value: "Button".to_string(), - font: asset_server.load("assets/fonts/FiraSans-Bold.ttf").unwrap(), + font: asset_server.load("fonts/FiraSans-Bold.ttf"), style: TextStyle { font_size: 40.0, color: Color::rgb(0.9, 0.9, 0.9), diff --git a/examples/ui/font_atlas_debug.rs b/examples/ui/font_atlas_debug.rs index d1d8022203229..26375ce2dfff1 100644 --- a/examples/ui/font_atlas_debug.rs +++ b/examples/ui/font_atlas_debug.rs @@ -34,7 +34,7 @@ fn atlas_render_system( font_atlas_sets: Res>, texture_atlases: Res>, ) { - if let Some(set) = font_atlas_sets.get(&state.handle.as_handle::()) { + if let Some(set) = font_atlas_sets.get(&state.handle.as_weak::()) { if let Some((_size, font_atlas)) = set.iter().next() { let x_offset = state.atlas_count as f32; if state.atlas_count == font_atlas.len() as u32 { @@ -45,7 +45,7 @@ fn atlas_render_system( .unwrap(); state.atlas_count += 1; commands.spawn(ImageComponents { - material: materials.add(texture_atlas.texture.into()), + material: materials.add(texture_atlas.texture.clone().into()), style: Style { position_type: PositionType::Absolute, position: Rect { @@ -73,8 +73,8 @@ fn text_update_system(mut state: ResMut, time: Res