Asset system rework and GLTF scene loading (#693)
|
@ -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"
|
||||
|
|
3
assets/data/asset.custom
Normal file
|
@ -0,0 +1,3 @@
|
|||
CustomAsset (
|
||||
value: 42
|
||||
)
|
|
@ -1,3 +0,0 @@
|
|||
MyCustomData (
|
||||
num: 42
|
||||
)
|
|
@ -1,3 +0,0 @@
|
|||
MySecondCustomData (
|
||||
is_set: true
|
||||
)
|
BIN
assets/models/FlightHelmet/FlightHelmet.bin
Normal file
After Width: | Height: | Size: 3.1 MiB |
705
assets/models/FlightHelmet/FlightHelmet.gltf
Normal file
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 212 KiB |
After Width: | Height: | Size: 242 KiB |
After Width: | Height: | Size: 283 KiB |
After Width: | Height: | Size: 306 KiB |
After Width: | Height: | Size: 357 KiB |
After Width: | Height: | Size: 327 KiB |
After Width: | Height: | Size: 238 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 179 KiB |
After Width: | Height: | Size: 287 KiB |
After Width: | Height: | Size: 290 KiB |
After Width: | Height: | Size: 310 KiB |
After Width: | Height: | Size: 249 KiB |
After Width: | Height: | Size: 253 KiB |
After Width: | Height: | Size: 294 KiB |
|
@ -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)
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<String>),
|
||||
#[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<RefChangeChannel>,
|
||||
pub(crate) ref_counts: Arc<RwLock<HashMap<HandleId, usize>>>,
|
||||
}
|
||||
|
||||
/// 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<TAssetIo: AssetIo = FileAssetIo> {
|
||||
pub(crate) asset_io: TAssetIo,
|
||||
pub(crate) asset_ref_counter: AssetRefCounter,
|
||||
pub(crate) asset_sources: Arc<RwLock<HashMap<SourcePathId, SourceInfo>>>,
|
||||
pub(crate) asset_lifecycles: Arc<RwLock<HashMap<Uuid, Box<dyn AssetLifecycle>>>>,
|
||||
loaders: RwLock<Vec<Arc<Box<dyn AssetLoader>>>>,
|
||||
extension_to_loader_index: RwLock<HashMap<String, usize>>,
|
||||
handle_to_path: Arc<RwLock<HashMap<HandleId, AssetPath<'static>>>>,
|
||||
task_pool: TaskPool,
|
||||
}
|
||||
|
||||
/// Loads assets from the filesystem on background threads
|
||||
pub struct AssetServer {
|
||||
asset_folders: RwLock<Vec<PathBuf>>,
|
||||
asset_handlers: RwLock<Vec<Arc<dyn AssetLoadRequestHandler>>>,
|
||||
loaders: Vec<Resources>,
|
||||
task_pool: TaskPool,
|
||||
extension_to_handler_index: HashMap<String, usize>,
|
||||
extension_to_loader_index: HashMap<String, usize>,
|
||||
asset_info: RwLock<HashMap<HandleId, AssetInfo>>,
|
||||
asset_info_paths: RwLock<HashMap<PathBuf, HandleId>>,
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
filesystem_watcher: Arc<RwLock<Option<FilesystemWatcher>>>,
|
||||
pub struct AssetServer<TAssetIo: AssetIo = FileAssetIo> {
|
||||
pub(crate) server: Arc<AssetServerInternal<TAssetIo>>,
|
||||
}
|
||||
|
||||
impl AssetServer {
|
||||
pub fn new(task_pool: TaskPool) -> Self {
|
||||
impl<TAssetIo: AssetIo> Clone for AssetServer<TAssetIo> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
server: self.server.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TAssetIo: AssetIo> AssetServer<TAssetIo> {
|
||||
pub fn new(source_io: TAssetIo, 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)),
|
||||
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,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_handler<T>(&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);
|
||||
}
|
||||
|
||||
asset_handlers.push(Arc::new(asset_handler));
|
||||
pub(crate) fn register_asset_type<T: Asset>(&self) -> Assets<T> {
|
||||
self.server.asset_lifecycles.write().insert(
|
||||
T::TYPE_UUID,
|
||||
Box::new(AssetLifecycleChannel::<T>::default()),
|
||||
);
|
||||
Assets::new(self.server.asset_ref_counter.channel.sender.clone())
|
||||
}
|
||||
|
||||
pub fn add_loader<TLoader, TAsset>(&mut self, loader: TLoader)
|
||||
pub fn add_loader<T>(&self, loader: T)
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
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);
|
||||
}
|
||||
|
||||
let mut resources = Resources::default();
|
||||
resources.insert::<Box<dyn AssetLoader<TAsset>>>(Box::new(loader));
|
||||
self.loaders.push(resources);
|
||||
loaders.push(Arc::new(Box::new(loader)));
|
||||
}
|
||||
|
||||
pub fn load_asset_folder<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<Vec<HandleId>, 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<T, P: AsRef<Path>>(&self, path: P) -> Option<Handle<T>> {
|
||||
self.asset_info_paths
|
||||
.read()
|
||||
.get(path.as_ref())
|
||||
.map(|handle_id| Handle::from(*handle_id))
|
||||
}
|
||||
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
fn watch_path_for_changes<P: AsRef<Path>>(
|
||||
filesystem_watcher: &mut Option<FilesystemWatcher>,
|
||||
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(())
|
||||
}
|
||||
|
||||
#[cfg(feature = "filesystem_watcher")]
|
||||
pub fn watch_for_changes(&self) -> Result<(), AssetServerError> {
|
||||
let mut filesystem_watcher = self.filesystem_watcher.write();
|
||||
|
||||
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)?;
|
||||
}
|
||||
|
||||
self.server.asset_io.watch_for_changes()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
|
||||
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_handle<T: Asset, I: Into<HandleId>>(&self, id: I) -> Handle<T> {
|
||||
let sender = self.server.asset_ref_counter.channel.sender.clone();
|
||||
Handle::strong(id.into(), sender)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn get_root_path(&self) -> Result<PathBuf, AssetServerError> {
|
||||
Ok(PathBuf::from("/"))
|
||||
pub fn get_handle_untyped<I: Into<HandleId>>(&self, id: I) -> HandleUntyped {
|
||||
let sender = self.server.asset_ref_counter.channel.sender.clone();
|
||||
HandleUntyped::strong(id.into(), sender)
|
||||
}
|
||||
|
||||
// TODO: add type checking here. people shouldn't be able to request a Handle<Texture> for a Mesh asset
|
||||
pub fn load<T, P: AsRef<Path>>(&self, path: P) -> Result<Handle<T>, AssetServerError> {
|
||||
self.load_untyped(self.get_root_path()?.join(path))
|
||||
.map(Handle::from)
|
||||
}
|
||||
|
||||
pub fn load_sync<T: Resource, P: AsRef<Path>>(
|
||||
fn get_asset_loader(
|
||||
&self,
|
||||
assets: &mut Assets<T>,
|
||||
path: P,
|
||||
) -> Result<Handle<T>, 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::<Box<dyn AssetLoader<T>>>().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)
|
||||
}
|
||||
} else {
|
||||
Err(AssetServerError::MissingAssetHandler)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_untyped<P: AsRef<Path>>(&self, path: P) -> Result<HandleId, AssetServerError> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_load_state_untyped(&self, handle_id: HandleId) -> Option<LoadState> {
|
||||
self.asset_info
|
||||
extension: &str,
|
||||
) -> Result<Arc<Box<dyn AssetLoader>>, AssetServerError> {
|
||||
self.server
|
||||
.extension_to_loader_index
|
||||
.read()
|
||||
.get(&handle_id)
|
||||
.map(|asset_info| asset_info.load_state.clone())
|
||||
.get(extension)
|
||||
.map(|index| self.server.loaders.read()[*index].clone())
|
||||
.ok_or_else(|| AssetServerError::MissingAssetLoader(Some(extension.to_string())))
|
||||
}
|
||||
|
||||
pub fn get_load_state<T>(&self, handle: Handle<T>) -> Option<LoadState> {
|
||||
self.get_load_state_untyped(handle.id)
|
||||
fn get_path_asset_loader<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<Arc<Box<dyn AssetLoader>>, AssetServerError> {
|
||||
path.as_ref()
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.ok_or(AssetServerError::MissingAssetLoader(None))
|
||||
.and_then(|extension| self.get_asset_loader(extension))
|
||||
}
|
||||
|
||||
pub fn get_group_load_state(&self, handle_ids: &[HandleId]) -> Option<LoadState> {
|
||||
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,
|
||||
pub fn get_handle_path<H: Into<HandleId>>(&self, handle: H) -> Option<AssetPath<'_>> {
|
||||
self.server
|
||||
.handle_to_path
|
||||
.read()
|
||||
.get(&handle.into())
|
||||
.cloned()
|
||||
}
|
||||
|
||||
pub fn get_load_state<H: Into<HandleId>>(&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,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_group_load_state(&self, handles: impl IntoIterator<Item = HandleId>) -> 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,
|
||||
}
|
||||
}
|
||||
|
||||
Some(load_state)
|
||||
load_state
|
||||
}
|
||||
|
||||
fn load_assets_in_folder_recursive(
|
||||
pub fn load<'a, T: Asset, P: Into<AssetPath<'a>>>(&self, path: P) -> Handle<T> {
|
||||
self.load_untyped(path).typed()
|
||||
}
|
||||
|
||||
// TODO: properly set failed LoadState in all failure cases
|
||||
fn load_sync<'a, P: Into<AssetPath<'a>>>(
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> Result<Vec<HandleId>, AssetServerError> {
|
||||
if !path.is_dir() {
|
||||
path: P,
|
||||
force: bool,
|
||||
) -> Result<AssetPathId, AssetServerError> {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
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 load_untyped<'a, P: Into<AssetPath<'a>>>(&self, path: P) -> HandleUntyped {
|
||||
let handle_id = self.load_untracked(path, false);
|
||||
self.get_handle_untyped(handle_id)
|
||||
}
|
||||
|
||||
pub(crate) fn load_untracked<'a, P: Into<AssetPath<'a>>>(
|
||||
&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()
|
||||
}
|
||||
|
||||
pub fn load_folder<P: AsRef<Path>>(
|
||||
&self,
|
||||
path: P,
|
||||
) -> Result<Vec<HandleUntyped>, 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<AssetServer>) {
|
||||
let mut changed = HashSet::default();
|
||||
|
||||
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()
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
};
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
changed.extend(paths);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_asset_storage<T: Asset>(&self, assets: &mut Assets<T>) {
|
||||
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::<AssetLifecycleChannel<T>>()
|
||||
.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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free_unused_assets_system(asset_server: Res<AssetServer>) {
|
||||
asset_server.free_unused_assets();
|
||||
}
|
||||
|
|
|
@ -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<T: Resource> {
|
||||
pub enum AssetEvent<T: Asset> {
|
||||
Created { handle: Handle<T> },
|
||||
Modified { handle: Handle<T> },
|
||||
Removed { handle: Handle<T> },
|
||||
}
|
||||
|
||||
/// Stores Assets of a given type and tracks changes to them.
|
||||
#[derive(Debug)]
|
||||
pub struct Assets<T: Resource> {
|
||||
assets: HashMap<Handle<T>, T>,
|
||||
events: Events<AssetEvent<T>>,
|
||||
impl<T: Asset> Debug for AssetEvent<T> {
|
||||
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::<T>()
|
||||
))
|
||||
.field("handle", &handle.id)
|
||||
.finish(),
|
||||
AssetEvent::Modified { handle } => f
|
||||
.debug_struct(&format!(
|
||||
"AssetEvent<{}>::Modified",
|
||||
std::any::type_name::<T>()
|
||||
))
|
||||
.field("handle", &handle.id)
|
||||
.finish(),
|
||||
AssetEvent::Removed { handle } => f
|
||||
.debug_struct(&format!(
|
||||
"AssetEvent<{}>::Removed",
|
||||
std::any::type_name::<T>()
|
||||
))
|
||||
.field("handle", &handle.id)
|
||||
.finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Resource> Default for Assets<T> {
|
||||
fn default() -> Self {
|
||||
/// Stores Assets of a given type and tracks changes to them.
|
||||
#[derive(Debug)]
|
||||
pub struct Assets<T: Asset> {
|
||||
assets: HashMap<HandleId, T>,
|
||||
events: Events<AssetEvent<T>>,
|
||||
pub(crate) ref_change_sender: Sender<RefChange>,
|
||||
}
|
||||
|
||||
impl<T: Asset> Assets<T> {
|
||||
pub(crate) fn new(ref_change_sender: Sender<RefChange>) -> Self {
|
||||
Assets {
|
||||
assets: HashMap::default(),
|
||||
events: Events::default(),
|
||||
ref_change_sender,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Resource> Assets<T> {
|
||||
pub fn add(&mut self, asset: T) -> Handle<T> {
|
||||
let handle = Handle::new();
|
||||
self.assets.insert(handle, asset);
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
handle
|
||||
let id = HandleId::random::<T>();
|
||||
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<T>, 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<H: Into<HandleId>>(&mut self, handle: H, asset: T) -> Handle<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),
|
||||
});
|
||||
}
|
||||
|
||||
self.get_handle(id)
|
||||
}
|
||||
|
||||
pub fn set_untracked<H: Into<HandleId>>(&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: Handle::weak(id),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_default(&mut self, asset: T) -> Handle<T> {
|
||||
let handle = Handle::default();
|
||||
let exists = self.assets.contains_key(&handle);
|
||||
self.assets.insert(handle, asset);
|
||||
if exists {
|
||||
self.events.send(AssetEvent::Modified { handle });
|
||||
} else {
|
||||
self.events.send(AssetEvent::Created { handle });
|
||||
}
|
||||
handle
|
||||
pub fn get<H: Into<HandleId>>(&self, handle: H) -> Option<&T> {
|
||||
self.assets.get(&handle.into())
|
||||
}
|
||||
|
||||
pub fn get_with_id(&self, id: HandleId) -> Option<&T> {
|
||||
self.get(&Handle::from_id(id))
|
||||
pub fn contains<H: Into<HandleId>>(&self, handle: H) -> bool {
|
||||
self.assets.contains_key(&handle.into())
|
||||
}
|
||||
|
||||
pub fn get_with_id_mut(&mut self, id: HandleId) -> Option<&mut T> {
|
||||
self.get_mut(&Handle::from_id(id))
|
||||
pub fn get_mut<H: Into<HandleId>>(&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(&self, handle: &Handle<T>) -> Option<&T> {
|
||||
self.assets.get(&handle)
|
||||
pub fn get_handle<H: Into<HandleId>>(&self, handle: H) -> Handle<T> {
|
||||
Handle::strong(handle.into(), self.ref_change_sender.clone())
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, handle: &Handle<T>) -> Option<&mut T> {
|
||||
self.events.send(AssetEvent::Modified { handle: *handle });
|
||||
self.assets.get_mut(&handle)
|
||||
}
|
||||
|
||||
pub fn get_or_insert_with(
|
||||
pub fn get_or_insert_with<H: Into<HandleId>>(
|
||||
&mut self,
|
||||
handle: Handle<T>,
|
||||
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<T: Resource> Assets<T> {
|
|||
borrowed
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = (Handle<T>, &T)> {
|
||||
pub fn iter(&self) -> impl Iterator<Item = (HandleId, &T)> {
|
||||
self.assets.iter().map(|(k, v)| (*k, v))
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, handle: &Handle<T>) -> Option<T> {
|
||||
self.assets.remove(&handle)
|
||||
pub fn ids(&self) -> impl Iterator<Item = HandleId> + '_ {
|
||||
self.assets.keys().cloned()
|
||||
}
|
||||
|
||||
pub fn remove<H: Into<HandleId>>(&mut self, handle: H) -> Option<T> {
|
||||
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<T: Resource> Assets<T> {
|
|||
) {
|
||||
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<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Send + Sync + 'static;
|
||||
fn add_asset_loader<TAsset, TLoader>(&mut self) -> &mut Self
|
||||
T: Asset;
|
||||
fn init_asset_loader<T>(&mut self) -> &mut Self
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + FromResources,
|
||||
TAsset: Send + Sync + 'static;
|
||||
fn add_asset_loader_from_instance<TAsset, TLoader>(&mut self, instance: TLoader) -> &mut Self
|
||||
T: AssetLoader + FromResources;
|
||||
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + FromResources,
|
||||
TAsset: Send + Sync + 'static;
|
||||
T: AssetLoader;
|
||||
}
|
||||
|
||||
impl AddAsset for AppBuilder {
|
||||
fn add_asset<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Resource,
|
||||
T: Asset,
|
||||
{
|
||||
self.init_resource::<Assets<T>>()
|
||||
let assets = {
|
||||
let asset_server = self.resources().get::<AssetServer>().unwrap();
|
||||
asset_server.register_asset_type::<T>()
|
||||
};
|
||||
|
||||
self.add_resource(assets)
|
||||
.register_component::<Handle<T>>()
|
||||
.add_system_to_stage(
|
||||
super::stage::ASSET_EVENTS,
|
||||
Assets::<T>::asset_event_system.system(),
|
||||
)
|
||||
.add_system_to_stage(
|
||||
crate::stage::LOAD_ASSETS,
|
||||
update_asset_storage_system::<T>.system(),
|
||||
)
|
||||
.add_event::<AssetEvent<T>>()
|
||||
}
|
||||
|
||||
fn add_asset_loader_from_instance<TAsset, TLoader>(&mut self, instance: TLoader) -> &mut Self
|
||||
fn init_asset_loader<T>(&mut self) -> &mut Self
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + FromResources,
|
||||
TAsset: Send + Sync + 'static,
|
||||
T: AssetLoader + FromResources,
|
||||
{
|
||||
{
|
||||
if !self.resources().contains::<AssetChannel<TAsset>>() {
|
||||
self.resources_mut().insert(AssetChannel::<TAsset>::new());
|
||||
self.add_system_to_stage(
|
||||
crate::stage::LOAD_ASSETS,
|
||||
update_asset_storage_system::<TAsset>.system(),
|
||||
);
|
||||
}
|
||||
let asset_channel = self
|
||||
.resources()
|
||||
.get::<AssetChannel<TAsset>>()
|
||||
.expect("AssetChannel should always exist at this point.");
|
||||
let mut asset_server = self
|
||||
.resources()
|
||||
.get_mut::<AssetServer>()
|
||||
.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<TAsset, TLoader>(&mut self) -> &mut Self
|
||||
fn add_asset_loader<T>(&mut self, loader: T) -> &mut Self
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + FromResources,
|
||||
TAsset: Send + Sync + 'static,
|
||||
T: AssetLoader,
|
||||
{
|
||||
self.add_asset_loader_from_instance::<TAsset, TLoader>(TLoader::from_resources(
|
||||
self.resources(),
|
||||
))
|
||||
self.resources()
|
||||
.get_mut::<AssetServer>()
|
||||
.expect("AssetServer does not exist. Consider adding it as a resource.")
|
||||
.add_loader(loader);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<AssetPathId> for HandleId {
|
||||
fn from(value: AssetPathId) -> Self {
|
||||
HandleId::AssetPathId(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AssetPath<'a>> 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<T: Asset>() -> Self {
|
||||
HandleId::Id(T::TYPE_UUID, rand::random())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn default<T: Asset>() -> Self {
|
||||
HandleId::Id(T::TYPE_UUID, 0)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub const fn new(type_uuid: Uuid, id: u64) -> Self {
|
||||
HandleId::Id(type_uuid, id)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,92 +63,125 @@ where
|
|||
{
|
||||
pub id: HandleId,
|
||||
#[property(ignore)]
|
||||
handle_type: HandleType,
|
||||
#[property(ignore)]
|
||||
marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
enum HandleType {
|
||||
Weak,
|
||||
Strong(Sender<RefChange>),
|
||||
}
|
||||
|
||||
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<T> Handle<T> {
|
||||
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<U>(&self) -> Handle<U> {
|
||||
Handle::from_id(self.id)
|
||||
}
|
||||
|
||||
pub const fn from_id(id: HandleId) -> Self {
|
||||
Handle {
|
||||
impl<T: Asset> Handle<T> {
|
||||
pub(crate) fn strong(id: HandleId, ref_change_sender: Sender<RefChange>) -> Self {
|
||||
ref_change_sender.send(RefChange::Increment(id)).unwrap();
|
||||
Self {
|
||||
id,
|
||||
handle_type: HandleType::Strong(ref_change_sender),
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn from_u128(value: u128) -> Self {
|
||||
pub fn weak(id: HandleId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
handle_type: HandleType::Weak,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_weak<U>(&self) -> Handle<U> {
|
||||
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<T>) {
|
||||
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 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 from_untyped(untyped_handle: HandleUntyped) -> Option<Handle<T>>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
if TypeId::of::<T>() == untyped_handle.type_id {
|
||||
Some(Handle::from_id(untyped_handle.id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
pub fn clone_weak_untyped(&self) -> HandleUntyped {
|
||||
HandleUntyped::weak(self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<HandleId> for Handle<T> {
|
||||
fn from(value: HandleId) -> Self {
|
||||
Handle::from_id(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<u128> for Handle<T> {
|
||||
fn from(value: u128) -> Self {
|
||||
Handle::from_u128(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<[u8; 16]> for Handle<T> {
|
||||
fn from(value: [u8; 16]) -> Self {
|
||||
Handle::from_bytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<HandleUntyped> for Handle<T>
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn from(handle: HandleUntyped) -> Self {
|
||||
if TypeId::of::<T>() == handle.type_id {
|
||||
Handle {
|
||||
id: handle.id,
|
||||
marker: PhantomData::default(),
|
||||
impl<T> Drop for Handle<T> {
|
||||
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));
|
||||
}
|
||||
} else {
|
||||
panic!("attempted to convert untyped handle to incorrect typed handle")
|
||||
HandleType::Weak => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Handle<T>> for HandleId {
|
||||
fn from(value: Handle<T>) -> Self {
|
||||
value.id
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for HandleId {
|
||||
fn from(value: &str) -> Self {
|
||||
AssetPathId::from(value).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<&Handle<T>> for HandleId {
|
||||
fn from(value: &Handle<T>) -> Self {
|
||||
value.id
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Hash for Handle<T> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
|
@ -148,31 +208,27 @@ impl<T> Ord for Handle<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<T: Asset> Default for Handle<T> {
|
||||
fn default() -> Self {
|
||||
Handle::weak(HandleId::default::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Debug for Handle<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
|
||||
let name = std::any::type_name::<T>().split("::").last().unwrap();
|
||||
write!(f, "Handle<{}>({:?})", name, self.id.0)
|
||||
write!(f, "{:?}Handle<{}>({:?})", self.handle_type, name, self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Default for Handle<T> {
|
||||
fn default() -> Self {
|
||||
Handle {
|
||||
id: DEFAULT_HANDLE_ID,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Clone for Handle<T> {
|
||||
impl<T: Asset> Clone for Handle<T> {
|
||||
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<T> Copy for Handle<T> {}
|
||||
|
||||
// SAFE: T is phantom data and Handle::id is an integer
|
||||
unsafe impl<T> Send for Handle<T> {}
|
||||
|
@ -181,26 +237,115 @@ unsafe impl<T> Sync for Handle<T> {}
|
|||
/// A non-generic version of [Handle]
|
||||
///
|
||||
/// This allows handles to be mingled in a cross asset context. For example, storing `Handle<A>` and `Handle<B>` in the same `HashSet<HandleUntyped>`.
|
||||
#[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<T: 'static>(untyped: &HandleUntyped) -> bool {
|
||||
TypeId::of::<T>() == untyped.type_id
|
||||
pub(crate) fn strong(id: HandleId, ref_change_sender: Sender<RefChange>) -> Self {
|
||||
ref_change_sender.send(RefChange::Increment(id)).unwrap();
|
||||
Self {
|
||||
id,
|
||||
handle_type: HandleType::Strong(ref_change_sender),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> From<Handle<T>> for HandleUntyped
|
||||
where
|
||||
T: 'static,
|
||||
{
|
||||
fn from(handle: Handle<T>) -> Self {
|
||||
HandleUntyped {
|
||||
id: handle.id,
|
||||
type_id: TypeId::of::<T>(),
|
||||
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<T: Asset>(mut self) -> Handle<T> {
|
||||
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 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<H: Hasher>(&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<RefChange>,
|
||||
pub receiver: Receiver<RefChange>,
|
||||
}
|
||||
|
||||
impl Default for RefChangeChannel {
|
||||
fn default() -> Self {
|
||||
let (sender, receiver) = crossbeam_channel::unbounded();
|
||||
RefChangeChannel { sender, receiver }
|
||||
}
|
||||
}
|
||||
|
|
49
crates/bevy_asset/src/info.rs
Normal file
|
@ -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<AssetMeta>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct AssetMeta {
|
||||
pub label: Option<String>,
|
||||
pub dependencies: Vec<AssetPath<'static>>,
|
||||
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<SourceMeta>,
|
||||
pub path: PathBuf,
|
||||
pub asset_types: HashMap<LabelId, Uuid>,
|
||||
pub load_state: LoadState,
|
||||
pub committed_assets: HashSet<LabelId>,
|
||||
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<Uuid> {
|
||||
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,
|
||||
}
|
168
crates/bevy_asset/src/io.rs
Normal file
|
@ -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<Vec<u8>, AssetIoError>;
|
||||
fn save_path(&self, path: &Path, bytes: &[u8]) -> Result<(), AssetIoError>;
|
||||
fn read_directory(
|
||||
&self,
|
||||
path: &Path,
|
||||
) -> Result<Box<dyn Iterator<Item = PathBuf>>, 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<RwLock<Option<FilesystemWatcher>>>,
|
||||
}
|
||||
|
||||
impl FileAssetIo {
|
||||
pub fn new<P: AsRef<Path>>(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<Vec<u8>, 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<Box<dyn Iterator<Item = PathBuf>>, 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<AssetServer>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::<HandleId>();
|
||||
.add_resource(asset_server)
|
||||
.register_property::<HandleId>()
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
TAsset: 'static,
|
||||
{
|
||||
sender: Sender<AssetResult<TAsset>>,
|
||||
loader: TLoader,
|
||||
}
|
|
@ -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<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
{
|
||||
pub fn new(loader: TLoader, sender: Sender<AssetResult<TAsset>>) -> Self {
|
||||
ChannelAssetHandler { sender, loader }
|
||||
}
|
||||
|
||||
fn load_asset(&self, load_request: &LoadRequest) -> Result<TAsset, AssetLoadError> {
|
||||
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<TLoader, TAsset> AssetLoadRequestHandler for ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + '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()
|
||||
}
|
||||
}
|
|
@ -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<TLoader, TAsset> ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset>,
|
||||
{
|
||||
pub fn new(loader: TLoader, sender: Sender<AssetResult<TAsset>>) -> Self {
|
||||
ChannelAssetHandler { sender, loader }
|
||||
}
|
||||
|
||||
async fn load_asset(&self, load_request: &LoadRequest) -> Result<TAsset, AssetLoadError> {
|
||||
// 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<TLoader, TAsset> AssetLoadRequestHandler for ChannelAssetHandler<TLoader, TAsset>
|
||||
where
|
||||
TLoader: AssetLoader<TAsset> + '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()
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
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;
|
||||
|
||||
/// 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),
|
||||
/// 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];
|
||||
}
|
||||
|
||||
/// A loader for a given asset of type `T`
|
||||
pub trait AssetLoader<T>: Send + Sync + 'static {
|
||||
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<T, anyhow::Error>;
|
||||
fn extensions(&self) -> &[&str];
|
||||
fn load_from_file(&self, asset_path: &Path) -> Result<T, AssetLoadError> {
|
||||
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<T> Asset for T where T: TypeUuid + AssetDynamic + TypeUuidDynamic {}
|
||||
|
||||
impl<T> AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {}
|
||||
|
||||
pub struct LoadedAsset {
|
||||
pub(crate) value: Option<Box<dyn AssetDynamic>>,
|
||||
pub(crate) dependencies: Vec<AssetPath<'static>>,
|
||||
}
|
||||
|
||||
impl LoadedAsset {
|
||||
pub fn new<T: Asset>(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<AssetPath<'static>>) -> 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<Option<String>, 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<I: Into<HandleId>, T: Asset>(&self, id: I) -> Handle<T> {
|
||||
Handle::strong(id.into(), self.ref_change_channel.sender.clone())
|
||||
}
|
||||
|
||||
pub fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
|
||||
self.asset_io.load_path(path.as_ref())
|
||||
}
|
||||
|
||||
pub fn get_asset_metas(&self) -> Vec<AssetMeta> {
|
||||
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<T: 'static> {
|
||||
pub result: Result<T, AssetLoadError>,
|
||||
pub handle: Handle<T>,
|
||||
pub path: PathBuf,
|
||||
pub version: AssetVersion,
|
||||
pub struct AssetResult<T: Resource> {
|
||||
pub asset: T,
|
||||
pub id: HandleId,
|
||||
pub version: usize,
|
||||
}
|
||||
|
||||
/// A channel to send and receive [AssetResult]s
|
||||
#[derive(Debug)]
|
||||
pub struct AssetChannel<T: 'static> {
|
||||
pub sender: Sender<AssetResult<T>>,
|
||||
pub receiver: Receiver<AssetResult<T>>,
|
||||
pub struct AssetLifecycleChannel<T: Resource> {
|
||||
pub sender: Sender<AssetLifecycleEvent<T>>,
|
||||
pub receiver: Receiver<AssetLifecycleEvent<T>>,
|
||||
}
|
||||
|
||||
impl<T> AssetChannel<T> {
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
pub enum AssetLifecycleEvent<T: Resource> {
|
||||
Create(AssetResult<T>),
|
||||
Free(HandleId),
|
||||
}
|
||||
|
||||
pub trait AssetLifecycle: Downcast + Send + Sync + 'static {
|
||||
fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize);
|
||||
fn free_asset(&self, id: HandleId);
|
||||
}
|
||||
impl_downcast!(AssetLifecycle);
|
||||
|
||||
impl<T: AssetDynamic> AssetLifecycle for AssetLifecycleChannel<T> {
|
||||
fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize) {
|
||||
if let Ok(asset) = asset.downcast::<T>() {
|
||||
self.sender
|
||||
.send(AssetLifecycleEvent::Create(AssetResult {
|
||||
id,
|
||||
asset: *asset,
|
||||
version,
|
||||
}))
|
||||
.unwrap()
|
||||
} else {
|
||||
panic!("failed to downcast asset to {}", std::any::type_name::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
fn free_asset(&self, id: HandleId) {
|
||||
self.sender.send(AssetLifecycleEvent::Free(id)).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Resource> Default for AssetLifecycleChannel<T> {
|
||||
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<T: Resource>(
|
||||
asset_channel: Res<AssetChannel<T>>,
|
||||
pub fn update_asset_storage_system<T: Asset + AssetDynamic>(
|
||||
asset_server: Res<AssetServer>,
|
||||
mut assets: ResMut<Assets<T>>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
|
|
168
crates/bevy_asset/src/path.rs
Normal file
|
@ -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<Cow<'a, str>>,
|
||||
}
|
||||
|
||||
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<String>) -> 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<AssetPathId> for SourcePathId {
|
||||
fn from(id: AssetPathId) -> Self {
|
||||
id.source_path_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<AssetPath<'a>> for SourcePathId {
|
||||
fn from(path: AssetPath) -> Self {
|
||||
AssetPathId::from(path).source_path_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Option<&'a str>> 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<T> for AssetPathId
|
||||
where
|
||||
T: Into<AssetPath<'a>>,
|
||||
{
|
||||
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<PathBuf> for AssetPath<'a> {
|
||||
fn from(path: PathBuf) -> Self {
|
||||
AssetPath {
|
||||
path: Cow::Owned(path),
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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<P> AudioOutput<P>
|
||||
where
|
||||
P: Decodable,
|
||||
P: Asset + Decodable,
|
||||
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
|
||||
<<P as Decodable>::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<P>(audio_sources: Res<Assets<P>>, audio_output: Res<AudioOutput<P>>)
|
||||
where
|
||||
pub fn play_queued_audio_system<P: Asset>(
|
||||
audio_sources: Res<Assets<P>>,
|
||||
audio_output: Res<AudioOutput<P>>,
|
||||
) where
|
||||
P: Decodable,
|
||||
<P as Decodable>::Decoder: rodio::Source + Send + Sync,
|
||||
<<P as Decodable>::Decoder as Iterator>::Item: rodio::Sample + Send + Sync,
|
||||
|
|
|
@ -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<AudioSource> for Mp3Loader {
|
||||
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<AudioSource> {
|
||||
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] {
|
||||
|
|
|
@ -20,7 +20,7 @@ impl Plugin for AudioPlugin {
|
|||
fn build(&self, app: &mut AppBuilder) {
|
||||
app.init_resource::<AudioOutput<AudioSource>>()
|
||||
.add_asset::<AudioSource>()
|
||||
.add_asset_loader::<AudioSource, Mp3Loader>()
|
||||
.init_asset_loader::<Mp3Loader>()
|
||||
.add_system_to_stage(
|
||||
stage::POST_UPDATE,
|
||||
play_queued_audio_system::<AudioSource>.system(),
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
98
crates/bevy_derive/src/type_uuid.rs
Normal file
|
@ -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::<LitInt>(&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<Self> {
|
||||
let path = input.parse()?;
|
||||
input.parse::<Token![,]>()?;
|
||||
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::<LitInt>(&byte_str).unwrap());
|
||||
|
||||
let gen = quote! {
|
||||
impl crate::TypeUuid for #path {
|
||||
const TYPE_UUID: Uuid = uuid::Uuid::from_bytes([
|
||||
#( #bytes ),*
|
||||
]);
|
||||
}
|
||||
};
|
||||
gen.into()
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -157,6 +157,18 @@ impl Resources {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn get_or_insert_with<T: Resource>(
|
||||
&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::<T>().is_some() {
|
||||
return self.get_mut::<T>().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<T: Resource + Clone>(&self) -> Option<T> {
|
||||
|
|
|
@ -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<dyn WorldWriter>),
|
||||
WriteResources(Box<dyn ResourcesWriter>),
|
||||
}
|
||||
|
||||
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<Self>, world: &mut World);
|
||||
pub trait Command: Send + Sync {
|
||||
fn write(self: Box<Self>, world: &mut World, resources: &mut Resources);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -38,11 +17,11 @@ where
|
|||
components: T,
|
||||
}
|
||||
|
||||
impl<T> WorldWriter for Spawn<T>
|
||||
impl<T> Command for Spawn<T>
|
||||
where
|
||||
T: DynamicBundle + Send + Sync + 'static,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.spawn(self.components);
|
||||
}
|
||||
}
|
||||
|
@ -55,12 +34,12 @@ where
|
|||
components_iter: I,
|
||||
}
|
||||
|
||||
impl<I> WorldWriter for SpawnBatch<I>
|
||||
impl<I> Command for SpawnBatch<I>
|
||||
where
|
||||
I: IntoIterator + Send + Sync,
|
||||
I::Item: Bundle,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
fn write(self: Box<Self>, 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<Self>, world: &mut World) {
|
||||
impl Command for Despawn {
|
||||
fn write(self: Box<Self>, 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<T> WorldWriter for Insert<T>
|
||||
impl<T> Command for Insert<T>
|
||||
where
|
||||
T: DynamicBundle + Send + Sync + 'static,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.insert(self.entity, self.components).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -104,11 +83,11 @@ where
|
|||
component: T,
|
||||
}
|
||||
|
||||
impl<T> WorldWriter for InsertOne<T>
|
||||
impl<T> Command for InsertOne<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.insert(self.entity, (self.component,)).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -122,11 +101,11 @@ where
|
|||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> WorldWriter for RemoveOne<T>
|
||||
impl<T> Command for RemoveOne<T>
|
||||
where
|
||||
T: Component,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
if world.get::<T>(self.entity).is_ok() {
|
||||
world.remove_one::<T>(self.entity).unwrap();
|
||||
}
|
||||
|
@ -142,11 +121,11 @@ where
|
|||
phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> WorldWriter for Remove<T>
|
||||
impl<T> Command for Remove<T>
|
||||
where
|
||||
T: Bundle + Send + Sync + 'static,
|
||||
{
|
||||
fn write(self: Box<Self>, world: &mut World) {
|
||||
fn write(self: Box<Self>, world: &mut World, _resources: &mut Resources) {
|
||||
world.remove::<T>(self.entity).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -159,8 +138,8 @@ pub struct InsertResource<T: Resource> {
|
|||
resource: T,
|
||||
}
|
||||
|
||||
impl<T: Resource> ResourcesWriter for InsertResource<T> {
|
||||
fn write(self: Box<Self>, resources: &mut Resources) {
|
||||
impl<T: Resource> Command for InsertResource<T> {
|
||||
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
|
||||
resources.insert(self.resource);
|
||||
}
|
||||
}
|
||||
|
@ -171,15 +150,15 @@ pub(crate) struct InsertLocalResource<T: Resource> {
|
|||
system_id: SystemId,
|
||||
}
|
||||
|
||||
impl<T: Resource> ResourcesWriter for InsertLocalResource<T> {
|
||||
fn write(self: Box<Self>, resources: &mut Resources) {
|
||||
impl<T: Resource> Command for InsertLocalResource<T> {
|
||||
fn write(self: Box<Self>, _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<Command>,
|
||||
pub commands: Vec<Box<dyn Command>>,
|
||||
pub current_entity: Option<Entity>,
|
||||
pub entity_reserver: Option<EntityReserver>,
|
||||
}
|
||||
|
@ -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<W: WorldWriter + 'static>(&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<dyn WorldWriter + 'static>) -> &mut Self {
|
||||
self.commands.push(Command::WriteWorld(world_writer));
|
||||
pub fn add_command<C: Command + 'static>(&mut self, command: C) -> &mut Self {
|
||||
self.commands.push(Box::new(command));
|
||||
self
|
||||
}
|
||||
|
||||
pub fn write_resources<W: ResourcesWriter + 'static>(
|
||||
&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<dyn Command>) -> &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<Mutex<CommandsInternal>>,
|
||||
}
|
||||
|
@ -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<T: Resource>(&mut self, resource: T) -> &mut Self {
|
||||
self.write_resources(InsertResource { resource })
|
||||
self.add_command(InsertResource { resource })
|
||||
}
|
||||
|
||||
pub fn insert_local_resource<T: 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<W: WorldWriter + 'static>(&mut self, world_writer: W) -> &mut Self {
|
||||
self.commands.lock().write_world(world_writer);
|
||||
pub fn add_command<C: Command + 'static>(&mut self, command: C) -> &mut Self {
|
||||
self.commands.lock().add_command(command);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn write_world_boxed(&mut self, world_writer: Box<dyn WorldWriter + 'static>) -> &mut Self {
|
||||
self.commands.lock().write_world_boxed(world_writer);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn write_resources<W: ResourcesWriter + 'static>(
|
||||
&mut self,
|
||||
resources_writer: W,
|
||||
) -> &mut Self {
|
||||
self.commands.lock().write_resources(resources_writer);
|
||||
pub fn add_command_boxed(&mut self, command: Box<dyn Command>) -> &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::<T> {
|
||||
self.add_command(RemoveOne::<T> {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
|
@ -371,7 +328,7 @@ impl Commands {
|
|||
where
|
||||
T: Bundle + Send + Sync + 'static,
|
||||
{
|
||||
self.write_world(Remove::<T> {
|
||||
self.add_command(Remove::<T> {
|
||||
entity,
|
||||
phantom: PhantomData,
|
||||
})
|
||||
|
|
49
crates/bevy_ecs/src/world/entity_map.rs
Normal file
|
@ -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<Entity, Entity>,
|
||||
}
|
||||
|
||||
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<Entity, MapEntitiesError> {
|
||||
self.map
|
||||
.get(&entity)
|
||||
.cloned()
|
||||
.ok_or(MapEntitiesError::EntityNotFound(entity))
|
||||
}
|
||||
|
||||
pub fn keys(&self) -> impl Iterator<Item = Entity> + '_ {
|
||||
self.map.keys().cloned()
|
||||
}
|
||||
|
||||
pub fn values(&self) -> impl Iterator<Item = Entity> + '_ {
|
||||
self.map.values().cloned()
|
||||
}
|
||||
}
|
|
@ -1,3 +1,5 @@
|
|||
mod entity_map;
|
||||
mod world_builder;
|
||||
|
||||
pub use entity_map::*;
|
||||
pub use world_builder::*;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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::<Mesh, GltfLoader>();
|
||||
app.init_asset_loader::<GltfLoader>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Mesh> for GltfLoader {
|
||||
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh> {
|
||||
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,14 +26,234 @@ 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),
|
||||
}
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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)?;
|
||||
|
||||
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,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
|
||||
fn load_node(
|
||||
node: &gltf::Node,
|
||||
world_builder: &mut WorldChildBuilder,
|
||||
load_context: &mut LoadContext,
|
||||
buffer_data: &[Vec<u8>],
|
||||
) -> 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 parent.current_entity().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
||||
|
@ -55,70 +267,17 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: this should return a scene
|
||||
pub fn load_gltf(asset_path: &Path, bytes: Vec<u8>) -> Result<Mesh, GltfError> {
|
||||
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)?);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this when full gltf support is added
|
||||
panic!("no mesh found!")
|
||||
}
|
||||
|
||||
fn load_node(buffer_data: &[Vec<u8>], node: &gltf::Node, depth: i32) -> Result<Mesh, GltfError> {
|
||||
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);
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
return Ok(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(child) = node.children().next() {
|
||||
return Ok(load_node(buffer_data, &child, depth + 1)?);
|
||||
}
|
||||
|
||||
panic!("failed to find mesh")
|
||||
}
|
||||
|
||||
fn load_buffers(gltf: &gltf::Gltf, asset_path: &Path) -> Result<Vec<Vec<u8>>, GltfError> {
|
||||
fn load_buffers(
|
||||
gltf: &gltf::Gltf,
|
||||
load_context: &LoadContext,
|
||||
asset_path: &Path,
|
||||
) -> Result<Vec<Vec<u8>>, 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<Vec<Vec<u8>>, 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 {
|
||||
|
|
|
@ -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::<RenderGraph>().unwrap();
|
||||
add_pbr_graph(&mut render_graph, resources);
|
||||
|
||||
// add default StandardMaterial
|
||||
let mut materials = app
|
||||
.resources()
|
||||
.get_mut::<Assets<StandardMaterial>>()
|
||||
.unwrap();
|
||||
materials.set_untracked(
|
||||
Handle::<StandardMaterial>::default(),
|
||||
StandardMaterial {
|
||||
albedo: Color::PINK,
|
||||
shaded: false,
|
||||
albedo_texture: None,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,9 +8,10 @@ use bevy_render::{
|
|||
shader::{Shader, ShaderStage, ShaderStages},
|
||||
texture::TextureFormat,
|
||||
};
|
||||
use bevy_type_registry::TypeUuid;
|
||||
|
||||
pub const FORWARD_PIPELINE_HANDLE: Handle<PipelineDescriptor> =
|
||||
Handle::from_u128(131483623140127713893804825450360211204);
|
||||
Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 13148362314012771389);
|
||||
|
||||
pub(crate) fn build_forward_pipeline(shaders: &mut Assets<Shader>) -> PipelineDescriptor {
|
||||
PipelineDescriptor {
|
||||
|
|
|
@ -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::<Assets<Shader>>().unwrap();
|
||||
let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
|
||||
pipelines.set(
|
||||
pipelines.set_untracked(
|
||||
FORWARD_PIPELINE_HANDLE,
|
||||
build_forward_pipeline(&mut shaders),
|
||||
);
|
||||
|
|
|
@ -1,36 +0,0 @@
|
|||
use super::{BatchKey, Key};
|
||||
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub struct Batch<TKey, TValue, TData>
|
||||
where
|
||||
TKey: Key,
|
||||
{
|
||||
pub batch_key: BatchKey<TKey>,
|
||||
pub values: Vec<TValue>,
|
||||
pub data: TData,
|
||||
}
|
||||
|
||||
impl<TKey, TValue, TData> Batch<TKey, TValue, TData>
|
||||
where
|
||||
TKey: Key,
|
||||
{
|
||||
pub fn new(batch_key: BatchKey<TKey>, 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<Item = &TValue> {
|
||||
self.values.iter()
|
||||
}
|
||||
|
||||
pub fn get_key(&self, index: usize) -> Option<&TKey> {
|
||||
self.batch_key.0.get(index)
|
||||
}
|
||||
}
|
|
@ -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<T: Clone + Eq + Hash + 'static> Key for T {}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct BatchKey<TKey: Key>(pub Cow<'static, SmallVec<[TKey; 2]>>);
|
||||
|
||||
impl<TKey: Key> BatchKey<TKey> {
|
||||
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<TKey: Key> {
|
||||
batch_key: Option<BatchKey<TKey>>,
|
||||
keys: SmallVec<[Option<TKey>; 2]>,
|
||||
}
|
||||
|
||||
impl<TKey: Key> BatcherKeyState<TKey> {
|
||||
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<BatchKey<TKey>> {
|
||||
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::<SmallVec<[TKey; 2]>>(),
|
||||
));
|
||||
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<TKey, TValue, TData>
|
||||
where
|
||||
TKey: Key,
|
||||
{
|
||||
pub batches: HashMap<BatchKey<TKey>, Batch<TKey, TValue, TData>>,
|
||||
pub is_index: Vec<fn(&TKey) -> bool>,
|
||||
pub key_states: HashMap<TValue, BatcherKeyState<TKey>>,
|
||||
pub key_count: usize,
|
||||
}
|
||||
|
||||
impl<TKey: Key, TValue, TData> fmt::Debug for Batcher<TKey, TValue, TData>
|
||||
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::<Vec<_>>();
|
||||
|
||||
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<TKey, TValue, TData> Batcher<TKey, TValue, TData>
|
||||
where
|
||||
TKey: Key,
|
||||
TValue: Clone + Eq + Hash,
|
||||
TData: Default,
|
||||
{
|
||||
pub fn new(is_index: Vec<fn(&TKey) -> 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<TKey>) -> Option<&Batch<TKey, TValue, TData>> {
|
||||
self.batches.get(batch_key)
|
||||
}
|
||||
|
||||
pub fn get_batch_mut(
|
||||
&mut self,
|
||||
batch_key: &BatchKey<TKey>,
|
||||
) -> Option<&mut Batch<TKey, TValue, TData>> {
|
||||
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<Item = &Batch<TKey, TValue, TData>> {
|
||||
self.batches.values()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Batch<TKey, TValue, TData>> {
|
||||
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<HandleUntyped, Entity, Data> = Batcher::new(vec![
|
||||
HandleUntyped::is_handle::<A>,
|
||||
HandleUntyped::is_handle::<B>,
|
||||
]);
|
||||
|
||||
let e1 = Entity(1);
|
||||
let e2 = Entity(2);
|
||||
let e3 = Entity(3);
|
||||
|
||||
let a1: HandleUntyped = Handle::<A>::new().into();
|
||||
let b1: HandleUntyped = Handle::<B>::new().into();
|
||||
let c1: HandleUntyped = Handle::<C>::new().into();
|
||||
|
||||
let a2: HandleUntyped = Handle::<A>::new().into();
|
||||
let b2: HandleUntyped = Handle::<B>::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);
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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<Vec4> 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<f32> for Color {
|
||||
type Output = Color;
|
||||
|
||||
|
|
|
@ -73,8 +73,10 @@ impl Draw {
|
|||
self.render_commands.clear();
|
||||
}
|
||||
|
||||
pub fn set_pipeline(&mut self, pipeline: Handle<PipelineDescriptor>) {
|
||||
self.render_command(RenderCommand::SetPipeline { pipeline });
|
||||
pub fn set_pipeline(&mut self, pipeline: &Handle<PipelineDescriptor>) {
|
||||
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<PipelineDescriptor>,
|
||||
pipeline_handle: &Handle<PipelineDescriptor>,
|
||||
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()
|
||||
|
|
|
@ -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::<Texture, ImageTextureLoader>();
|
||||
app.init_asset_loader::<ImageTextureLoader>();
|
||||
}
|
||||
#[cfg(feature = "hdr")]
|
||||
{
|
||||
app.add_asset_loader::<Texture, HdrTextureLoader>();
|
||||
app.init_asset_loader::<HdrTextureLoader>();
|
||||
}
|
||||
|
||||
if app.resources().get::<ClearColor>().is_none() {
|
||||
|
|
|
@ -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<u32>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, TypeUuid)]
|
||||
#[uuid = "8ecbac0f-f545-4473-ad43-e1f4243af51e"]
|
||||
pub struct Mesh {
|
||||
pub primitive_topology: PrimitiveTopology,
|
||||
pub attributes: Vec<VertexAttribute>,
|
||||
|
@ -476,10 +478,10 @@ pub mod shape {
|
|||
|
||||
fn remove_current_mesh_resources(
|
||||
render_resource_context: &dyn RenderResourceContext,
|
||||
handle: Handle<Mesh>,
|
||||
handle: &Handle<Mesh>,
|
||||
) {
|
||||
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)
|
||||
|
|
|
@ -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<PipelineDescriptor>);
|
||||
fn set_pipeline(&mut self, pipeline_handle: &Handle<PipelineDescriptor>);
|
||||
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<u32>, instances: Range<u32>);
|
||||
|
|
|
@ -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<String>,
|
||||
pub layout: Option<PipelineLayout>,
|
||||
|
@ -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 {
|
||||
|
|
|
@ -77,14 +77,14 @@ impl PipelineCompiler {
|
|||
) -> Handle<Shader> {
|
||||
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::<Vec<String>>();
|
||||
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<PipelineDescriptor>,
|
||||
pipeline: &Handle<PipelineDescriptor>,
|
||||
specialization: &PipelineSpecialization,
|
||||
) -> Option<Handle<PipelineDescriptor>> {
|
||||
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<PipelineDescriptor>,
|
||||
shaders: &mut Assets<Shader>,
|
||||
source_pipeline: Handle<PipelineDescriptor>,
|
||||
source_pipeline: &Handle<PipelineDescriptor>,
|
||||
vertex_buffer_descriptors: &VertexBufferDescriptors,
|
||||
pipeline_specialization: &PipelineSpecialization,
|
||||
) -> Handle<PipelineDescriptor> {
|
||||
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(
|
||||
|
|
|
@ -56,7 +56,7 @@ impl RenderPipelines {
|
|||
RenderPipelines {
|
||||
pipelines: handles
|
||||
.into_iter()
|
||||
.map(|pipeline| RenderPipeline::new(*pipeline))
|
||||
.map(|pipeline| RenderPipeline::new(pipeline.clone_weak()))
|
||||
.collect::<Vec<RenderPipeline>>(),
|
||||
..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();
|
||||
|
|
|
@ -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<PipelineDescriptor>,
|
||||
handle: &Handle<PipelineDescriptor>,
|
||||
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
|
||||
|
|
|
@ -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<T> SystemNode for AssetRenderResourcesNode<T>
|
||||
where
|
||||
T: renderer::RenderResources,
|
||||
T: renderer::RenderResources + Asset,
|
||||
{
|
||||
fn get_system(&self, commands: &mut Commands) -> Box<dyn System> {
|
||||
let system = asset_render_resources_node_system::<T>.system();
|
||||
|
@ -555,7 +555,7 @@ where
|
|||
system.id(),
|
||||
RenderResourcesNodeState {
|
||||
command_queue: self.command_queue.clone(),
|
||||
uniform_buffer_arrays: UniformBufferArrays::<Handle<T>, T>::default(),
|
||||
uniform_buffer_arrays: UniformBufferArrays::<HandleId, T>::default(),
|
||||
dynamic_uniforms: self.dynamic_uniforms,
|
||||
},
|
||||
);
|
||||
|
@ -564,8 +564,8 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
fn asset_render_resources_node_system<T: RenderResources>(
|
||||
mut state: Local<RenderResourcesNodeState<Handle<T>, T>>,
|
||||
fn asset_render_resources_node_system<T: RenderResources + Asset>(
|
||||
mut state: Local<RenderResourcesNodeState<HandleId, T>>,
|
||||
assets: Res<Assets<T>>,
|
||||
mut asset_render_resource_bindings: ResMut<AssetRenderResourceBindings>,
|
||||
render_resource_context: Res<Box<dyn RenderResourceContext>>,
|
||||
|
@ -575,22 +575,20 @@ fn asset_render_resources_node_system<T: RenderResources>(
|
|||
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::<Vec<Handle<T>>>();
|
||||
let modified_assets = assets.ids().collect::<Vec<_>>();
|
||||
|
||||
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::<T>::weak(*asset_handle));
|
||||
setup_uniform_texture_resources::<T>(&asset, render_resource_context, &mut bindings);
|
||||
}
|
||||
|
||||
|
@ -604,9 +602,9 @@ fn asset_render_resources_node_system<T: RenderResources>(
|
|||
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::<T>::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<T: RenderResources>(
|
|||
} 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::<T>::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<T: RenderResources>(
|
|||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -76,7 +76,7 @@ impl RenderResourceContext for HeadlessRenderResourceContext {
|
|||
buffer
|
||||
}
|
||||
|
||||
fn create_shader_module(&self, _shader_handle: Handle<Shader>, _shaders: &Assets<Shader>) {}
|
||||
fn create_shader_module(&self, _shader_handle: &Handle<Shader>, _shaders: &Assets<Shader>) {}
|
||||
|
||||
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: &Shader) {}
|
||||
fn create_shader_module_from_source(&self, _shader_handle: &Handle<Shader>, _shader: &Shader) {}
|
||||
|
||||
fn remove_asset_resource_untyped(&self, handle: HandleUntyped, index: usize) {
|
||||
self.asset_resources.write().remove(&(handle, index));
|
||||
|
|
|
@ -77,7 +77,7 @@ pub trait RenderResource {
|
|||
fn write_buffer_bytes(&self, buffer: &mut [u8]);
|
||||
fn buffer_byte_len(&self) -> Option<usize>;
|
||||
// TODO: consider making these panic by default, but return non-options
|
||||
fn texture(&self) -> Option<Handle<Texture>>;
|
||||
fn texture(&self) -> Option<&Handle<Texture>>;
|
||||
}
|
||||
|
||||
pub trait RenderResources: Send + Sync + 'static {
|
||||
|
@ -136,7 +136,7 @@ macro_rules! impl_render_resource_bytes {
|
|||
Some(self.byte_len())
|
||||
}
|
||||
|
||||
fn texture(&self) -> Option<Handle<Texture>> {
|
||||
fn texture(&self) -> Option<&Handle<Texture>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -175,7 +175,7 @@ where
|
|||
Some(self.byte_len())
|
||||
}
|
||||
|
||||
fn texture(&self) -> Option<Handle<Texture>> {
|
||||
fn texture(&self) -> Option<&Handle<Texture>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -194,7 +194,7 @@ impl RenderResource for GlobalTransform {
|
|||
Some(std::mem::size_of::<[f32; 16]>())
|
||||
}
|
||||
|
||||
fn texture(&self) -> Option<Handle<Texture>> {
|
||||
fn texture(&self) -> Option<&Handle<Texture>> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T>(&self, handle: Handle<T>) -> Option<&RenderResourceBindings> {
|
||||
self.bindings.get(&HandleUntyped::from(handle))
|
||||
pub fn get<T: Asset>(&self, handle: &Handle<T>) -> Option<&RenderResourceBindings> {
|
||||
self.bindings.get(&handle.clone_weak_untyped())
|
||||
}
|
||||
|
||||
pub fn get_or_insert_mut<T>(&mut self, handle: Handle<T>) -> &mut RenderResourceBindings {
|
||||
pub fn get_or_insert_mut<T: Asset>(
|
||||
&mut self,
|
||||
handle: &Handle<T>,
|
||||
) -> &mut RenderResourceBindings {
|
||||
self.bindings
|
||||
.entry(HandleUntyped::from(handle))
|
||||
.entry(handle.clone_weak_untyped())
|
||||
.or_insert_with(RenderResourceBindings::default)
|
||||
}
|
||||
|
||||
pub fn get_mut<T>(&mut self, handle: Handle<T>) -> Option<&mut RenderResourceBindings> {
|
||||
self.bindings.get_mut(&HandleUntyped::from(handle))
|
||||
pub fn get_mut<T: Asset>(&mut self, handle: &Handle<T>) -> Option<&mut RenderResourceBindings> {
|
||||
self.bindings.get_mut(&handle.clone_weak_untyped())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Shader>, shaders: &Assets<Shader>);
|
||||
fn create_shader_module_from_source(&self, shader_handle: Handle<Shader>, shader: &Shader);
|
||||
fn create_shader_module(&self, shader_handle: &Handle<Shader>, shaders: &Assets<Shader>);
|
||||
fn create_shader_module_from_source(&self, shader_handle: &Handle<Shader>, 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<T>(&self, handle: Handle<T>, resource: RenderResourceId, index: usize)
|
||||
where
|
||||
T: 'static,
|
||||
pub fn set_asset_resource<T>(
|
||||
&self,
|
||||
handle: &Handle<T>,
|
||||
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<T>(&self, handle: Handle<T>, index: usize) -> Option<RenderResourceId>
|
||||
pub fn get_asset_resource<T>(
|
||||
&self,
|
||||
handle: &Handle<T>,
|
||||
index: usize,
|
||||
) -> Option<RenderResourceId>
|
||||
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<T>(&self, handle: Handle<T>, index: usize)
|
||||
pub fn remove_asset_resource<T>(&self, handle: &Handle<T>, 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Self::Item> {
|
||||
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;
|
||||
|
|
|
@ -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<T>(
|
||||
pub fn asset_shader_defs_system<T: Asset>(
|
||||
assets: Res<Assets<T>>,
|
||||
mut query: Query<(&Handle<T>, &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
|
||||
|
|
|
@ -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<Texture> for HdrTextureLoader {
|
||||
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Texture> {
|
||||
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<Texture> 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<Texture> 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] {
|
||||
|
|
|
@ -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<Texture> for ImageTextureLoader {
|
||||
fn from_bytes(&self, asset_path: &Path, bytes: Vec<u8>) -> Result<Texture> {
|
||||
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<Texture> 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<Texture> 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<Texture> 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] {
|
||||
|
|
|
@ -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<u8>,
|
||||
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<Texture>,
|
||||
handle: &Handle<Texture>,
|
||||
) {
|
||||
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<Handle<Texture>> {
|
||||
fn resource_type(&self) -> Option<RenderResourceType> {
|
||||
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<Handle<Texture>> {
|
|||
None
|
||||
}
|
||||
|
||||
fn texture(&self) -> Option<Handle<Texture>> {
|
||||
*self
|
||||
fn texture(&self) -> Option<&Handle<Texture>> {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,7 +172,7 @@ impl RenderResource for Handle<Texture> {
|
|||
None
|
||||
}
|
||||
|
||||
fn texture(&self) -> Option<Handle<Texture>> {
|
||||
Some(*self)
|
||||
fn texture(&self) -> Option<&Handle<Texture>> {
|
||||
Some(self)
|
||||
}
|
||||
}
|
||||
|
|
25
crates/bevy_scene/src/command.rs
Normal file
|
@ -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<Scene>,
|
||||
}
|
||||
|
||||
impl Command for SpawnScene {
|
||||
fn write(self: Box<Self>, _world: &mut World, resources: &mut Resources) {
|
||||
let mut spawner = resources.get_mut::<SceneSpawner>().unwrap();
|
||||
spawner.spawn(self.scene_handle);
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SpawnSceneCommands {
|
||||
fn spawn_scene(&mut self, scene: Handle<Scene>) -> &mut Self;
|
||||
}
|
||||
|
||||
impl SpawnSceneCommands for Commands {
|
||||
fn spawn_scene(&mut self, scene_handle: Handle<Scene>) -> &mut Self {
|
||||
self.add_command(SpawnScene { scene_handle })
|
||||
}
|
||||
}
|
117
crates/bevy_scene/src/dynamic_scene.rs
Normal file
|
@ -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<Entity>,
|
||||
}
|
||||
|
||||
pub struct Entity {
|
||||
pub entity: u32,
|
||||
pub components: Vec<DynamicProperties>,
|
||||
}
|
||||
|
||||
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::<TypeRegistry>().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<String, ron::Error> {
|
||||
serialize_ron(SceneSerializer::new(self, registry))
|
||||
}
|
||||
|
||||
pub fn get_scene(&self, resources: &Resources) -> Result<Scene, DynamicSceneToWorldError> {
|
||||
let mut world = World::default();
|
||||
self.write_to_world(&mut world, resources)?;
|
||||
Ok(Scene::new(world))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_ron<S>(serialize: S) -> Result<String, ron::Error>
|
||||
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())
|
||||
}
|
|
@ -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::<Scene>()
|
||||
.add_asset_loader::<Scene, SceneLoader>()
|
||||
app.add_asset::<DynamicScene>()
|
||||
.add_asset::<Scene>()
|
||||
.init_asset_loader::<SceneLoader>()
|
||||
.init_resource::<SceneSpawner>()
|
||||
.add_stage_after(stage::EVENT_UPDATE, SCENE_STAGE)
|
||||
.add_system_to_stage(SCENE_STAGE, scene_spawner_system.thread_local_system());
|
||||
|
|
|
@ -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<Entity>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Entity {
|
||||
pub entity: u32,
|
||||
pub components: Vec<DynamicProperties>,
|
||||
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
|
||||
}
|
||||
|
||||
// TODO: move to AssetSaver when it is implemented
|
||||
pub fn serialize_ron(&self, registry: &PropertyTypeRegistry) -> Result<String, ron::Error> {
|
||||
serialize_ron(SceneSerializer::new(self, registry))
|
||||
pub fn new(world: World) -> Self {
|
||||
Self { world }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn serialize_ron<S>(serialize: S) -> Result<String, ron::Error>
|
||||
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())
|
||||
}
|
||||
|
|
|
@ -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<Scene> for SceneLoader {
|
||||
fn from_bytes(&self, _asset_path: &Path, bytes: Vec<u8>) -> Result<Scene> {
|
||||
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] {
|
|
@ -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<u32, bevy_ecs::Entity>,
|
||||
entity_map: EntityMap,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
|
@ -24,10 +24,12 @@ impl InstanceId {
|
|||
#[derive(Default)]
|
||||
pub struct SceneSpawner {
|
||||
spawned_scenes: HashMap<Handle<Scene>, Vec<InstanceId>>,
|
||||
spawned_dynamic_scenes: HashMap<Handle<DynamicScene>, Vec<InstanceId>>,
|
||||
spawned_instances: HashMap<InstanceId, InstanceInfo>,
|
||||
scene_asset_event_reader: EventReader<AssetEvent<Scene>>,
|
||||
scenes_to_instance: Vec<Handle<Scene>>,
|
||||
scenes_to_despawn: Vec<Handle<Scene>>,
|
||||
scene_asset_event_reader: EventReader<AssetEvent<DynamicScene>>,
|
||||
dynamic_scenes_to_spawn: Vec<Handle<DynamicScene>>,
|
||||
scenes_to_spawn: Vec<Handle<Scene>>,
|
||||
scenes_to_despawn: Vec<Handle<DynamicScene>>,
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
@ -35,33 +37,99 @@ 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<Scene> },
|
||||
NonExistentScene { handle: Handle<DynamicScene> },
|
||||
#[error("Scene does not exist. Perhaps it is still loading?")]
|
||||
NonExistentRealScene { handle: Handle<Scene> },
|
||||
}
|
||||
|
||||
impl SceneSpawner {
|
||||
pub fn spawn(&mut self, scene_handle: Handle<Scene>) {
|
||||
self.scenes_to_instance.push(scene_handle);
|
||||
pub fn spawn_dynamic(&mut self, scene_handle: Handle<DynamicScene>) {
|
||||
self.dynamic_scenes_to_spawn.push(scene_handle);
|
||||
}
|
||||
|
||||
pub fn despawn(&mut self, scene_handle: Handle<Scene>) {
|
||||
pub fn spawn(&mut self, scene_handle: Handle<Scene>) {
|
||||
self.scenes_to_spawn.push(scene_handle);
|
||||
}
|
||||
|
||||
pub fn despawn(&mut self, scene_handle: Handle<DynamicScene>) {
|
||||
self.scenes_to_despawn.push(scene_handle);
|
||||
}
|
||||
|
||||
pub fn despawn_sync(
|
||||
&mut self,
|
||||
world: &mut World,
|
||||
scene_handle: Handle<Scene>,
|
||||
scene_handle: Handle<DynamicScene>,
|
||||
) -> 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_dynamic_sync(
|
||||
&mut self,
|
||||
world: &mut World,
|
||||
resources: &Resources,
|
||||
scene_handle: &Handle<DynamicScene>,
|
||||
) -> Result<(), SceneSpawnError> {
|
||||
let instance_id = InstanceId::new();
|
||||
let mut instance_info = InstanceInfo {
|
||||
entity_map: EntityMap::default(),
|
||||
};
|
||||
Self::spawn_dynamic_internal(world, resources, scene_handle, &mut instance_info)?;
|
||||
self.spawned_instances.insert(instance_id, instance_info);
|
||||
let spawned = self
|
||||
.spawned_dynamic_scenes
|
||||
.entry(scene_handle.clone())
|
||||
.or_insert_with(Vec::new);
|
||||
spawned.push(instance_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_dynamic_internal(
|
||||
world: &mut World,
|
||||
resources: &Resources,
|
||||
scene_handle: &Handle<DynamicScene>,
|
||||
instance_info: &mut InstanceInfo,
|
||||
) -> Result<(), SceneSpawnError> {
|
||||
let type_registry = resources.get::<TypeRegistry>().unwrap();
|
||||
let component_registry = type_registry.component.read();
|
||||
let scenes = resources.get::<Assets<DynamicScene>>().unwrap();
|
||||
let scene = scenes
|
||||
.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
|
||||
// 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
|
||||
.get_with_name(&component.type_name)
|
||||
.ok_or(SceneSpawnError::UnregisteredComponent {
|
||||
type_name: component.type_name.to_string(),
|
||||
})?;
|
||||
if world.has_component_type(entity, component_registration.ty) {
|
||||
if component.type_name != "Camera" {
|
||||
component_registration.apply_property_to_entity(world, entity, component);
|
||||
}
|
||||
} else {
|
||||
component_registration
|
||||
.add_property_to_entity(world, resources, entity, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -74,9 +142,42 @@ impl SceneSpawner {
|
|||
) -> 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)?;
|
||||
let type_registry = resources.get::<TypeRegistry>().unwrap();
|
||||
let component_registry = type_registry.component.read();
|
||||
let scenes = resources.get::<Assets<Scene>>().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
|
||||
|
@ -86,56 +187,22 @@ impl SceneSpawner {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn spawn_internal(
|
||||
world: &mut World,
|
||||
resources: &Resources,
|
||||
scene_handle: Handle<Scene>,
|
||||
instance_info: &mut InstanceInfo,
|
||||
) -> Result<(), SceneSpawnError> {
|
||||
let type_registry = resources.get::<TypeRegistry>().unwrap();
|
||||
let component_registry = type_registry.component.read();
|
||||
let scenes = resources.get::<Assets<Scene>>().unwrap();
|
||||
let scene = scenes
|
||||
.get(&scene_handle)
|
||||
.ok_or(SceneSpawnError::NonExistentScene {
|
||||
handle: scene_handle,
|
||||
})?;
|
||||
|
||||
for scene_entity in scene.entities.iter() {
|
||||
let entity = *instance_info
|
||||
.entity_map
|
||||
.entry(scene_entity.entity)
|
||||
.or_insert_with(|| world.reserve_entity());
|
||||
for component in scene_entity.components.iter() {
|
||||
let component_registration = component_registry
|
||||
.get_with_name(&component.type_name)
|
||||
.ok_or(SceneSpawnError::UnregisteredComponent {
|
||||
type_name: component.type_name.to_string(),
|
||||
})?;
|
||||
if world.has_component_type(entity, component_registration.ty) {
|
||||
if component.type_name != "Camera" {
|
||||
component_registration.apply_component_to_entity(world, entity, component);
|
||||
}
|
||||
} else {
|
||||
component_registration
|
||||
.add_component_to_entity(world, resources, entity, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn update_spawned_scenes(
|
||||
&mut self,
|
||||
world: &mut World,
|
||||
resources: &Resources,
|
||||
scene_handles: &[Handle<Scene>],
|
||||
scene_handles: &[Handle<DynamicScene>],
|
||||
) -> 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_dynamic_sync(world, resources, &scene_handle) {
|
||||
Ok(_) => {}
|
||||
Err(SceneSpawnError::NonExistentScene { .. }) => {
|
||||
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::NonExistentScene { .. }) => {
|
||||
self.scenes_to_instance.push(scene_handle)
|
||||
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::<SceneSpawner>().unwrap();
|
||||
let scene_asset_events = resources.get::<Events<AssetEvent<Scene>>>().unwrap();
|
||||
let scene_asset_events = resources.get::<Events<AssetEvent<DynamicScene>>>().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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
|
||||
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,
|
||||
})?;
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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<Mesh> = Handle::from_u128(142404619811301375266013514540294236421);
|
||||
pub const QUAD_HANDLE: Handle<Mesh> = 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::<ColorMaterial>.system(),
|
||||
);
|
||||
|
||||
let resources = app.resources();
|
||||
let resources = app.resources_mut();
|
||||
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
|
||||
render_graph.add_sprite_graph(resources);
|
||||
|
||||
let mut meshes = resources.get_mut::<Assets<Mesh>>().unwrap();
|
||||
meshes.set(
|
||||
|
||||
let mut color_materials = resources.get_mut::<Assets<ColorMaterial>>().unwrap();
|
||||
color_materials.set_untracked(Handle::<ColorMaterial>::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::<Assets<ColorMaterial>>().unwrap();
|
||||
color_materials.add_default(ColorMaterial::default());
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,12 +11,13 @@ use bevy_render::{
|
|||
shader::{Shader, ShaderStage, ShaderStages},
|
||||
texture::TextureFormat,
|
||||
};
|
||||
use bevy_type_registry::TypeUuid;
|
||||
|
||||
pub const SPRITE_PIPELINE_HANDLE: Handle<PipelineDescriptor> =
|
||||
Handle::from_u128(278534784033876544639935131272264723170);
|
||||
Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 2785347840338765446);
|
||||
|
||||
pub const SPRITE_SHEET_PIPELINE_HANDLE: Handle<PipelineDescriptor> =
|
||||
Handle::from_u128(90168858051802816124217444474933884151);
|
||||
Handle::weak_from_u64(PipelineDescriptor::TYPE_UUID, 9016885805180281612);
|
||||
|
||||
pub fn build_sprite_sheet_pipeline(shaders: &mut Assets<Shader>) -> PipelineDescriptor {
|
||||
PipelineDescriptor {
|
||||
|
@ -150,8 +151,8 @@ impl SpriteRenderGraphBuilder for RenderGraph {
|
|||
|
||||
let mut pipelines = resources.get_mut::<Assets<PipelineDescriptor>>().unwrap();
|
||||
let mut shaders = resources.get_mut::<Assets<Shader>>().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),
|
||||
);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<Texture>,
|
||||
|
@ -138,9 +140,9 @@ impl TextureAtlas {
|
|||
self.textures.is_empty()
|
||||
}
|
||||
|
||||
pub fn get_texture_index(&self, texture: Handle<Texture>) -> Option<usize> {
|
||||
pub fn get_texture_index(&self, texture: &Handle<Texture>) -> Option<usize> {
|
||||
self.texture_handles
|
||||
.as_ref()
|
||||
.and_then(|texture_handles| texture_handles.get(&texture).cloned())
|
||||
.and_then(|texture_handles| texture_handles.get(texture).cloned())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
font_atlases: HashMap<FontSizeKey, Vec<FontAtlas>>,
|
||||
|
@ -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,
|
||||
|
|