diff --git a/Cargo.toml b/Cargo.toml index 231b5ff..34c95bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,11 @@ bevy = "0.11.0" bevy_prototype_lyon = "0.9.0" trybuild = "1.0.71" +[[example]] +name = "asset_schematic" +path = "examples/asset_schematic.rs" +required-features = ["ron", "auto_name", "custom_schematics", "bevy_sprite"] + [[example]] name = "basic_schematic" path = "examples/basic_schematic.rs" @@ -165,6 +170,16 @@ name = "templates" path = "examples/templates.rs" required-features = ["ron", "auto_name", "custom_schematics", "bevy_sprite", "yaml"] +[[example]] +name = "bevy_asset_loading" +path = "examples/bevy/asset_loading.rs" +required-features = ["ron", "auto_name", "custom_schematics"] + +[[example]] +name = "bevy_sprite_sheet" +path = "examples/bevy/sprite_sheet.rs" +required-features = ["ron", "auto_name", "custom_schematics", "bevy_sprite"] + [[example]] name = "bevy_ui" path = "examples/bevy/ui.rs" diff --git a/README.md b/README.md index 158b30b..36dc6b0 100644 --- a/README.md +++ b/README.md @@ -58,8 +58,15 @@ This crate can be used for: > ( > name: "Puppy", > schematics: { - > "game::image::GameImage": ( - > handle: AssetPath("textures/puppy.png"), + > "game::level::Level": ( + > // Load by path: + > background: AssetPath("textures/bg.png"), + > // Or define assets inline: + > map: Asset(( + > name: "Level 1", + > size: (20, 20), + > // ... + > )) > ), > }, > ) diff --git a/assets/examples/asset_schematic/Level1.prototype.ron b/assets/examples/asset_schematic/Level1.prototype.ron new file mode 100644 index 0000000..b8fdb0f --- /dev/null +++ b/assets/examples/asset_schematic/Level1.prototype.ron @@ -0,0 +1,14 @@ +( + name: "Level1", + schematics: { + "asset_schematic::CurrentLevel": ( + // The `InlinableProtoAsset::Asset` variant allows us to define our asset inline. + level: Asset(( + name: "Tutorial", + // Both `InlinableProtoAsset` and the standard `ProtoAsset` contain an `AssetPath` + // variant for cases where we want to reference an asset defined in a separate file. + player: AssetPath("textures/platformer/player/p1_front.png") + )) + ), + } +) \ No newline at end of file diff --git a/assets/examples/bevy/asset_loading/Camera.prototype.ron b/assets/examples/bevy/asset_loading/Camera.prototype.ron new file mode 100644 index 0000000..31e69a8 --- /dev/null +++ b/assets/examples/bevy/asset_loading/Camera.prototype.ron @@ -0,0 +1,15 @@ +( + name: "Camera", + schematics: { + "bevy_proto::custom::Camera3dBundle": ( + transform: ( + translation: ( + x: 0, + y: 3.0, + z: 10.0 + ), + rotation: (-0.14521314, -0.0, -0.0, 0.9894004), + ) + ) + } +) \ No newline at end of file diff --git a/assets/examples/bevy/asset_loading/Cube.prototype.ron b/assets/examples/bevy/asset_loading/Cube.prototype.ron new file mode 100644 index 0000000..162854c --- /dev/null +++ b/assets/examples/bevy/asset_loading/Cube.prototype.ron @@ -0,0 +1,29 @@ +( + name: "Cube", + schematics: { + "bevy_proto::custom::MaterialMeshBundle": ( + // Mesh is an `AssetSchematic` which has a dedicated `MeshInput` type used for + // defining the mesh within a prototype file. + // The `MeshInput` type is an enum with various variants for primitive shapes. + // Here, we're using the `Cube` variant: + mesh: Asset(Cube(( + size: 2.0, + ))), + material: Asset(( + base_color: Rgba( + red: 0.8, + green: 0.7, + blue: 0.6, + alpha: 1.0 + ), + )), + transform: ( + translation: ( + x: 0.0, + y: 0.0, + z: 0.0 + ) + ), + ) + }, +) diff --git a/assets/examples/bevy/asset_loading/Light.prototype.ron b/assets/examples/bevy/asset_loading/Light.prototype.ron new file mode 100644 index 0000000..848b0e7 --- /dev/null +++ b/assets/examples/bevy/asset_loading/Light.prototype.ron @@ -0,0 +1,14 @@ +( + name: "Light", + schematics: { + "bevy_proto::custom::PointLightBundle": ( + transform: ( + translation: ( + x: 4.0, + y: 5.0, + z: 4.0 + ), + ) + ) + } +) \ No newline at end of file diff --git a/assets/examples/bevy/asset_loading/Monkey.prototype.ron b/assets/examples/bevy/asset_loading/Monkey.prototype.ron new file mode 100644 index 0000000..6f2ccf1 --- /dev/null +++ b/assets/examples/bevy/asset_loading/Monkey.prototype.ron @@ -0,0 +1,23 @@ +( + name: "Monkey", + schematics: { + "bevy_proto::custom::MaterialMeshBundle": ( + mesh: AssetPath("examples/bevy/asset_loading/monkey/Monkey.gltf#Mesh0/Primitive0"), + material: Asset(( + base_color: Rgba( + red: 0.8, + green: 0.7, + blue: 0.6, + alpha: 1.0 + ), + )), + transform: ( + translation: ( + x: -3.0, + y: 0.0, + z: 0.0 + ) + ), + ) + } +) \ No newline at end of file diff --git a/assets/examples/bevy/asset_loading/Sphere.prototype.ron b/assets/examples/bevy/asset_loading/Sphere.prototype.ron new file mode 100644 index 0000000..0f873d9 --- /dev/null +++ b/assets/examples/bevy/asset_loading/Sphere.prototype.ron @@ -0,0 +1,29 @@ +( + name: "Sphere", + schematics: { + "bevy_proto::custom::MaterialMeshBundle": ( + // Mesh is an `AssetSchematic` which has a dedicated `MeshInput` type used for + // defining the mesh within a prototype file. + // The `MeshInput` type is an enum with various variants for primitive shapes. + // Here, we're using the `Cube` variant: + mesh: Asset(UvSphere(( + radius: 1.0, + ))), + material: Asset(( + base_color: Rgba( + red: 0.8, + green: 0.7, + blue: 0.6, + alpha: 1.0 + ), + )), + transform: ( + translation: ( + x: 3.0, + y: 0.0, + z: 0.0 + ) + ), + ) + } +) diff --git a/assets/examples/bevy/asset_loading/monkey/Monkey.gltf b/assets/examples/bevy/asset_loading/monkey/Monkey.gltf new file mode 100644 index 0000000..f7b34ed --- /dev/null +++ b/assets/examples/bevy/asset_loading/monkey/Monkey.gltf @@ -0,0 +1,122 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.3.48", + "version" : "2.0" + }, + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0 + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "Suzanne" + } + ], + "materials" : [ + { + "doubleSided" : true, + "emissiveFactor" : [ + 0, + 0, + 0 + ], + "name" : "Material", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 0.7612724900245667, + 0.5813313126564026, + 0.41983750462532043, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.4000000059604645 + } + } + ], + "meshes" : [ + { + "name" : "Suzanne", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3, + "material" : 0 + } + ] + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 2012, + "max" : [ + 1.325934886932373, + 0.9392361640930176, + 0.8223199844360352 + ], + "min" : [ + -1.325934886932373, + -0.9704862236976624, + -0.7782661318778992 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 2012, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 2012, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 11808, + "type" : "SCALAR" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 24144, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 24144, + "byteOffset" : 24144 + }, + { + "buffer" : 0, + "byteLength" : 16096, + "byteOffset" : 48288 + }, + { + "buffer" : 0, + "byteLength" : 23616, + "byteOffset" : 64384 + } + ], + "buffers" : [ + { + "byteLength" : 88000, + "uri" : "data:application/octet-stream;base64,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NgA3AC0AMwA3AC0ANwAgADgAOQA6ADgAOgA7ADkAPAA9ADkAPQA6ADoAPQAwADoAMAAuADsAOgAuADsALgAoACAANwA+ACAAPgAhADcANgA/ADcAPwA+AD4APwBAAD4AQABBACEAPgBBACEAQQAiAEIAQwBEAEIARABFAEMAOAA7AEMAOwBEAEQAOwAoAEQAKAAlAEUARAAlAEUAJQAkADYARgBHADYARwA/AEYASABJAEYASQBHAEcASQBKAEcASgBLAD8ARwBLAD8ASwBAAEwATQBOAEwATgBPAE0AUABRAE0AUQBOAE4AUQA4AE4AOABDAE8ATgBDAE8AQwBCADQAUgBTADQAUwA1AFIAVABVAFIAVQBTAFMAVQBIAFMASABGADUAUwBGADUARgA2AFAAVgBXAFAAVwBRAFYAWABZAFYAWQBXAFcAWQA8AFcAPAA5AFEAVwA5AFEAOQA4AFQAWgBbAFQAWwBVAFoAXABdAFoAXQBbAFsAXQBeAFsAXgBfAFUAWwBfAFUAXwBIAGAAYQBiAGAAYgBjAGEAZABlAGEAZQBiAGIAZQBYAGIAWABWAGMAYgBWAGMAVgBQAEgAXwBmAEgAZgBJAF8AXgBnAF8AZwBmAGYAZwBoAGYAaABpAEkAZgBpAEkAaQBKAGoAawBsAGoAbABtAGsAYABjAGsAYwBsAGwAYwBQAGwAUABNAG0AbABNAG0ATQBMAF4AbgBvAF4AbwBnAG4AcABxAG4AcQBvAG8AcQByAG8AcgBzAGcAbwBzAGcAcwBoAHQAdQB2AHQAdgB3AHUAeAB5AHUAeQB2AHYAeQBgAHYAYABrAHcAdgBrAHcAawBqAFwAegB7AFwAewBdAHoAfAB9AHoAfQB7AHsAfQBwAHsAcABuAF0AewBuAF0AbgBeAHgAfgB/AHgAfwB5AH4AgACBAH4AgQB/AH8AgQBkAH8AZABhAHkAfwBhAHkAYQBgAHwAggCDAHwAgwB9AIIAhACFAIIAhQCDAIMAhQCGAIMAhgCHAH0AgwCHAH0AhwBwAIgAiQCKAIgAigCLAIkAjACNAIkAjQCKAIoAjQCAAIoAgAB+AIsAigB+AIsAfgB4AHAAhwCOAHAAjgBxAIcAhgCPAIcAjwCOAI4AjwCQAI4AkACRAHEAjgCRAHEAkQByAJIAkwCUAJIAlACVAJMAiACLAJMAiwCUAJQAiwB4AJQAeAB1AJUAlAB1AJUAdQB0AIYAlgCXAIYAlwCPAJYACAATAJYAEwCXAJcAEwAXAJcAFwCYAI8AlwCYAI8AmACQAB0AHACZAB0AmQCaABwAEQCbABwAmwCZAJkAmwCIAJkAiACTAJoAmQCTAJoAkwCSAIQAnACdAIQAnQCFAJwAAAADAJwAAwCdAJ0AAwAIAJ0ACACWAIUAnQCWAIUAlgCGABEAEACeABEAngCbABAADwCfABAAnwCeAJ4AnwCMAJ4AjACJAJsAngCJAJsAiQCIAAAAnACgAAAAoAChAJwAhACiAJwAogCgAKAAogCjAKAAowCkAKEAoACkAKEApAClAKYApwCoAKYAqACpAKcAjACfAKcAnwCoAKgAnwAPAKgADwCqAKkAqACqAKkAqgCrAIQAggCsAIQArACiAIIAfACtAIIArQCsAKwArQCuAKwArgCvAKIArACvAKIArwCjALAAsQCyALAAsgCzALEAgACNALEAjQCyALIAjQCMALIAjACnALMAsgCnALMApwCmAHwAegC0AHwAtACtAHoAXAC1AHoAtQC0ALQAtQC2ALQAtgC3AK0AtAC3AK0AtwCuALgAuQC6ALgAugC7ALkAZACBALkAgQC6ALoAgQCAALoAgACxALsAugCxALsAsQCwAFwAWgC8AFwAvAC1AFoAVAC9AFoAvQC8ALwAvQC+ALwAvgC/ALUAvAC/ALUAvwC2AMAAwQDCAMAAwgDDAMEAWABlAMEAZQDCAMIAZQBkAMIAZAC5AMMAwgC5AMMAuQC4AFQAUgDEAFQAxAC9AFIANADFAFIAxQDEAMQAxQDGAMQAxgDHAL0AxADHAL0AxwC+AMgAyQDKAMgAygDLAMkAPABZAMkAWQDKAMoAWQBYAMoAWADBAMsAygDBAMsAwQDAADQAMgDMADQAzADFADIALADNADIAzQDMAMwAzQDOAMwAzgDPAMUAzADPAMUAzwDGANAA0QDSANAA0gDTANEAMAA9ANEAPQDSANIAPQA8ANIAPADJANMA0gDJANMAyQDIACwAKgDUACwA1ADNACoABADVACoA1QDUANQA1QDWANQA1gDXAM0A1ADXAM0A1wDOANgA2QDaANgA2gDbANkADQAxANkAMQDaANoAMQAwANoAMADRANsA2gDRANsA0QDQAAQAAQDcAAQA3ADVAAEAAAChAAEAoQDcANwAoQClANwApQDdANUA3ADdANUA3QDWAKsAqgDeAKsA3gDfAKoADwAOAKoADgDeAN4ADgANAN4ADQDZAN8A3gDZAN8A2QDYAOAA4QDiAOAA4gDjANYA3QDiANYA4gDhAKUA4wDiAKUA4gDdAKsA3wDkAKsA5ADlANgA5gDkANgA5ADfAOcA5QDkAOcA5ADmAM4A1wDoAM4A6ADpANYA4QDoANYA6ADXAOAA6QDoAOAA6ADhAOcA5gDqAOcA6gDrANgA2wDqANgA6gDmANAA6wDqANAA6gDbAOAA7ADtAOAA7QDpAMYAzwDtAMYA7QDsAM4A6QDtAM4A7QDPANAA0wDuANAA7gDrAMgA7wDuAMgA7gDTAOcA6wDuAOcA7gDvAOAA8ADxAOAA8QDsAL4AxwDxAL4A8QDwAMYA7ADxAMYA8QDHAMgAywDyAMgA8gDvAMAA8wDyAMAA8gDLAOcA7wDyAOcA8gDzAOAA9AD1AOAA9QDwALYAvwD1ALYA9QD0AL4A8AD1AL4A9QC/AMAAwwD2AMAA9gDzALgA9wD2ALgA9gDDAOcA8wD2AOcA9gD3AOAA+AD5AOAA+QD0AK4AtwD5AK4A+QD4ALYA9AD5ALYA+QC3ALgAuwD6ALgA+gD3ALAA+wD6ALAA+gC7AOcA9wD6AOcA+gD7AOAA/AD9AOAA/QD4AKMArwD9AKMA/QD8AK4A+AD9AK4A/QCvALAAswD+ALAA/gD7AKYA/wD+AKYA/gCzAOcA+wD+AOcA/gD/AOAA4wAAAeAAAAH8AKUApAAAAaUAAAHjAKMA/AAAAaMAAAGkAKYAqQABAaYAAQH/AKsA5QABAasAAQGpAOcA/wABAecAAQHlAAIBAwEEAQIBBAEFAQMBBgEHAQMBBwEEAQQBBwEIAQQBCAEJAQUBBAEJAQUBCQEKAQgBCwEMAQgBDAEJAQsBDQEOAQsBDgEMAQwBDgEPAQwBDwEQAQkBDAEQAQkBEAEKAREBEgETAREBEwEUARIBFQEWARIBFgETARMBFgEGARMBBgEDARQBEwEDARQBAwECAQ0BFwEYAQ0BGAEOARcBGQEaARcBGgEYARgBGgEbARgBGwEcAQ4BGAEcAQ4BHAEPAR0BHgEfAR0BHwEgAR4BIQEiAR4BIgEfAR8BIgEVAR8BFQESASABHwESASABEgERARkBIwEkARkBJAEaASMBJQEmASMBJgEkASQBJgEnASQBJwEoARoBJAEoARoBKAEbASkBKgErASkBKwEsASoBLQEuASoBLgErASsBLgEhASsBIQEeASwBKwEeASwBHgEdASUBLwEwASUBMAEmAS8BMQEyAS8BMgEwATABMgEzATABMwE0ASYBMAE0ASYBNAEnATUBNgE3ATUBNwE4ATYBOQE6ATYBOgE3ATcBOgEtATcBLQEqATgBNwEqATgBKgEpATEBOwE8ATEBPAEyATsBPQE+ATsBPgE8ATwBPgE/ATwBPwFAATIBPAFAATIBQAEzAUEBQgFDAUEBQwFEAUIBRQFGAUIBRgFDAUMBRgFHAUMBRwFIAUQBQwFIAUQBSAFJAUoBSwFMAUoBTAFNAUsBTgFPAUsBTwFMAUwBTwFQAUwBUAFRAU0BTAFRAU0BUQFSAUUBUwFUAUUBVAFGAVMBVQFWAVMBVgFUAVQBVgFXAVQBVwFYAUYBVAFYAUYBWAFHAVkBWgFbAVkBWwFcAVoBXQFeAVoBXgFbAVsBXgFOAVsBTgFLAVwBWwFLAVwBSwFKAVUBXwFgAVUBYAFWAV8BYQFiAV8BYgFgAWABYgFjAWABYwFkAVYBYAFkAVYBZAFXAWUBZgFnAWUBZwFoAWYBaQFqAWYBagFnAWcBagFdAWcBXQFaAWgBZwFaAWgBWgFZAWEBawFsAWEBbAFiAWsBbQFuAWsBbgFsAWwBbgFvAWwBbwFwAWIBbAFwAWIBcAFjAXEBcgFzAXEBcwF0AXIBdQF2AXIBdgFzAXMBdgFpAXMBaQFmAXQBcwFmAXQBZgFlAW0BdwF4AW0BeAFuAXcBeQF6AXcBegF4AXgBegF7AXgBewF8AW4BeAF8AW4BfAFvAX0BfgF/AX0BfwGAAX4BgQGCAX4BggF/AX8BggF1AX8BdQFyAYABfwFyAYABcgFxAXkBgwGEAXkBhAF6AYMBhQGGAYMBhgGEAYQBhgGHAYQBhwGIAXoBhAGIAXoBiAF7AYkBigGLAYkBiwGMAYoBjQGOAYoBjgGLAYsBjgGBAYsBgQF+AYwBiwF+AYwBfgF9AYUBjwGQAYUBkAGGAY8BkQGSAY8BkgGQAZABkgGTAZABkwGUAYYBkAGUAYYBlAGHAZUBlgGXAZUBlwGYAZYBmQGaAZYBmgGXAZcBmgGNAZcBjQGKAZgBlwGKAZgBigGJAZEBmwGcAZEBnAGSAZsBnQGeAZsBngGcAZwBngGfAZwBnwGgAZIBnAGgAZIBoAGTAaEBogGjAaEBowGkAaIBpQGmAaIBpgGjAaMBpgGZAaMBmQGWAaQBowGWAaQBlgGVAZ0BpwGoAZ0BqAGeAacBqQGqAacBqgGoAagBqgGrAagBqwGsAZ4BqAGsAZ4BrAGfAa0BrgGvAa0BrwGwAa4BsQGyAa4BsgGvAa8BsgGlAa8BpQGiAbABrwGiAbABogGhAakBswG0AakBtAGqAbMBtQG2AbMBtgG0AbQBtgG3AbQBtwG4AaoBtAG4AaoBuAGrAbcBtgG5AbcBuQG6AbYBtQG7AbYBuwG5AbkBuwGxAbkBsQGuAboBuQGuAboBrgGtAbwBvQG+AbwBvgG/Ab0BwAHBAb0BwQG+Ab4BwQGfAb4BnwGsAb8BvgGsAb8BrAGrAaEBwgHDAaEBwwGwAcIBxAHFAcIBxQHDAcMBxQHGAcMBxgHHAbABwwHHAbABxwGtAcAByAHJAcAByQHBAcgBygHLAcgBywHJAckBywGTAckBkwGgAcEByQGgAcEBoAGfAZUBzAHNAZUBzQGkAcwBzgHPAcwBzwHNAc0BzwHEAc0BxAHCAaQBzQHCAaQBwgGhAdAB0QHSAdAB0gHTAdEBhwGUAdEBlAHSAdIBlAGTAdIBkwHLAdMB0gHLAdMBywHKAZUBmAHUAZUB1AHMAZgBiQHVAZgB1QHUAdQB1QHWAdQB1gHXAcwB1AHXAcwB1wHOAdgB2QHaAdgB2gHbAdkBewGIAdkBiAHaAdoBiAGHAdoBhwHRAdsB2gHRAdsB0QHQAYkBjAHcAYkB3AHVAYwBfQHdAYwB3QHcAdwB3QHeAdwB3gHfAdUB3AHfAdUB3wHWAeAB4QHiAeAB4gHjAeEBbwF8AeEBfAHiAeIBfAF7AeIBewHZAeMB4gHZAeMB2QHYAX0BgAHkAX0B5AHdAYABcQHlAYAB5QHkAeQB5QHmAeQB5gHnAd0B5AHnAd0B5wHeAegB6QHqAegB6gHrAekBYwFwAekBcAHqAeoBcAFvAeoBbwHhAesB6gHhAesB4QHgAXEBdAHsAXEB7AHlAXQBZQHtAXQB7QHsAewB7QHuAewB7gHvAeUB7AHvAeUB7wHmAfAB8QHyAfAB8gHzAfEBVwFkAfEBZAHyAfIBZAFjAfIBYwHpAfMB8gHpAfMB6QHoAWUBaAH0AWUB9AHtAWgBWQH1AWgB9QH0AfQB9QH2AfQB9gH3Ae0B9AH3Ae0B9wHuAfgB+QH6AfgB+gH7AfkBRwFYAfkBWAH6AfoBWAFXAfoBVwHxAfsB+gHxAfsB8QHwAVkBXAH8AVkB/AH1AVwBSgH9AVwB/QH8AfwB/QH+AfwB/gH/AfUB/AH/AfUB/wH2AQACAQICAgACAgIDAgECSQFIAQECSAECAgICSAFHAQICRwH5AQMCAgL5AQMC+QH4AUoBTQEEAkoBBAL9AU0BUgEFAk0BBQIEAgQCBQIGAgQCBgIHAv0BBAIHAv0BBwL+AQACCAIJAgACCQIBAggCCgILAggCCwIJAgkCCwIMAgkCDAINAgECCQINAgECDQJJAQwCDgIPAgwCDwIQAg4CEQISAg4CEgIPAg8CEgIGAg8CBgIFAhACDwIFAhACBQJSAbwBvwETArwBEwIUAr8BqwG4Ab8BuAETAhMCuAG3ARMCtwEVAhQCEwIVAhQCFQIWArcBugEXArcBFwIYAroBrQHHAboBxwEXAhcCxwHGARcCxgEZAhgCFwIZAhgCGQIaAhYCFQIbAhYCGwIcAhUCtwEdAhUCHQIbAhsCHQIeAhsCHgIfAhwCGwIfAhwCHwIgAh4CHQIhAh4CIQIiAh0CtwEYAh0CGAIhAiECGAIaAiECGgIjAiICIQIjAiICIwIkAgwCCwIlAgwCJQImAgsCCgInAgsCJwIlAiUCJwIgAiUCIAIfAiYCJQIfAiYCHwIeAiQCKAIpAiQCKQIiAigCEQIOAigCDgIpAikCDgIMAikCDAImAiICKQImAiICJgIeAioCKwIsAioCLAItAisCLgIvAisCLwIsAiwCLwIIASwCCAEHAS0CLAIHAS0CBwEGAQgBLwIwAggBMAILAS8CLgIxAi8CMQIwAjACMQIyAjACMgIzAgsBMAIzAgsBMwINATQCNQI2AjQCNgI3AjUCKgItAjUCLQI2AjYCLQIGATYCBgEWATcCNgIWATcCFgEVAQ0BMwI4Ag0BOAIXATMCMgI5AjMCOQI4AjgCOQI6AjgCOgI7AhcBOAI7AhcBOwIZATwCPQI+AjwCPgI/Aj0CNAI3Aj0CNwI+Aj4CNwIVAT4CFQEiAT8CPgIiAT8CIgEhARkBOwJAAhkBQAIjATsCOgJBAjsCQQJAAkACQQJCAkACQgJDAiMBQAJDAiMBQwIlATkBRAJFAjkBRQI6AUQCRgJHAkQCRwJFAkUCRwJIAkUCSAJJAjoBRQJJAjoBSQItAUoCSwJMAkoCTAJNAksCTgJPAksCTwJMAkwCTwI9AUwCPQE7AU0CTAI7AU0COwExATwCPwJQAjwCUAJRAj8CIQEuAT8CLgFQAlACLgEtAVACLQFJAlECUAJJAlECSQJIAjEBLwFSAjEBUgJNAi8BJQFDAi8BQwJSAlICQwJCAlICQgJTAk0CUgJTAk0CUwJKAlQCVQJWAlQCVgJXAlUCWAJZAlUCWQJWAlYCWQJaAlYCWgJbAlcCVgJbAlcCWwJGAloCWQJcAloCXAJdAlkCWAJeAlkCXgJcAlwCXgJfAlwCXwJgAl0CXAJgAl0CYAJOAkgCRwJhAkgCYQJiAkcCRgJbAkcCWwJhAmECWwJaAmECWgJjAmICYQJjAmICYwJkAloCXQJlAloCZQJjAl0CTgJLAl0CSwJlAmUCSwJKAmUCSgJmAmMCZQJmAmMCZgJkAmcCaAJpAmcCaQJqAmgCPAJRAmgCUQJpAmkCUQJIAmkCSAJiAmoCaQJiAmoCYgJkAkoCUwJrAkoCawJmAlMCQgJsAlMCbAJrAmsCbAJnAmsCZwJqAmYCawJqAmYCagJkAm0CbgJvAm0CbwJwAm4CcQJyAm4CcgJvAm8CcgJzAm8CcwJ0AnACbwJ0AnACdAJUAnUCdgJ3AnUCdwJ4AnYCeQJ6AnYCegJ3AncCegJ7AncCewJ8AngCdwJ8AngCfAJfAn0CfgJ/An0CfwKAAn4CgQKCAn4CggJ/An8CggJxAn8CcQJuAoACfwJuAoACbgJtAnkCgwKEAnkChAJ6AoMChQKGAoMChgKEAoQChgKHAoQChwKIAnoChAKIAnoCiAJ7AokCigKLAokCiwKMAooCjQKOAooCjgKLAosCjgKBAosCgQJ+AowCiwJ+AowCfgJ9AoUCjwKQAoUCkAKGAo8CkQKSAo8CkgKQApACkgKJApACiQKTAoYCkAKTAoYCkwKHApQClQKWApQClgKXApUCmAKZApUCmQKWApYCmQKNApYCjQKKApcClgKKApcCigKJApECmgKbApECmwKSApoCmAKVApoClQKbApsClQKUApsClAKXApICmwKXApIClwKJAlgCVQKcAlgCnAKdAlUCVAJ0AlUCdAKcApwCdAJzApwCcwKeAp0CnAKeAp0CngKfAnUCeAKgAnUCoAKhAngCXwJeAngCXgKgAqACXgJYAqACWAKdAqECoAKdAqECnQKfAp8CngKiAp8CogKjAp4CcwKkAp4CpAKiAqICpAKlAqICpQKmAqMCogKmAqMCpgKnAqgCqQKqAqgCqgKrAqkCdQKhAqkCoQKqAqoCoQKfAqoCnwKjAqsCqgKjAqsCowKnApgCrAKtApgCrQKZAqwCrgKvAqwCrwKtAq0CrwKwAq0CsAKxApkCrQKxApkCsQKNArICswK0ArICtAK1ArMCrgKsArMCrAK0ArQCrAKYArQCmAKaArUCtAKaArUCmgKRAo0CsQK2Ao0CtgKOArECsAK3ArECtwK2ArYCtwK4ArYCuAK5Ao4CtgK5Ao4CuQKBAroCuwK8AroCvAK9ArsCsgK1ArsCtQK8ArwCtQKRArwCkQKPAr0CvAKPAr0CjwKFAoECuQK+AoECvgKCArkCuAK/ArkCvwK+Ar4CvwLAAr4CwALBAoICvgLBAoICwQJxAsICwwLEAsICxALFAsMCugK9AsMCvQLEAsQCvQKFAsQChQKDAsUCxAKDAsUCgwJ5AnECwQLGAnECxgJyAsECwALHAsECxwLGAsYCxwKlAsYCpQKkAnICxgKkAnICpAJzAqgCyALJAqgCyQKpAsgCwgLFAsgCxQLJAskCxQJ5AskCeQJ2AqkCyQJ2AqkCdgJ1Aq4CygLLAq4CywKvAsoCwAK/AsoCvwLLAssCvwK4AssCuAK3Aq8CywK3Aq8CtwKwAroCwwLMAroCzAK7AsMCwgLNAsMCzQLMAswCzQKuAswCrgKzArsCzAKzArsCswKyAq4CzgLPAq4CzwLKAs4CpwKmAs4CpgLPAs8CpgKlAs8CpQLHAsoCzwLHAsoCxwLAAqgCqwLQAqgC0ALIAqsCpwLOAqsCzgLQAtACzgKuAtACrgLNAsgC0ALNAsgCzQLCAokCjALRAokC0QLSAowCfQLTAowC0wLRAtEC0wJJAdECSQENAtIC0QINAtICDQIMAlIB1ALVAlIB1QIQAtQChwKTAtQCkwLVAtUCkwKJAtUCiQLSAhAC1QLSAhAC0gIMAn0CgALWAn0C1gLTAoACbQLXAoAC1wLWAtYC1wLYAtYC2ALZAtMC1gLZAtMC2QJJAdoC2wLcAtoC3ALdAtsCewKIAtsCiALcAtwCiAKHAtwChwLUAt0C3ALUAt0C1AJSAW0CcALeAm0C3gLXAnACVALfAnAC3wLeAt4C3wLgAt4C4ALhAtcC3gLhAtcC4QLYAuIC4wLkAuIC5ALlAuMCXwJ8AuMCfALkAuQCfAJ7AuQCewLbAuUC5ALbAuUC2wLaAlQCVwLmAlQC5gLfAlcCRgJEAlcCRALmAuYCRAI5AeYCOQHnAt8C5gLnAt8C5wLgAj0BTwLoAj0B6ALpAk8CTgJgAk8CYALoAugCYAJfAugCXwLjAukC6ALjAukC4wLiAjUB6gLrAjUB6wI2AeoC7ALtAuoC7QLrAusC7QLgAusC4ALnAjYB6wLnAjYB5wI5AeIC7gLvAuIC7wLpAu4C8ALxAu4C8QLvAu8C8QI/Ae8CPwE+AekC7wI+AekCPgE9AewC8gLzAuwC8wLtAvIC9AL1AvIC9QLzAvMC9QLYAvMC2ALhAu0C8wLhAu0C4QLgAtoC9gL3AtoC9wLlAvYC+AL5AvYC+QL3AvcC+QLwAvcC8ALuAuUC9wLuAuUC7gLiAkEBRAH6AkEB+gL7AkQBSQHZAkQB2QL6AvoC2QLYAvoC2AL1AvsC+gL1AvsC9QL0AtoC3QL8AtoC/AL2At0CUgFRAd0CUQH8AvwCUQFQAfwCUAH9AvYC/AL9AvYC/QL4AjwCaAL+AjwC/gL/AmgCZwIAA2gCAAP+Av4CAAMBA/4CAQMCA/8C/gICA/8CAgMDAwEDAAMEAwEDBAMFAwADZwJsAgADbAIEAwQDbAJCAgQDQgIGAwUDBAMGAwUDBgMHAzQCPQIIAzQCCAMJAz0CPAL/Aj0C/wIIAwgD/wIDAwgDAwMKAwkDCAMKAwkDCgMLAwcDBgMMAwcDDAMNAwYDQgJBAgYDQQIMAwwDQQI6AgwDOgIOAw0DDAMOAw0DDgMPAyoCNQIQAyoCEAMRAzUCNAIJAzUCCQMQAxADCQMLAxADCwMSAxEDEAMSAxEDEgMTAw8DDgMUAw8DFAMVAw4DOgI5Ag4DOQIUAxQDOQIyAhQDMgIWAxUDFAMWAxUDFgMXAy4CKwIYAy4CGAMZAysCKgIRAysCEQMYAxgDEQMTAxgDEwMaAxkDGAMaAxkDGgMbAxcDFgMcAxcDHAMdAxYDMgIxAhYDMQIcAxwDMQIuAhwDLgIZAx0DHAMZAx0DGQMbAxsDGgMeAxsDHgMfAxoDEwMgAxoDIAMeAx4DIAMhAx4DIQMiAx8DHgMiAx8DIgMjAyQDJQMmAyQDJgMnAyUDFwMdAyUDHQMmAyYDHQMbAyYDGwMfAycDJgMfAycDHwMjAxMDEgMoAxMDKAMgAxIDCwMpAxIDKQMoAygDKQMqAygDKgMrAyADKAMrAyADKwMhAywDLQMuAywDLgMvAy0DDwMVAy0DFQMuAy4DFQMXAy4DFwMlAy8DLgMlAy8DJQMkAwsDCgMwAwsDMAMpAwoDAwMxAwoDMQMwAzADMQMyAzADMgMzAykDMAMzAykDMwMqAzQDNQM2AzQDNgM3AzUDBwMNAzUDDQM2AzYDDQMPAzYDDwMtAzcDNgMtAzcDLQMsAwMDAgM4AwMDOAMxAwIDAQM5AwIDOQM4AzgDOQM6AzgDOgM7AzEDOAM7AzEDOwMyAzoDOQM8AzoDPAM9AzkDAQMFAzkDBQM8AzwDBQMHAzwDBwM1Az0DPAM1Az0DNQM0AzoDPgM/AzoDPwM7Az4DIwMiAz4DIgM/Az8DIgMhAz8DIQNAAzsDPwNAAzsDQAMyAyQDJwNBAyQDQQNCAycDIwM+AycDPgNBA0EDPgM6A0EDOgM9A0IDQQM9A0IDPQM0AzIDQANDAzIDQwMzAyEDKwNDAyEDQwNAAyoDMwNDAyoDQwMrAywDLwNEAywDRAM3AyQDQgNEAyQDRAMvAzQDNwNEAzQDRANCAyACJwJFAyACRQNGAycCCgJHAycCRwNFA0UDRwNIA0UDSANJA0YDRQNJA0YDSQNKA0sDTANNA0sDTQNOA0wDEQIoAkwDKAJNA00DKAIkAk0DJAJPA04DTQNPA04DTwNQAxYCHAJRAxYCUQNSAxwCIAJGAxwCRgNRA1EDRgNKA1EDSgNTA1IDUQNTA1IDUwNUA1ADTwNVA1ADVQNWA08DJAIjAk8DIwJVA1UDIwIaAlUDGgJXA1YDVQNXA1YDVwNYA7wBFAJZA7wBWQNaAxQCFgJSAxQCUgNZA1kDUgNUA1kDVANbA1oDWQNbA1oDWwNcA1gDVwNdA1gDXQNeA1cDGgIZAlcDGQJdA10DGQLGAV0DxgFfA14DXQNfA14DXwNgAwoCCAJhAwoCYQNHAwgCAAJiAwgCYgNhA2EDYgNjA2EDYwNkA0cDYQNkA0cDZANIA2UDZgNnA2UDZwNoA2YDBgISAmYDEgJnA2cDEgIRAmcDEQJMA2gDZwNMA2gDTANLAwACAwJpAwACaQNiAwMC+AFqAwMCagNpA2kDagNrA2kDawNsA2IDaQNsA2IDbANjA20DbgNvA20DbwNwA24D/gEHAm4DBwJvA28DBwIGAm8DBgJmA3ADbwNmA3ADZgNlA/gB+wFxA/gBcQNqA/sB8AFyA/sBcgNxA3EDcgNzA3EDcwN0A2oDcQN0A2oDdANrA3UDdgN3A3UDdwN4A3YD9gH/AXYD/wF3A3cD/wH+AXcD/gFuA3gDdwNuA3gDbgNtA/AB8wF5A/ABeQNyA/MB6AF6A/MBegN5A3kDegN7A3kDewN8A3IDeQN8A3IDfANzA30DfgN/A30DfwOAA34D7gH3AX4D9wF/A38D9wH2AX8D9gF2A4ADfwN2A4ADdgN1A+gB6wGBA+gBgQN6A+sB4AGCA+sBggOBA4EDggODA4EDgwOEA3oDgQOEA3oDhAN7A4UDhgOHA4UDhwOIA4YD5gHvAYYD7wGHA4cD7wHuAYcD7gF+A4gDhwN+A4gDfgN9A+AB4wGJA+ABiQOCA+MB2AGKA+MBigOJA4kDigOLA4kDiwOMA4IDiQOMA4IDjAODA40DjgOPA40DjwOQA44D3gHnAY4D5wGPA48D5wHmAY8D5gGGA5ADjwOGA5ADhgOFA9gB2wGRA9gBkQOKA9sB0AGSA9sBkgORA5EDkgOTA5EDkwOUA4oDkQOUA4oDlAOLA5UDlgOXA5UDlwOYA5YD1gHfAZYD3wGXA5cD3wHeAZcD3gGOA5gDlwOOA5gDjgONA9AB0wGZA9ABmQOSA9MBygGaA9MBmgOZA5kDmgObA5kDmwOcA5IDmQOcA5IDnAOTA50DngOfA50DnwOgA54DzgHXAZ4D1wGfA58D1wHWAZ8D1gGWA6ADnwOWA6ADlgOVA8oByAGhA8oBoQOaA8gBwAGiA8gBogOhA6EDogOjA6EDowOkA5oDoQOkA5oDpAObA6UDpgOnA6UDpwOoA6YDxAHPAaYDzwGnA6cDzwHOAacDzgGeA6gDpwOeA6gDngOdA8ABvQGpA8ABqQOiA70BvAFaA70BWgOpA6kDWgNcA6kDXAOqA6IDqQOqA6IDqgOjA2ADXwOrA2ADqwOsA18DxgHFAV8DxQGrA6sDxQHEAasDxAGmA6wDqwOmA6wDpgOlA6MDqgOtA6MDrQOuA6oDXAOvA6oDrwOtA60DrwOwA60DsAOxA64DrQOxA64DsQOyA7MDtAO1A7MDtQO2A7QDYAOsA7QDrAO1A7UDrAOlA7UDpQO3A7YDtQO3A7YDtwO4A5sDpAO5A5sDuQO6A6QDowOuA6QDrgO5A7kDrgOyA7kDsgO7A7oDuQO7A7oDuwO8A7gDtwO9A7gDvQO+A7cDpQOoA7cDqAO9A70DqAOdA70DnQO/A74DvQO/A74DvwPAA5MDnAPBA5MDwQPCA5wDmwO6A5wDugPBA8EDugO8A8EDvAPDA8IDwQPDA8IDwwPEA8ADvwPFA8ADxQPGA78DnQOgA78DoAPFA8UDoAOVA8UDlQPHA8YDxQPHA8YDxwPIA4sDlAPJA4sDyQPKA5QDkwPCA5QDwgPJA8kDwgPEA8kDxAPLA8oDyQPLA8oDywPMA8gDxwPNA8gDzQPOA8cDlQOYA8cDmAPNA80DmAONA80DjQPPA84DzQPPA84DzwPQA4MDjAPRA4MD0QPSA4wDiwPKA4wDygPRA9EDygPMA9EDzAPTA9ID0QPTA9ID0wPUA9ADzwPVA9AD1QPWA88DjQOQA88DkAPVA9UDkAOFA9UDhQPXA9YD1QPXA9YD1wPYA3sDhAPZA3sD2QPaA4QDgwPSA4QD0gPZA9kD0gPUA9kD1APbA9oD2QPbA9oD2wPcA9gD1wPdA9gD3QPeA9cDhQOIA9cDiAPdA90DiAN9A90DfQPfA94D3QPfA94D3wPgA3MDfAPhA3MD4QPiA3wDewPaA3wD2gPhA+ED2gPcA+ED3APjA+ID4QPjA+ID4wPkA+AD3wPlA+AD5QPmA98DfQOAA98DgAPlA+UDgAN1A+UDdQPnA+YD5QPnA+YD5wPoA2sDdAPpA2sD6QPqA3QDcwPiA3QD4gPpA+kD4gPkA+kD5APrA+oD6QPrA+oD6wPsA+gD5wPtA+gD7QPuA+cDdQN4A+cDeAPtA+0DeANtA+0DbQPvA+4D7QPvA+4D7wPwA2MDbAPxA2MD8QPyA2wDawPqA2wD6gPxA/ED6gPsA/ED7APzA/ID8QPzA/ID8wP0A/AD7wP1A/AD9QP2A+8DbQNwA+8DcAP1A/UDcANlA/UDZQP3A/YD9QP3A/YD9wP4A0gDZAP5A0gD+QP6A2QDYwPyA2QD8gP5A/kD8gP0A/kD9AP7A/oD+QP7A/oD+wP8A/gD9wP9A/gD/QP+A/cDZQNoA/cDaAP9A/0DaANLA/0DSwP/A/4D/QP/A/4D/wMABFwDWwMBBFwDAQSvA1sDVAMCBFsDAgQBBAEEAgQDBAEEAwQEBK8DAQQEBK8DBASwAwUEBgQHBAUEBwQIBAYEWANeAwYEXgMHBAcEXgNgAwcEYAO0AwgEBwS0AwgEtAOzA1QDUwMJBFQDCQQCBFMDSgMKBFMDCgQJBAkECgQLBAkECwQMBAIECQQMBAIEDAQDBA0EDgQPBA0EDwQQBA4EUANWAw4EVgMPBA8EVgNYAw8EWAMGBBAEDwQGBBAEBgQFBEoDSQMRBEoDEQQKBEkDSAP6A0kD+gMRBBEE+gP8AxEE/AMSBAoEEQQSBAoEEgQLBAAE/wMTBAAEEwQUBP8DSwNOA/8DTgMTBBMETgNQAxMEUAMOBBQEEwQOBBQEDgQNBLUBswEVBLUBFQQWBLMBqQEXBLMBFwQVBBUEFwQYBBUEGAQZBBYEFQQZBBYEGQQaBBsEHAQdBBsEHQQeBBwEsQG7ARwEuwEdBB0EuwG1AR0EtQEWBB4EHQQWBB4EFgQaBKkBpwEfBKkBHwQXBKcBnQEgBKcBIAQfBB8EIAQhBB8EIQQiBBcEHwQiBBcEIgQYBCMEJAQlBCMEJQQmBCQEpQGyASQEsgElBCUEsgGxASUEsQEcBCYEJQQcBCYEHAQbBJ0BmwEnBJ0BJwQgBJsBkQEoBJsBKAQnBCcEKAQpBCcEKQQqBCAEJwQqBCAEKgQhBCsELAQtBCsELQQuBCwEmQGmASwEpgEtBC0EpgGlAS0EpQEkBC4ELQQkBC4EJAQjBJEBjwEvBJEBLwQoBI8BhQEwBI8BMAQvBC8EMAQxBC8EMQQyBCgELwQyBCgEMgQpBDMENAQ1BDMENQQ2BDQEjQGaATQEmgE1BDUEmgGZATUEmQEsBDYENQQsBDYELAQrBIUBgwE3BIUBNwQwBIMBeQE4BIMBOAQ3BDcEOAQ5BDcEOQQ6BDAENwQ6BDAEOgQxBDsEPAQ9BDsEPQQ+BDwEgQGOATwEjgE9BD0EjgGNAT0EjQE0BD4EPQQ0BD4ENAQzBHkBdwE/BHkBPwQ4BHcBbQFABHcBQAQ/BD8EQARBBD8EQQRCBDgEPwRCBDgEQgQ5BEMERARFBEMERQRGBEQEdQGCAUQEggFFBEUEggGBAUUEgQE8BEYERQQ8BEYEPAQ7BG0BawFHBG0BRwRABGsBYQFIBGsBSARHBEcESARJBEcESQRKBEAERwRKBEAESgRBBEsETARNBEsETQROBEwEaQF2AUwEdgFNBE0EdgF1AU0EdQFEBE4ETQREBE4ERARDBGEBXwFPBGEBTwRIBF8BVQFQBF8BUARPBE8EUARRBE8EUQRSBEgETwRSBEgEUgRJBFMEVARVBFMEVQRWBFQEXQFqAVQEagFVBFUEagFpAVUEaQFMBFYEVQRMBFYETARLBFUBUwFXBFUBVwRQBFMBRQFYBFMBWARXBFcEWARZBFcEWQRaBFAEVwRaBFAEWgRRBFsEXARdBFsEXQReBFwETgFeAVwEXgFdBF0EXgFdAV0EXQFUBF4EXQRUBF4EVARTBF8EYARhBF8EYQRiBGAEYwRkBGAEZARhBGEEZARlBGEEZQRmBGIEYQRmBGIEZgRnBGgEaQRqBGgEagRrBGkEYwRgBGkEYARqBGoEYARfBGoEXwRsBGsEagRsBGsEbARtBG4EbwRwBG4EcARxBG8EXwRiBG8EYgRwBHAEYgRnBHAEZwRyBHEEcARyBHEEcgRzBG0EbAR0BG0EdAR1BGwEXwRvBGwEbwR0BHQEbwRuBHQEbgR2BHUEdAR2BHUEdgR3BHgEeQR6BHgEegR7BHkEbgRxBHkEcQR6BHoEcQRzBHoEcwR8BHsEegR8BHsEfAR9BHcEdgR+BHcEfgR/BHYEbgR5BHYEeQR+BH4EeQR4BH4EeASABH8EfgSABH8EgASBBAIBBQGCBAIBggSDBAUBCgGEBAUBhASCBIIEhAR4BIIEeAR7BIMEggR7BIMEewR9BHgEhASFBHgEhQSABIQECgEQAYQEEAGFBIUEEAEPAYUEDwGGBIAEhQSGBIAEhgSBBBEBFAGHBBEBhwSIBBQBAgGDBBQBgwSHBIcEgwR9BIcEfQSJBIgEhwSJBIgEiQSKBIEEhgSLBIEEiwSMBIYEDwEcAYYEHAGLBIsEHAEbAYsEGwGNBIwEiwSNBIwEjQSOBB0BIAGPBB0BjwSQBCABEQGIBCABiASPBI8EiASKBI8EigSRBJAEjwSRBJAEkQSSBI4EjQSTBI4EkwSUBI0EGwEoAY0EKAGTBJMEKAEnAZMEJwGVBJQEkwSVBJQElQSWBCkBLAGXBCkBlwSYBCwBHQGQBCwBkASXBJcEkASSBJcEkgSZBJgElwSZBJgEmQSaBJYElQSbBJYEmwScBJUEJwE0AZUENAGbBJsENAEzAZsEMwGdBJwEmwSdBJwEnQSeBJIEnwSgBJIEoASZBJ8EcwRyBJ8EcgSgBKAEcgRnBKAEZwShBJkEoAShBJkEoQSaBG0EdQSiBG0EogSjBHUEdwSkBHUEpASiBKIEpASWBKIElgScBKMEogScBKMEnASeBJIEkQSlBJIEpQSfBJEEigSJBJEEiQSlBKUEiQR9BKUEfQR8BJ8EpQR8BJ8EfARzBIEEjASmBIEEpgR/BIwEjgSUBIwElASmBKYElASWBKYElgSkBH8EpgSkBH8EpAR3BKcEqASpBKcEqQSqBKgEmgShBKgEoQSpBKkEoQRnBKkEZwRmBKoEqQRmBKoEZgRlBG0EowSrBG0EqwRrBKMEngSsBKMErASrBKsErAStBKsErQSuBGsEqwSuBGsErgRoBDUBOAGvBDUBrwSwBDgBKQGYBDgBmASvBK8EmASaBK8EmgSoBLAErwSoBLAEqASnBJ4EnQSxBJ4EsQSsBJ0EMwFAAZ0EQAGxBLEEQAE/AbEEPwGyBKwEsQSyBKwEsgStBOwCswS0BOwCtATyArMEtQS2BLMEtgS0BLQEtgS3BLQEtwS4BPICtAS4BPICuAT0ArkEugS7BLkEuwS8BLoEvQS+BLoEvgS7BLsEvgTwArsE8AL5ArwEuwT5ArwE+QL4AjUBsAS/BDUBvwTqArAEpwTABLAEwAS/BL8EwAS1BL8EtQSzBOoCvwSzBOoCswTsAr0EwQTCBL0EwgS+BMEErQSyBMEEsgTCBMIEsgQ/AcIEPwHxAr4EwgTxAr4E8QLwAkEB+wLDBEEBwwTEBPsC9AK4BPsCuATDBMMEuAS3BMMEtwTFBMQEwwTFBMQExQTGBLkEvATHBLkExwTIBLwE+AL9ArwE/QLHBMcE/QJQAccEUAHJBMgExwTJBMgEyQTKBEEBxATLBEEBywRCAcQExgTMBMQEzATLBMsEzARZBMsEWQRYBEIBywRYBEIBWARFAVsEzQTOBFsEzgRcBM0EygTJBM0EyQTOBM4EyQRQAc4EUAFPAVwEzgRPAVwETwFOAc8E0ATRBM8E0QTSBNAE0wTUBNAE1ATRBNEE1ATVBNEE1QTWBNIE0QTWBNIE1gTXBNgE2QTaBNgE2gTbBNkE3ATdBNkE3QTaBNoE3QTPBNoEzwTSBNsE2gTSBNsE0gTXBNcE1gTeBNcE3gTfBNYE1QTgBNYE4ATeBN4E4AThBN4E4QTiBN8E3gTiBN8E4gTjBOQE5QTmBOQE5gTnBOUE2ATbBOUE2wTmBOYE2wTXBOYE1wTfBOcE5gTfBOcE3wTjBOME4gToBOME6ATpBOIE4QTqBOIE6gToBOgE6gTrBOgE6wTsBOkE6ATsBOkE7ATtBO4E7wTwBO4E8ATxBO8E5ATnBO8E5wTwBPAE5wTjBPAE4wTpBPEE8ATpBPEE6QTtBO0E7ATyBO0E8gTzBOwE6wT0BOwE9ATyBPIE9ARlBPIEZQRkBPME8gRkBPMEZARjBGgE9QT2BGgE9gRpBPUE7gTxBPUE8QT2BPYE8QTtBPYE7QTzBGkE9gTzBGkE8wRjBKcEqgT3BKcE9wTABKoEZQT0BKoE9AT3BPcE9ATrBPcE6wT4BMAE9wT4BMAE+AS1BO4E9QT5BO4E+QT6BPUEaASuBPUErgT5BPkErgStBPkErQTBBPoE+QTBBPoEwQS9BEkEUgT7BEkE+wT8BFIEUQT9BFIE/QT7BPsE/QT+BPsE/gT/BPwE+wT/BPwE/wQABQEFAgUDBQEFAwUEBQIFUwRWBAIFVgQDBQMFVgRLBAMFSwQFBQQFAwUFBQQFBQUGBQcFCAUJBQcFCQUKBQgFCwUMBQgFDAUJBQkFDAXTBAkF0wTQBAoFCQXQBAoF0ATPBNwEDQUOBdwEDgXdBA0FDwUQBQ0FEAUOBQ4FEAUHBQ4FBwUKBd0EDgUKBd0ECgXPBBEFEgUTBREFEwUUBRIFFQUWBRIFFgUTBRMFFgUXBRMFFwUYBRQFEwUYBRQFGAUZBRoFGwUcBRoFHAUdBRsFHgUfBRsFHwUcBRwFHwURBRwFEQUUBR0FHAUUBR0FFAUZBRkFGAUgBRkFIAUhBRgFFwUiBRgFIgUgBSAFIgUjBSAFIwUkBSEFIAUkBSEFJAUlBSYFJwUoBSYFKAUpBScFGgUdBScFHQUoBSgFHQUZBSgFGQUhBSkFKAUhBSkFIQUlBSUFJAUqBSUFKgUrBSQFIwUsBSQFLAUqBSoFLAULBSoFCwUIBSsFKgUIBSsFCAUHBQ8FLQUuBQ8FLgUQBS0FJgUpBS0FKQUuBS4FKQUlBS4FJQUrBRAFLgUrBRAFKwUHBTkEQgQvBTkELwUwBUIEQQQxBUIEMQUvBS8FMQUyBS8FMgUzBTAFLwUzBTAFMwU0BTUFNgU3BTUFNwU4BTYFQwRGBDYFRgQ3BTcFRgQ7BDcFOwQ5BTgFNwU5BTgFOQU6BTQFMwU7BTQFOwU8BTMFMgU9BTMFPQU7BTsFPQU+BTsFPgU/BTwFOwU/BTwFPwVABUEFQgVDBUEFQwVEBUIFNQU4BUIFOAVDBUMFOAU6BUMFOgVFBUQFQwVFBUQFRQVGBUAFPwVHBUAFRwVIBT8FPgVJBT8FSQVHBUcFSQVKBUcFSgVLBUgFRwVLBUgFSwVMBU0FTgVPBU0FTwVQBU4FQQVEBU4FRAVPBU8FRAVGBU8FRgVRBVAFTwVRBVAFUQVSBUwFSwVTBUwFUwVUBUsFSgVVBUsFVQVTBVMFVQVWBVMFVgVXBVQFUwVXBVQFVwVYBVkFWgVbBVkFWwVcBVoFTQVQBVoFUAVbBVsFUAVSBVsFUgVdBVwFWwVdBVwFXQVeBV8FYAVhBV8FYQViBWAFYwVkBWAFZAVhBWEFZAVYBWEFWAVXBWIFYQVXBWIFVwVWBV4FZQVmBV4FZgVcBWUFZwVoBWUFaAVmBWYFaAVpBWYFaQVqBVwFZgVqBVwFagVZBQsFLAVrBQsFawVsBSwFIwVtBSwFbQVrBWsFbQVYBWsFWAVkBWwFawVkBWwFZAVjBV4FbgVvBV4FbwVlBW4FJgUtBW4FLQVvBW8FLQUPBW8FDwVwBWUFbwVwBWUFcAVnBSMFIgVxBSMFcQVtBSIFFwVyBSIFcgVxBXEFcgVMBXEFTAVUBW0FcQVUBW0FVAVYBVIFcwV0BVIFdAVdBXMFGgUnBXMFJwV0BXQFJwUmBXQFJgVuBV0FdAVuBV0FbgVeBRcFFgV1BRcFdQVyBRYFFQV2BRYFdgV1BXUFdgVABXUFQAVIBXIFdQVIBXIFSAVMBUYFdwV4BUYFeAVRBXcFHgUbBXcFGwV4BXgFGwUaBXgFGgVzBVEFeAVzBVEFcwVSBRUFeQV6BRUFegV2BXkFewV8BXkFfAV6BXoFfAU0BXoFNAU8BXYFegU8BXYFPAVABToFfQV+BToFfgVFBX0FfwWABX0FgAV+BX4FgAUeBX4FHgV3BUUFfgV3BUUFdwVGBTEEOgSBBTEEgQWCBToEOQQwBToEMAWBBYEFMAU0BYEFNAV8BYIFgQV8BYIFfAV7BToFOQWDBToFgwV9BTkFOwQ+BDkFPgSDBYMFPgQzBIMFMwSEBX0FgwWEBX0FhAV/BYUFhgWHBYUFhwWIBYYFewV5BYYFeQWHBYcFeQUVBYcFFQUSBYgFhwUSBYgFEgURBR4FgAWJBR4FiQUfBYAFfwWKBYAFigWJBYkFigWFBYkFhQWIBR8FiQWIBR8FiAURBRgEIgSLBRgEiwWMBSIEIQQqBCIEKgSLBYsFKgQpBIsFKQQyBIwFiwUyBIwFMgQxBCsELgSNBSsEjQU2BC4EIwQmBC4EJgSNBY0FJgQbBI0FGwSOBTYEjQWOBTYEjgUzBBgEjAWPBRgEjwWQBYwFMQSCBYwFggWPBY8FggV7BY8FewWGBZAFjwWGBZAFhgWFBX8FhAWRBX8FkQWKBYQFMwSOBYQFjgWRBZEFjgUbBJEFGwSSBYoFkQWSBYoFkgWFBRoEGQSTBRoEkwWUBRgEkAWTBRgEkwUZBIUFlAWTBYUFkwWQBYUFkgWVBYUFlQWUBRsEHgSVBRsElQWSBRoElAWVBRoElQUeBEEESgSWBUEElgUxBUoESQT8BEoE/ASWBZYF/AQABZYFAAWXBTEFlgWXBTEFlwUyBQYFBQWYBQYFmAWZBQUFSwROBAUFTgSYBZgFTgRDBJgFQwQ2BZkFmAU2BZkFNgU1BQAFmgWbBQAFmwWXBZoFnAWdBZoFnQWbBZsFnQU+BZsFPgU9BZcFmwU9BZcFPQUyBUEFngWfBUEFnwVCBZ4FoAWhBZ4FoQWfBZ8FoQUGBZ8FBgWZBUIFnwWZBUIFmQU1BZwFogWjBZwFowWdBaIFpAWlBaIFpQWjBaMFpQVKBaMFSgVJBZ0FowVJBZ0FSQU+BU0FpgWnBU0FpwVOBaYFqAWpBaYFqQWnBacFqQWgBacFoAWeBU4FpwWeBU4FngVBBV8FYgWqBV8FqgWrBWIFVgVVBWIFVQWqBaoFVQVKBaoFSgWlBasFqgWlBasFpQWkBU0FWgWsBU0FrAWmBVoFWQVqBVoFagWsBawFagVpBawFaQWtBaYFrAWtBaYFrQWoBesE6gSuBesErgWvBeoE4QSwBeoEsAWuBa4FsAWxBa4FsQWyBa8FrgWyBa8FsgWzBbQFtQW2BbQFtgW3BbUF5ATvBLUF7wS2BbYF7wTuBLYF7gS4BbcFtgW4BbcFuAW5Bf4EugW7Bf4EuwW8BboFswWyBboFsgW7BbsFsgWxBbsFsQW9BbwFuwW9BbwFvQW+BbQFtwW/BbQFvwXABbcFuQXBBbcFwQW/Bb8FwQUBBb8FAQXCBcAFvwXCBcAFwgXDBVEEWgTEBVEExAX9BFoEWQTFBVoExQXEBcQFxQWzBcQFswW6Bf0ExAW6Bf0EugX+BLkFxgXHBbkFxwXBBcYFWwReBMYFXgTHBccFXgRTBMcFUwQCBcEFxwUCBcEFAgUBBVkEyAXJBVkEyQXFBcgFtwS2BMgFtgTJBckFtgS1BMkFtQTKBcUFyQXKBcUFygWzBb0EugTLBb0EywXMBboEuQTNBboEzQXLBcsFzQVbBMsFWwTGBcwFywXGBcwFxgW5BbUE+ATOBbUEzgXKBesErwXOBesEzgX4BLMFygXOBbMFzgWvBbkFuAXPBbkFzwXMBe4E+gTPBe4EzwW4Bb0EzAXPBb0EzwX6BFkEzATQBVkE0AXIBcYExQTQBcYE0AXMBLcEyAXQBbcE0AXFBLkEyATRBbkE0QXNBcoEzQTRBcoE0QXIBFsEzQXRBVsE0QXNBF8F0gXTBV8F0wVgBdIF1AXVBdIF1QXTBdMF1QXWBdMF1gXXBWAF0wXXBWAF1wVjBdgF2QXaBdgF2gXbBdkF3AXdBdkF3QXaBdoF3QVpBdoFaQVoBdsF2gVoBdsFaAVnBQsFbAXeBQsF3gUMBWwFYwXXBWwF1wXeBd4F1wXWBd4F1gXfBQwF3gXfBQwF3wXTBNgF2wXgBdgF4AXhBdsFZwVwBdsFcAXgBeAFcAUPBeAFDwUNBeEF4AUNBeEFDQXcBL4FvQXiBb4F4gXjBb0FsQXkBb0F5AXiBeIF5AXWBeIF1gXVBeMF4gXVBeMF1QXUBdgF5QXmBdgF5gXZBeUFtAXABeUFwAXmBeYFwAXDBeYFwwXnBdkF5gXnBdkF5wXcBeEE4AToBeEE6AWwBeAE1QTpBeAE6QXoBegF6QXWBegF1gXkBbAF6AXkBbAF5AWxBdgF6gXrBdgF6wXlBeoF2ATlBOoF5QTrBesF5QTkBOsF5AS1BeUF6wW1BeUFtQW0BdME3wXsBdME7AXUBNYF6QXsBdYF7AXfBdUE1ATsBdUE7AXpBdgE6gXtBdgE7QXZBNgF4QXtBdgF7QXqBdwE2QTtBdwE7QXhBe4F7wXwBe4F8AXxBe8F8gXzBe8F8wXwBfAF8wX0BfAF9AX1BfEF8AX1BfEF9QX2BfcF+AX5BfcF+QX6BfgF+wX8BfgF/AX5BfkF/AX9BfkF/QX+BfoF+QX+BfoF/gX/Be4F8QUABu4FAAYBBvEF9gUCBvEFAgYABgAGAgYDBgAGAwYEBgEGAAYEBgEGBAYFBgYGBwYIBgYGCAYJBgcG/wX+BQcG/gUIBggG/gX9BQgG/QUKBgkGCAYKBgkGCgYLBgUGBAYMBgUGDAYNBgQGAwYOBgQGDgYMBgwGDgYPBgwGDwYQBg0GDAYQBg0GEAYRBhIGEwYUBhIGFAYVBhMGBgYJBhMGCQYUBhQGCQYLBhQGCwYWBhUGFAYWBhUGFgYXBhEGEAYYBhEGGAYZBhAGDwYaBhAGGgYYBhgGGgYbBhgGGwYcBhkGGAYcBhkGHAYdBh4GHwYgBh4GIAYhBh8GEgYVBh8GFQYgBiAGFQYXBiAGFwYiBiEGIAYiBiEGIgYjBh0GHAYkBh0GJAYlBhwGGwYmBhwGJgYkBiQGJgYnBiQGJwYoBiUGJAYoBiUGKAYpBioGKwYsBioGLAYtBisGHgYhBisGIQYsBiwGIQYjBiwGIwYuBi0GLAYuBi0GLgYvBikGKAYwBikGMAYxBigGJwYyBigGMgYwBjAGMgYzBjAGMwY0BjEGMAY0BjEGNAY1BjYGNwY4BjYGOAY5BjcGKgYtBjcGLQY4BjgGLQYvBjgGLwY6BjkGOAY6BjkGOgY7BicGPAY9BicGPQYyBjwGPgY/BjwGPwY9Bj0GPwZABj0GQAZBBjIGPQZBBjIGQQYzBkIGQwZEBkIGRAZFBkMGRgZHBkMGRwZEBkQGRwYqBkQGKgY3BkUGRAY3BkUGNwY2BhsGSAZJBhsGSQYmBkgGSgZLBkgGSwZJBkkGSwY+BkkGPgY8BiYGSQY8BiYGPAYnBkYGTAZNBkYGTQZHBkwGTgZPBkwGTwZNBk0GTwYeBk0GHgYrBkcGTQYrBkcGKwYqBg8GUAZRBg8GUQYaBlAGUgZTBlAGUwZRBlEGUwZKBlEGSgZIBhoGUQZIBhoGSAYbBk4GVAZVBk4GVQZPBlQGVgZXBlQGVwZVBlUGVwYSBlUGEgYfBk8GVQYfBk8GHwYeBgMGWAZZBgMGWQYOBlgGWgZbBlgGWwZZBlkGWwZSBlkGUgZQBg4GWQZQBg4GUAYPBlYGXAZdBlYGXQZXBlwGXgZfBlwGXwZdBl0GXwYGBl0GBgYTBlcGXQYTBlcGEwYSBvYFYAZhBvYFYQYCBmAGYgZjBmAGYwZhBmEGYwZaBmEGWgZYBgIGYQZYBgIGWAYDBl4GZAZlBl4GZQZfBmQGZgZnBmQGZwZlBmUGZwb/BWUG/wUHBl8GZQYHBl8GBwYGBvYF9QVoBvYFaAZgBvUF9AVpBvUFaQZoBmgGaQZqBmgGagZrBmAGaAZrBmAGawZiBmwGbQZuBmwGbgZvBm0G9wX6BW0G+gVuBm4G+gX/BW4G/wVnBm8GbgZnBm8GZwZmBv4EvAVwBv4EcAZxBrwFvgVyBrwFcgZwBnAGcgZzBnAGcwZ0BnEGcAZ0BnEGdAZ1BnYGdwZ4BnYGeAZ5BncGwwXCBXcGwgV4BngGwgUBBXgGAQV6BnkGeAZ6BnkGegZ7Br4FfAZ9Br4FfQZyBnwGNQY0BnwGNAZ9Bn0GNAYzBn0GMwZ+BnIGfQZ+BnIGfgZzBjYGOQZ/BjYGfwaABjkGOwaBBjkGgQZ/Bn8GgQbDBX8GwwV3BoAGfwZ3BoAGdwZ2BgAF/wSCBgAFggaaBf8E/gRxBv8EcQaCBoIGcQZ1BoIGdQaDBpoFggaDBpoFgwacBXsGegaEBnsGhAaFBnoGAQUEBXoGBAWEBoQGBAUGBYQGBgWhBYUGhAahBYUGoQWgBaQFhgaHBqQFhwaIBoYGiQaKBoYGigaHBocGigb0BYcG9AXzBYgGhwbzBYgG8wXyBfcFiwaMBvcFjAb4BYsGjQaOBosGjgaMBowGjgaoBYwGqAWPBvgFjAaPBvgFjwb7BTMGQQaQBjMGkAZ+BkEGQAaRBkEGkQaQBpAGkQaSBpAGkgaTBn4GkAaTBn4GkwZzBpQGlQaWBpQGlgaXBpUGQgZFBpUGRQaWBpYGRQY2BpYGNgaABpcGlgaABpcGgAZ2BpgGmQaaBpgGmgabBpkGnAadBpkGnQaaBpoGnQZzBpoGcwaTBpsGmgaTBpsGkwaSBnYGngafBnYGnwaXBp4GoAahBp4GoQafBp8GoQaiBp8GogajBpcGnwajBpcGowaUBqQGpQamBqQGpganBqUGnAaZBqUGmQamBqYGmQaYBqYGmAaoBqcGpgaoBqcGqAapBqIGoQaqBqIGqgarBqEGoAasBqEGrAaqBqoGrAatBqoGrQauBqsGqgauBqsGrgavBrAGsQayBrAGsgazBrEGtAa1BrEGtQayBrIGtQacBrIGnAalBrMGsgalBrMGpQakBqAGtga3BqAGtwasBrYGuAa5BrYGuQa3BrcGuQa6BrcGuga7BqwGtwa7BqwGuwatBokGvAa9BokGvQa+BrwGtAaxBrwGsQa9Br0GsQawBr0GsAa/Br4GvQa/Br4GvwbABroGuQbBBroGwQbCBrkGuAbDBrkGwwbBBsEGwwaNBsEGjQbEBsIGwQbEBsIGxAbFBvQFigbGBvQFxgZpBooGiQa+BooGvgbGBsYGvgbABsYGwAbHBmkGxgbHBmkGxwZqBsUGxAbIBsUGyAbJBsQGjQaLBsQGiwbIBsgGiwb3BcgG9wVtBskGyAZtBskGbQZsBpwFygbLBpwFywaiBcoGtAa8BsoGvAbLBssGvAaJBssGiQaGBqIFywaGBqIFhgakBY0GwwbMBo0GzAaOBsMGuAbNBsMGzQbMBswGzQagBcwGoAWpBY4GzAapBY4GqQWoBZwFgwbOBpwFzgbKBoMGdQbPBoMGzwbOBs4GzwacBs4GnAa1BsoGzga1BsoGtQa0BqAG0AbRBqAG0Qa2BtAGewaFBtAGhQbRBtEGhQagBdEGoAXNBrYG0QbNBrYGzQa4BnUGdAbSBnUG0gbPBnMGnQbSBnMG0gZ0BpwGzwbSBpwG0gadBqAGngbTBqAG0wbQBnYGeQbTBnYG0waeBnsG0AbTBnsG0wZ5BmoGxwbUBmoG1AbVBscGwAbWBscG1gbUBtQG1gbXBtQG1wbYBtUG1AbYBtUG2AbZBtoG2wbcBtoG3AbdBtsGxQbJBtsGyQbcBtwGyQZsBtwGbAbeBt0G3AbeBt0G3gbfBsAGvwbgBsAG4AbWBr8GsAbhBr8G4QbgBuAG4QbiBuAG4gbjBtYG4AbjBtYG4wbXBuQG5QbmBuQG5gbnBuUGugbCBuUGwgbmBuYGwgbFBuYGxQbbBucG5gbbBucG2wbaBrAGswboBrAG6AbhBrMGpAbpBrMG6QboBugG6QbqBugG6gbrBuEG6AbrBuEG6wbiBuwG7QbuBuwG7gbvBu0GrQa7Bu0GuwbuBu4Guwa6Bu4GugblBu8G7gblBu8G5QbkBqQGpwbwBqQG8AbpBqcGqQbxBqcG8QbwBvAG8QbyBvAG8gbzBukG8AbzBukG8wbqBvQG9Qb2BvQG9gb3BvUGrwauBvUGrgb2BvYGrgatBvYGrQbtBvcG9gbtBvcG7QbsBqkGqAb4BqkG+AbxBqgGmAb5BqgG+Qb4BvgG+Qb6BvgG+gb7BvEG+Ab7BvEG+wbyBvwG/Qb+BvwG/gb/Bv0GogarBv0Gqwb+Bv4GqwavBv4Grwb1Bv8G/gb1Bv8G9Qb0BpgGmwYAB5gGAAf5BpsGkgYBB5sGAQcABwAHAQcCBwAHAgcDB/kGAAcDB/kGAwf6BgQHBQcGBwQHBgcHBwUHlAajBgUHowYGBwYHowaiBgYHogb9BgcHBgf9BgcH/Qb8BpIGkQYIB5IGCAcBB5EGQAYJB5EGCQcIBwgHCQcKBwgHCgcLBwEHCAcLBwEHCwcCBwwHDQcOBwwHDgcPBw0HQgaVBg0HlQYOBw4HlQaUBg4HlAYFBw8HDgcFBw8HBQcEB2IGawYQB2IGEAcRB2sGagbVBmsG1QYQBxAH1QbZBhAH2QYSBxEHEAcSBxEHEgcTB98G3gYUB98GFAcVB94GbAZvBt4GbwYUBxQHbwZmBhQHZgYWBxUHFAcWBxUHFgcXB1oGYwYYB1oGGAcZB2MGYgYRB2MGEQcYBxgHEQcTBxgHEwcaBxkHGAcaBxkHGgcbBxcHFgccBxcHHAcdBxYHZgZkBhYHZAYcBxwHZAZeBhwHXgYeBx0HHAceBx0HHgcfB1IGWwYgB1IGIAchB1sGWgYZB1sGGQcgByAHGQcbByAHGwciByEHIAciByEHIgcjBx8HHgckBx8HJAclBx4HXgZcBh4HXAYkByQHXAZWBiQHVgYmByUHJAcmByUHJgcnB0oGUwYoB0oGKAcpB1MGUgYhB1MGIQcoBygHIQcjBygHIwcqBykHKAcqBykHKgcrBycHJgcsBycHLActByYHVgZUBiYHVAYsBywHVAZOBiwHTgYuBy0HLAcuBy0HLgcvBz4GSwYwBz4GMAcxB0sGSgYpB0sGKQcwBzAHKQcrBzAHKwcyBzEHMAcyBzEHMgczBy8HLgc0By8HNAc1By4HTgZMBi4HTAY0BzQHTAZGBjQHRgY2BzUHNAc2BzUHNgc3B0AGPwY4B0AGOAcJBz8GPgYxBz8GMQc4BzgHMQczBzgHMwc5BwkHOAc5BwkHOQcKBzcHNgc6BzcHOgc7BzYHRgZDBjYHQwY6BzoHQwZCBjoHQgYNBzsHOgcNBzsHDQcMB/IG+wY8B/IGPAc9B/sG+gY+B/sGPgc8BzwHPgc/BzwHPwdABz0HPAdABz0HQAdBB0IHQwdEB0IHRAdFB0MH/Ab/BkMH/wZEB0QH/wb0BkQH9AZGB0UHRAdGB0UHRgdHB0EHQAdIB0EHSAdJB0AHPwdKB0AHSgdIB0gHSgdLB0gHSwdMB0kHSAdMB0kHTAdNB04HTwdQB04HUAdRB08HQgdFB08HRQdQB1AHRQdHB1AHRwdSB1EHUAdSB1EHUgdTB00HTAdUB00HVAdVB0wHSwdWB0wHVgdUB1QHVgdXB1QHVwdYB1UHVAdYB1UHWAdZB1oHWwdcB1oHXAddB1sHTgdRB1sHUQdcB1wHUQdTB1wHUwdeB10HXAdeB10HXgdfB1kHWAdgB1kHYAdhB1gHVwdiB1gHYgdgB2AHYgdjB2AHYwdkB2EHYAdkB2EHZAdlB2YHZwdoB2YHaAdpB2cHWgddB2cHXQdoB2gHXQdfB2gHXwdqB2kHaAdqB2kHagdrBxMHEgdsBxMHbAdtBxIH2QZuBxIHbgdsB2wHbgdZB2wHWQdhB20HbAdhB20HYQdlB18HbwdwB18HcAdqB28H3wYVB28HFQdwB3AHFQcXB3AHFwdxB2oHcAdxB2oHcQdrB9cGcgdzB9cGcwfYBnIHTQdVB3IHVQdzB3MHVQdZB3MHWQduB9gGcwduB9gGbgfZBl8HXgd0B18HdAdvB14HUwd1B14HdQd0B3QHdQfaBnQH2gbdBm8HdAfdBm8H3QbfBtcG4wZ2B9cGdgdyB+MG4gZ3B+MGdwd2B3YHdwdBB3YHQQdJB3IHdgdJB3IHSQdNB0cHeAd5B0cHeQdSB3gH5AbnBngH5wZ5B3kH5wbaBnkH2gZ1B1IHeQd1B1IHdQdTB/IGPQd6B/IGegfzBj0HQQd3Bz0Hdwd6B3oHdwfiBnoH4gbrBvMGegfrBvMG6wbqBuQGeAd7B+QGewfvBngHRwdGB3gHRgd7B3sHRgf0BnsH9Ab3Bu8Gewf3Bu8G9wbsBgIHCwd8BwIHfAcDBwsHCgd9BwsHfQd8B3wHfQc/B3wHPwc+BwMHfAc+BwMHPgf6BkIHfgd/B0IHfwdDB34HDAcPB34HDwd/B38HDwcEB38HBAcHB0MHfwcHB0MHBwf8BjMHgAeBBzMHgQc5B4AHSwdKB4AHSgeBB4EHSgc/B4EHPwd9BzkHgQd9BzkHfQcKB0IHTweCB0IHggd+B08HTgeDB08HgweCB4IHgwc3B4IHNwc7B34Hggc7B34HOwcMBysHhAeFBysHhQcyB4QHVwdWB4QHVgeFB4UHVgdLB4UHSweABzIHhQeABzIHgAczB04HWweGB04HhgeDB1sHWgeHB1sHhweGB4YHhwcvB4YHLwc1B4MHhgc1B4MHNQc3ByMHiAeJByMHiQcqB4gHYwdiB4gHYgeJB4kHYgdXB4kHVweEByoHiQeEByoHhAcrB1oHZweKB1oHigeHB2cHZgeLB2cHiweKB4oHiwcnB4oHJwctB4cHigctB4cHLQcvBxsHjAeNBxsHjQciB4wHZQdkB4wHZAeNB40HZAdjB40HYweIByIHjQeIByIHiAcjB2YHaQeOB2YHjgeLB2kHawePB2kHjweOB44HjwcfB44HHwclB4sHjgclB4sHJQcnBxMHbQeQBxMHkAcaB2UHjAeQB2UHkAdtBxsHGgeQBxsHkAeMBx8HjweRBx8HkQcdB2sHcQeRB2sHkQePBxcHHQeRBxcHkQdxBykGMQaSBykGkgeTBzEGNQaUBzEGlAeSB5IHlAeVB5IHlQeWB5MHkgeWB5MHlgeXB5gHmQeaB5gHmgebB5kHOwY6BpkHOgaaB5oHOgYvBpoHLwacB5sHmgecB5sHnAedBx0GJQaeBx0GngefByUGKQaTByUGkweeB54HkweXB54HlwegB58HngegB58HoAehB50HnAeiB50HogejB5wHLwYuBpwHLgaiB6IHLgYjBqIHIwakB6MHogekB6MHpAelBxEGGQamBxEGpgenBxkGHQafBxkGnwemB6YHnwehB6YHoQeoB6cHpgeoB6cHqAepB6UHpAeqB6UHqgerB6QHIwYiBqQHIgaqB6oHIgYXBqoHFwasB6sHqgesB6sHrAetBwUGDQauBwUGrgevBw0GEQanBw0GpweuB64HpwepB64HqQewB68HrgewB68HsAexB60HrAeyB60HsgezB6wHFwYWBqwHFgayB7IHFgYLBrIHCwa0B7MHsge0B7MHtAe1B+4FAQa2B+4Ftge3BwEGBQavBwEGrwe2B7YHrwexB7YHsQe4B7cHtge4B7cHuAe5B7UHtAe6B7UHuge7B7QHCwYKBrQHCga6B7oHCgb9BboH/QW8B7sHuge8B7sHvAe9B/IF7wW+B/IFvge/B+8F7gW3B+8Ftwe+B74Htwe5B74HuQfAB78HvgfAB78HwAfBB70HvAfCB70HwgfDB7wH/QX8BbwH/AXCB8IH/AX7BcIH+wXEB8MHwgfEB8MHxAfFB7kHxgfHB7kHxwfAB8YHlweWB8YHlgfHB8cHlgeVB8cHlQfIB8AHxwfIB8AHyAfBB5gHmwfJB5gHyQfKB5sHnQfLB5sHywfJB8kHywe9B8kHvQfDB8oHyQfDB8oHwwfFB7kHuAfMB7kHzAfGB7gHsQfNB7gHzQfMB8wHzQehB8wHoQegB8YHzAegB8YHoAeXB6UHzgfPB6UHzwejB84HtQe7B84HuwfPB88Huwe9B88HvQfLB6MHzwfLB6MHywedB7EHsAfQB7EH0AfNB6kHqAfQB6kH0AewB6EHzQfQB6EH0AeoB6UHqwfRB6UH0QfOB60HswfRB60H0QerB7UHzgfRB7UH0QezB18FqwXSB18F0gfTB6sFpAWIBqsFiAbSB9IHiAbyBdIH8gW/B9MH0ge/B9MHvwfBB/sFjwbUB/sF1AfEB48GqAWtBY8GrQXUB9QHrQVpBdQHaQXVB8QH1AfVB8QH1QfFB18F0wfWB18F1gfSBdMHwQfIB9MHyAfWB9YHyAeVB9YHlQfXB9IF1gfXB9IF1wfUBZgHygfYB5gH2AfZB8oHxQfVB8oH1QfYB9gH1QdpBdgHaQXdBdkH2AfdBdkH3QXcBb4F4wXaB74F2gd8BuMF1AXXB+MF1wfaB9oH1weVB9oHlQeUB3wG2geUB3wGlAc1BpgH2QfbB5gH2weZB9kH3AXnBdkH5wXbB9sH5wXDBdsHwwWBBpkH2weBBpkHgQY7Bg==" + } + ] +} diff --git a/assets/examples/bevy/sprite_sheet/Player.prototype.ron b/assets/examples/bevy/sprite_sheet/Player.prototype.ron new file mode 100644 index 0000000..5c27402 --- /dev/null +++ b/assets/examples/bevy/sprite_sheet/Player.prototype.ron @@ -0,0 +1,44 @@ +( + name: "Player", + schematics: { + "bevy_proto::custom::SpriteSheetBundle": ( + // The schematic for `TextureAtlas` defines an enum input with a `Grid` variant. + // The fields of this variant map to the parameters of `TextureAtlas::from_grid`. + texture_atlas: Asset(Grid( + texture: AssetPath("textures/rpg/chars/gabe/gabe-idle-run.png"), + tile_size: ( + x: 24.0, + y: 24.0 + ), + columns: 7, + rows: 1, + padding: None, + offset: None, + )), + sprite: ( + // Start our sprite at the first frame of the run animation + index: 1 + ), + transform: ( + scale: ( + x: 6.0, + y: 6.0, + z: 6.0, + ) + ), + ), + "bevy_sprite_sheet::AnimationIndices": ( + // The run animation actually starts on the second frame (index 1) + first: 1, + last: 6 + ), + "bevy_sprite_sheet::AnimationTimer": (( + duration: ( + secs: 0, + // 1e8 nanoseconds == 0.1 seconds + nanos: 100000000 + ), + mode: Repeating + )) + } +) \ No newline at end of file diff --git a/assets/textures/rpg/chars/gabe/gabe-idle-run.png b/assets/textures/rpg/chars/gabe/gabe-idle-run.png new file mode 100644 index 0000000..c8f57e3 Binary files /dev/null and b/assets/textures/rpg/chars/gabe/gabe-idle-run.png differ diff --git a/assets/textures/rpg/release.txt b/assets/textures/rpg/release.txt new file mode 100644 index 0000000..7a39568 --- /dev/null +++ b/assets/textures/rpg/release.txt @@ -0,0 +1,23 @@ +ESTÚDIO VACA ROXA + +Ajude a iniciativa Vaca Roxa: +https://apoia.se/vacaroxa +https://patreon.com/bakudas + +Social: +https://twitter.com/bakudas +https://twitter.com/estudiovacaroxa/ +https://www.fb.com/estudiovacaroxa +https://www.youtube.com/estudiovacaroxa + +Artwork by Bakudas and Gabe Fern +Versão ALPHA release v0.4 + +v0.4: +- Readme update licence to cc0 as itch page. + +Licence: +CC0 1.0 Universal (CC0 1.0) +Public Domain Dedication + +You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission. \ No newline at end of file diff --git a/bevy_proto_backend/src/proto/event.rs b/bevy_proto_backend/src/assets/event.rs similarity index 100% rename from bevy_proto_backend/src/proto/event.rs rename to bevy_proto_backend/src/assets/event.rs diff --git a/bevy_proto_backend/src/assets/extension.rs b/bevy_proto_backend/src/assets/extension.rs new file mode 100644 index 0000000..fee6bca --- /dev/null +++ b/bevy_proto_backend/src/assets/extension.rs @@ -0,0 +1,31 @@ +use crate::assets::{AssetSchematic, InlinableProtoAsset, ProtoAsset}; +use bevy::app::App; +use bevy::asset::{AddAsset, Handle}; +use bevy::reflect::TypePath; + +/// [`App`] extension trait for working with [`AssetSchematics`]. +/// +/// [`AssetSchematics`]: AssetSchematic +pub trait AssetSchematicAppExt { + /// Registers an [`AssetSchematic`]. + /// + /// This is a convenience method for the following registrations: + /// - `Handle` + /// - `ProtoAsset` + /// - `InlinableProtoAsset` + /// - `T::Input` + fn register_asset_schematic(&mut self) -> &mut Self; +} + +impl AssetSchematicAppExt for App { + fn register_asset_schematic(&mut self) -> &mut Self { + self.add_asset::() + .register_type::>() + .register_type::>>() + .register_type::>() + .register_type::>>() + .register_type::>() + .register_type::>>() + .register_type::() + } +} diff --git a/bevy_proto_backend/src/assets/mod.rs b/bevy_proto_backend/src/assets/mod.rs new file mode 100644 index 0000000..5f523b5 --- /dev/null +++ b/bevy_proto_backend/src/assets/mod.rs @@ -0,0 +1,110 @@ +//! Schematic types used to build [assets]. +//! +//! # Handling Assets +//! +//! Bevy uses a [`Handle`] type to reference assets. +//! Unfortunately, this type isn't very useful serialized as it doesn't directly +//! track the asset's path. +//! +//! In order to reference assets in a serialized format, +//! this crate defines a special [`ProtoAsset`] type which does keep track of the path. +//! This can then be used in the input type for a [`Schematic`] to load assets. +//! And, in fact, when deriving `Schematic`, a `Handle`-containing field +//! can be marked with the `#[schematic(asset)]` attribute to automatically handle this. +//! +//! ``` +//! # use bevy::prelude::*; +//! # use bevy_proto_derive::Schematic; +//! # use bevy_proto_backend::schematics::ReflectSchematic; +//! #[derive(Component, Reflect, Schematic)] +//! #[reflect(Schematic)] +//! struct Player { +//! #[schematic(asset)] +//! sprite: Handle +//! } +//! ``` +//! +//! This will generate a schematic input type with the appropriate `ProtoAsset` field: +//! +//! ``` +//! # use bevy::prelude::*; +//! # use bevy_proto_backend::assets::ProtoAsset; +//! #[derive(Reflect)] +//! struct PlayerInput { +//! sprite: ProtoAsset +//! } +//! ``` +//! +//! See the [derive macro documentation](bevy_proto_derive::Schematic) for more details +//! on this attribute and its various arguments. +//! +//! # Asset Schematics +//! +//! While referencing an asset by path is good enough for most cases, +//! sometimes it would be better to just define an asset as part of a prototype, +//! without needing to create a completely separate asset file. +//! +//! This is where [`AssetSchematic`] comes in. +//! This trait defines an [`Input`] data type and an [`Output`] asset type, +//! which can be used to create assets inline with a given schematic. +//! Because the implementor does not need to be the asset type itself, +//! any number of schematics can be defined for a single asset type. +//! +//! An asset schematic is used much like normal assets, +//! except, instead of using `ProtoAsset`, [`InlinableProtoAsset`] is used. +//! +//! The `AssetSchematic` trait can be derived and can even reference other assets. +//! It can then be referenced in a schematic using the `#[schematic(asset)]` attribute +//! with the `inline` argument. +//! +//! ``` +//! # use bevy::prelude::*; +//! # use bevy::reflect::{TypePath, TypeUuid}; +//! # use bevy_proto_derive::{AssetSchematic, Schematic}; +//! # use bevy_proto_backend::schematics::ReflectSchematic; +//! #[derive(AssetSchematic, TypeUuid, TypePath)] +//! #[uuid = "a3de79e1-0364-4fdf-9a82-9fae288e0aed"] +//! struct Level { +//! name: String, +//! #[asset_schematic(asset)] +//! background: Handle +//! } +//! +//! #[derive(Component, Reflect, Schematic)] +//! #[reflect(Schematic)] +//! struct CurrentLevel { +//! #[schematic(asset(inline))] +//! sprite: Handle +//! } +//! ``` +//! +//! Asset schematics have a lot of moving parts that need to be registered in the app. +//! To make things easier, this crate comes with an [extension trait] which can be used +//! to automatically register all of the necessary types. +//! +//! ```ignore +//! app.register_asset_schematic::(); +//! ``` +//! +//! See the [derive macro documentation](bevy_proto_derive::AssetSchematic) for more details. +//! +//! [assets]: bevy::asset::Asset +//! [`Handle`]: bevy::asset::Handle +//! [`ProtoAsset`]: ProtoAsset +//! [`Schematic`]: crate::schematics::Schematic +//! [`AssetSchematic`]: AssetSchematic +//! [`Input`]: AssetSchematic::Input +//! [`Output`]: AssetSchematic::Output +//! [`InlinableProtoAsset`]: InlinableProtoAsset +//! [extension trait]: AssetSchematicAppExt + +pub use bevy_proto_derive::AssetSchematic; +pub use event::*; +pub use extension::*; +pub use proto::*; +pub use schematic::*; + +mod event; +mod extension; +mod proto; +mod schematic; diff --git a/bevy_proto_backend/src/assets/proto.rs b/bevy_proto_backend/src/assets/proto.rs new file mode 100644 index 0000000..758ff3b --- /dev/null +++ b/bevy_proto_backend/src/assets/proto.rs @@ -0,0 +1,205 @@ +use crate::assets::{AssetSchematic, PreloadAssetSchematic}; +use crate::deps::DependenciesBuilder; +use crate::schematics::{ + FromSchematicInput, FromSchematicPreloadInput, SchematicContext, SchematicId, +}; +use bevy::asset::{Asset, AssetServer, Assets, Handle, HandleId}; +use bevy::prelude::Reflect; +use bevy::reflect::TypeUuid; +use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; + +/// Replacement type for asset handles in a [`Schematic::Input`] generated by the +/// [derive macro]. +/// +/// This allows assets to be referenced by path from within a schematic. +/// To inline an asset, use [`InlinableProtoAsset`]. +/// +/// [`Schematic::Input`]: crate::schematics::Schematic::Input +/// [derive macro]: bevy_proto_derive::Schematic +#[derive(Reflect)] +pub enum ProtoAsset { + /// The path to an asset relative to the `assets` directory. + AssetPath(String), + /// An existing [`Handle`]. + /// + /// Note: This handle should always be _weak_. + Handle(Handle), +} + +impl Default for ProtoAsset { + fn default() -> Self { + Self::Handle(Handle::default()) + } +} + +impl Clone for ProtoAsset { + fn clone(&self) -> Self { + match self { + Self::AssetPath(path) => Self::AssetPath(path.clone()), + Self::Handle(handle) => Self::Handle(handle.clone()), + } + } +} + +impl Debug for ProtoAsset { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::AssetPath(path) => f.debug_tuple("AssetPath").field(path).finish(), + Self::Handle(handle) => f.debug_tuple("Handle").field(handle).finish(), + } + } +} + +impl Eq for ProtoAsset {} + +impl PartialEq for ProtoAsset { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::AssetPath(a), Self::AssetPath(b)) => a == b, + (Self::Handle(a), Self::Handle(b)) => a == b, + _ => false, + } + } +} + +impl Hash for ProtoAsset { + fn hash(&self, state: &mut H) { + match self { + ProtoAsset::AssetPath(path) => path.hash(state), + ProtoAsset::Handle(handle) => handle.hash(state), + } + } +} + +impl From> for ProtoAsset { + fn from(value: Handle) -> Self { + Self::Handle(value.clone_weak()) + } +} + +impl FromSchematicInput> for Handle { + fn from_input(input: ProtoAsset, _id: SchematicId, context: &mut SchematicContext) -> Self { + match input { + ProtoAsset::AssetPath(path) => context.world().resource::().load(path), + ProtoAsset::Handle(handle) => { + context.world().resource::().get_handle(handle) + } + } + } +} + +impl FromSchematicPreloadInput> for Handle { + /// # Panics + /// + /// This will panic if the input is a [`ProtoAsset::Handle`]. + fn from_preload_input( + input: ProtoAsset, + _id: SchematicId, + dependencies: &mut DependenciesBuilder, + ) -> Self { + match input { + ProtoAsset::AssetPath(path) => dependencies.add_dependency(path), + ProtoAsset::Handle(_) => unimplemented!("cannot preload a handle"), + } + } +} + +/// Replacement type for asset handles in a [`Schematic::Input`] generated by the +/// [derive macro]. +/// +/// Unlike the base [`ProtoAsset`], this type allows for assets to be inlined +/// using the [`AssetSchematic`] trait. +/// +/// [`Schematic::Input`]: crate::schematics::Schematic::Input +/// [derive macro]: bevy_proto_derive::Schematic +#[derive(Reflect)] +pub enum InlinableProtoAsset { + /// The input to an [`AssetSchematic`] of type `T`. + /// + /// This is used to generate the actual asset at runtime. + Asset(T::Input), + /// The path to an asset relative to the `assets` directory. + AssetPath(String), + /// An existing [`Handle`]. + Handle(Handle), +} + +impl Default for InlinableProtoAsset { + fn default() -> Self { + Self::Handle(Handle::default()) + } +} + +impl FromSchematicInput> for Handle { + fn from_input( + input: InlinableProtoAsset, + id: SchematicId, + context: &mut SchematicContext, + ) -> Self { + match input { + InlinableProtoAsset::Asset(input) => { + let asset = T::load( + &input, + id.next(bevy::utils::Uuid::from_u128( + 0x54e193c39c62443da0884276371f0a27, + )), + context, + ); + context.world_mut().resource_mut::>().set( + HandleId::new( + T::Output::TYPE_UUID, + id.next(bevy::utils::Uuid::from_u128( + 0xf9dd72f06c22482fa0bdd45b417cb946, + )) + .value(), + ), + asset, + ) + } + InlinableProtoAsset::AssetPath(path) => { + context.world().resource::().load(path) + } + InlinableProtoAsset::Handle(handle) => { + context.world().resource::().get_handle(handle) + } + } + } +} + +impl FromSchematicPreloadInput> + for Handle +{ + /// # Panics + /// + /// This will panic if the input is a [`InlinableProtoAsset::Handle`]. + fn from_preload_input( + input: InlinableProtoAsset, + id: SchematicId, + dependencies: &mut DependenciesBuilder, + ) -> Self { + match input { + InlinableProtoAsset::Asset(input) => { + let asset = T::preload( + input, + id.next(bevy::utils::Uuid::from_u128( + 0x54e193c39c62443da0884276371f0a27, + )), + dependencies, + ); + dependencies.add_asset( + asset, + format!( + "{}", + id.next(bevy::utils::Uuid::from_u128( + 0x5760fb8ae11d4936986f5f19226c3e58 + )) + .value() + ), + ) + } + InlinableProtoAsset::AssetPath(path) => dependencies.add_dependency(path), + InlinableProtoAsset::Handle(_) => unimplemented!("cannot preload a handle"), + } + } +} diff --git a/bevy_proto_backend/src/assets/schematic.rs b/bevy_proto_backend/src/assets/schematic.rs new file mode 100644 index 0000000..4299ea2 --- /dev/null +++ b/bevy_proto_backend/src/assets/schematic.rs @@ -0,0 +1,50 @@ +use crate::deps::DependenciesBuilder; +use crate::schematics::{SchematicContext, SchematicId}; +use bevy::asset::Asset; +use bevy::reflect::{FromReflect, GetTypeRegistration}; + +/// Trait used to create a schematic for an [asset] which allows them to be +/// defined and loaded within a [schematic]. +/// +/// The implementor of this trait does not need to be an asset itself. +/// The actual asset type is defined by the associated [`Output`] type. +/// This allows assets to be defined with any number of schematics, +/// even for external assets. +/// +/// This trait can either be manually implemented or [derived]. +/// +/// See the [module-level documentation] for details. +/// +/// [asset]: Asset +/// [schematic]: crate::schematics::Schematic +/// [`Output`]: AssetSchematic::Output +/// [derived]: bevy_proto_derive::AssetSchematic +/// [module-level documentation]: crate::assets +pub trait AssetSchematic: 'static { + /// The input type of the schematic. + /// + /// This is the type that is deserialized from a prototype file + /// and used to store the asset's data. + type Input: FromReflect + GetTypeRegistration; + /// The output asset type. + type Output: Asset; + + /// Loads the [output] asset from the given [input]. + /// + /// [output]: Self::Output + /// [input]: Self::Input + fn load(input: &Self::Input, id: SchematicId, context: &mut SchematicContext) -> Self::Output; +} + +/// Allows an [`AssetSchematic`] to preload its asset. +pub trait PreloadAssetSchematic: AssetSchematic { + /// Preloads the [output] asset from the given [input]. + /// + /// [output]: Self::Output + /// [input]: Self::Input + fn preload( + input: Self::Input, + id: SchematicId, + context: &mut DependenciesBuilder, + ) -> Self::Output; +} diff --git a/bevy_proto_backend/src/deps/collection.rs b/bevy_proto_backend/src/deps/collection.rs index 646ab63..780a26d 100644 --- a/bevy_proto_backend/src/deps/collection.rs +++ b/bevy_proto_backend/src/deps/collection.rs @@ -1,6 +1,6 @@ use std::fmt::{Debug, Formatter}; -use bevy::asset::{Asset, AssetPath, Handle, HandleUntyped, LoadContext}; +use bevy::asset::{Asset, AssetPath, Handle, HandleUntyped, LoadContext, LoadedAsset}; use bevy::utils::hashbrown::hash_map::Iter; use bevy::utils::HashMap; @@ -90,6 +90,12 @@ impl<'a, 'ctx> DependenciesBuilder<'a, 'ctx> { handle } + /// Add a labeled asset. + pub fn add_asset>(&mut self, asset: T, label: L) -> Handle { + self.ctx + .set_labeled_asset(label.as_ref(), LoadedAsset::new(asset)) + } + fn get_handle>>( &mut self, path: P, diff --git a/bevy_proto_backend/src/impls/bevy_impls/asset.rs b/bevy_proto_backend/src/impls/bevy_impls/asset.rs index f8f241f..3279027 100644 --- a/bevy_proto_backend/src/impls/bevy_impls/asset.rs +++ b/bevy_proto_backend/src/impls/bevy_impls/asset.rs @@ -1,11 +1,10 @@ use bevy::app::App; -use bevy::asset::{Asset, AssetServer, Handle}; +use bevy::asset::{Asset, Handle}; use crate::impls::macros::register_schematic; use bevy_proto_derive::impl_external_schematic; -use crate::proto::ProtoAsset; -use crate::schematics::{FromSchematicInput, SchematicContext}; +use crate::assets::ProtoAsset; #[allow(unused_variables)] pub(super) fn register(app: &mut App) { @@ -59,15 +58,6 @@ pub(super) fn register(app: &mut App) { } impl_external_schematic! { - #[schematic(from = ProtoAsset)] + #[schematic(from = ProtoAsset)] struct Handle {} - // --- - impl FromSchematicInput for Handle { - fn from_input(input: ProtoAsset, context: &mut SchematicContext) -> Self { - match input { - ProtoAsset::AssetPath(path) => context.world().resource::().load(path), - ProtoAsset::HandleId(handle_id) => context.world().resource::().get_handle(handle_id), - } - } - } } diff --git a/bevy_proto_backend/src/impls/bevy_impls/pbr.rs b/bevy_proto_backend/src/impls/bevy_impls/pbr.rs index 5c70fab..d32a69b 100644 --- a/bevy_proto_backend/src/impls/bevy_impls/pbr.rs +++ b/bevy_proto_backend/src/impls/bevy_impls/pbr.rs @@ -1,15 +1,22 @@ use bevy::app::App; use bevy::pbr::wireframe::Wireframe; use bevy::pbr::{ - CascadeShadowConfig, CascadeShadowConfigBuilder, ClusterConfig, DirectionalLight, - EnvironmentMapLight, FogFalloff, FogSettings, NotShadowCaster, NotShadowReceiver, PointLight, - SpotLight, + AlphaMode, CascadeShadowConfig, CascadeShadowConfigBuilder, ClusterConfig, DirectionalLight, + EnvironmentMapLight, FogFalloff, FogSettings, NotShadowCaster, NotShadowReceiver, + ParallaxMappingMethod, PointLight, SpotLight, StandardMaterial, }; use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::prelude::Image; +use bevy::render::render_resource::Face; +use crate::assets::{AssetSchematicAppExt, ProtoAsset}; +use crate::deps::DependenciesBuilder; use crate::impls::macros::{from_to_default, register_schematic}; use crate::proto::ProtoColor; -use bevy_proto_derive::impl_external_schematic; +use crate::schematics::{ + FromSchematicInput, FromSchematicPreloadInput, SchematicContext, SchematicId, +}; +use bevy_proto_derive::{impl_external_asset_schematic, impl_external_schematic}; pub(super) fn register(app: &mut App) { register_schematic!( @@ -25,6 +32,9 @@ pub(super) fn register(app: &mut App) { SpotLight, Wireframe, ); + + app.register_asset_schematic::() + .register_type::(); } impl_external_schematic! { @@ -75,9 +85,9 @@ impl_external_schematic! { impl_external_schematic! { pub struct EnvironmentMapLight { - #[schematic(asset(lazy))] + #[schematic(asset)] pub diffuse_map: Handle, - #[schematic(asset(lazy))] + #[schematic(asset)] pub specular_map: Handle, } } @@ -125,3 +135,248 @@ impl_external_schematic! { impl_external_schematic! { struct Wireframe; } + +impl_external_asset_schematic! { + #[asset_schematic(from = StandardMaterialInput)] + struct StandardMaterial {} +} + +#[derive(Reflect)] +#[reflect(Default)] +pub struct StandardMaterialInput { + pub base_color: ProtoColor, + pub base_color_texture: Option>, + pub emissive: ProtoColor, + pub emissive_texture: Option>, + pub perceptual_roughness: f32, + pub metallic: f32, + pub metallic_roughness_texture: Option>, + pub reflectance: f32, + pub normal_map_texture: Option>, + pub flip_normal_map_y: bool, + pub occlusion_texture: Option>, + pub double_sided: bool, + pub cull_mode: Option, + pub unlit: bool, + pub fog_enabled: bool, + pub alpha_mode: AlphaMode, + pub depth_bias: f32, + pub depth_map: Option>, + pub parallax_depth_scale: f32, + pub parallax_mapping_method: ParallaxMappingMethod, + pub max_parallax_layer_count: f32, +} + +impl Default for StandardMaterialInput { + fn default() -> Self { + let base = StandardMaterial::default(); + + Self { + base_color: base.base_color.into(), + base_color_texture: base.base_color_texture.map(ProtoAsset::Handle), + emissive: base.emissive.into(), + emissive_texture: base.emissive_texture.map(ProtoAsset::Handle), + perceptual_roughness: base.perceptual_roughness, + metallic: base.metallic, + metallic_roughness_texture: base.metallic_roughness_texture.map(ProtoAsset::Handle), + reflectance: base.reflectance, + normal_map_texture: base.normal_map_texture.map(ProtoAsset::Handle), + flip_normal_map_y: base.flip_normal_map_y, + occlusion_texture: base.occlusion_texture.map(ProtoAsset::Handle), + double_sided: base.double_sided, + cull_mode: base.cull_mode.map(Into::into), + unlit: base.unlit, + fog_enabled: base.fog_enabled, + alpha_mode: base.alpha_mode, + depth_bias: base.depth_bias, + depth_map: base.depth_map.map(ProtoAsset::Handle), + parallax_depth_scale: base.parallax_depth_scale, + parallax_mapping_method: base.parallax_mapping_method, + max_parallax_layer_count: base.max_parallax_layer_count, + } + } +} + +impl FromSchematicInput for StandardMaterial { + fn from_input( + input: StandardMaterialInput, + id: SchematicId, + context: &mut SchematicContext, + ) -> Self { + Self { + base_color: input.base_color.into(), + base_color_texture: input.base_color_texture.map(|value| { + FromSchematicInput::from_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x0ec0883fecc74db5b30c8e7855f0aeed, + )), + context, + ) + }), + emissive: input.emissive.into(), + emissive_texture: input.emissive_texture.map(|value| { + FromSchematicInput::from_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x3f0b895dda574a55887cc32f9c20285a, + )), + context, + ) + }), + perceptual_roughness: input.perceptual_roughness, + metallic: input.metallic, + metallic_roughness_texture: input.metallic_roughness_texture.map(|value| { + FromSchematicInput::from_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0xd063b6d68c33437a9bd341be88d64c06, + )), + context, + ) + }), + reflectance: input.reflectance, + normal_map_texture: input.normal_map_texture.map(|value| { + FromSchematicInput::from_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x0ede476ee6994cf986f5a333bd385b62, + )), + context, + ) + }), + flip_normal_map_y: input.flip_normal_map_y, + occlusion_texture: input.occlusion_texture.map(|value| { + FromSchematicInput::from_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x19d7d9309c184fb58e31166652c5bee8, + )), + context, + ) + }), + double_sided: input.double_sided, + cull_mode: input.cull_mode.map(Into::into), + unlit: input.unlit, + fog_enabled: input.fog_enabled, + alpha_mode: input.alpha_mode, + depth_bias: input.depth_bias, + depth_map: input.depth_map.map(|value| { + FromSchematicInput::from_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x833fe20f1b7d4b469e9d6a86f873f7ae, + )), + context, + ) + }), + parallax_depth_scale: input.parallax_depth_scale, + parallax_mapping_method: input.parallax_mapping_method, + max_parallax_layer_count: input.max_parallax_layer_count, + } + } +} + +impl FromSchematicPreloadInput for StandardMaterial { + fn from_preload_input( + input: StandardMaterialInput, + id: SchematicId, + dependencies: &mut DependenciesBuilder, + ) -> Self { + Self { + base_color: input.base_color.into(), + base_color_texture: input.base_color_texture.map(|value| { + FromSchematicPreloadInput::from_preload_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x0ec0883fecc74db5b30c8e7855f0aeed, + )), + dependencies, + ) + }), + emissive: input.emissive.into(), + emissive_texture: input.emissive_texture.map(|value| { + FromSchematicPreloadInput::from_preload_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x3f0b895dda574a55887cc32f9c20285a, + )), + dependencies, + ) + }), + perceptual_roughness: input.perceptual_roughness, + metallic: input.metallic, + metallic_roughness_texture: input.metallic_roughness_texture.map(|value| { + FromSchematicPreloadInput::from_preload_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0xd063b6d68c33437a9bd341be88d64c06, + )), + dependencies, + ) + }), + reflectance: input.reflectance, + normal_map_texture: input.normal_map_texture.map(|value| { + FromSchematicPreloadInput::from_preload_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x0ede476ee6994cf986f5a333bd385b62, + )), + dependencies, + ) + }), + flip_normal_map_y: input.flip_normal_map_y, + occlusion_texture: input.occlusion_texture.map(|value| { + FromSchematicPreloadInput::from_preload_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x19d7d9309c184fb58e31166652c5bee8, + )), + dependencies, + ) + }), + double_sided: input.double_sided, + cull_mode: input.cull_mode.map(Into::into), + unlit: input.unlit, + fog_enabled: input.fog_enabled, + alpha_mode: input.alpha_mode, + depth_bias: input.depth_bias, + depth_map: input.depth_map.map(|value| { + FromSchematicPreloadInput::from_preload_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x833fe20f1b7d4b469e9d6a86f873f7ae, + )), + dependencies, + ) + }), + parallax_depth_scale: input.parallax_depth_scale, + parallax_mapping_method: input.parallax_mapping_method, + max_parallax_layer_count: input.max_parallax_layer_count, + } + } +} + +#[derive(Reflect)] +pub enum FaceInput { + Front = 0, + Back = 1, +} + +impl From for Face { + fn from(value: FaceInput) -> Self { + match value { + FaceInput::Front => Face::Front, + FaceInput::Back => Face::Back, + } + } +} + +impl From for FaceInput { + fn from(value: Face) -> Self { + match value { + Face::Front => FaceInput::Front, + Face::Back => FaceInput::Back, + } + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render.rs b/bevy_proto_backend/src/impls/bevy_impls/render.rs deleted file mode 100644 index 13ef489..0000000 --- a/bevy_proto_backend/src/impls/bevy_impls/render.rs +++ /dev/null @@ -1,117 +0,0 @@ -use bevy::app::App; -use bevy::prelude::{Camera, Entity, OrthographicProjection, PerspectiveProjection, Projection}; -use bevy::reflect::{std_traits::ReflectDefault, Reflect}; -use bevy::render::camera::CameraRenderGraph; -use bevy::render::mesh::skinning::SkinnedMesh; -use bevy::render::primitives::Aabb; -use bevy::render::view::{ColorGrading, RenderLayers, Visibility}; - -use crate::impls::macros::{from_to_default, register_schematic}; -use crate::tree::ProtoEntityList; -use bevy_proto_derive::impl_external_schematic; - -pub(super) fn register(app: &mut App) { - register_schematic!( - app, - Aabb, - Camera, - CameraRenderGraph, - ColorGrading, - OrthographicProjection, - PerspectiveProjection, - Projection, - RenderLayers, - SkinnedMesh, - Visibility, - ); -} - -impl_external_schematic! { - struct Aabb {} -} - -impl_external_schematic! { - struct Camera {} -} - -impl_external_schematic! { - struct CameraRenderGraph {} -} - -impl_external_schematic! { - #[schematic(from = ColorGradingInput)] - struct ColorGrading {} - // --- - #[derive(Reflect)] - #[reflect(Default)] - pub struct ColorGradingInput { - pub exposure: f32, - pub gamma: f32, - pub pre_saturation: f32, - pub post_saturation: f32, - } - from_to_default! { - ColorGrading, - ColorGradingInput, - |value: Input| Self { - exposure: value.exposure, - gamma: value.gamma, - pre_saturation: value.pre_saturation, - post_saturation: value.post_saturation, - } - } -} - -impl_external_schematic! { - struct OrthographicProjection {} -} - -impl_external_schematic! { - struct PerspectiveProjection {} -} - -impl_external_schematic! { - #[schematic(from = ProjectionInput)] - enum Projection {} - // --- - #[derive(Reflect)] - #[reflect(Default)] - pub enum ProjectionInput { - Perspective(PerspectiveProjection), - Orthographic(OrthographicProjection), - } - from_to_default! { - Projection, - ProjectionInput, - |value: Input| match value { - Input::Perspective(projection) => Self::Perspective(projection), - Input::Orthographic(projection) => Self::Orthographic(projection), - } - } -} - -impl_external_schematic! { - #[schematic(from = RenderLayersInput)] - struct RenderLayers(); - // --- - #[derive(Reflect)] - pub struct RenderLayersInput(u8); - impl From for RenderLayers { - fn from(value: RenderLayersInput) -> Self { - Self::layer(value.0) - } - } -} - -impl_external_schematic! { - pub struct SkinnedMesh { - #[schematic(asset(lazy))] - pub inverse_bindposes: Handle, - #[schematic(from = ProtoEntityList)] - pub joints: Vec, - } -} - -impl_external_schematic! { - enum Visibility {} -} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/mod.rs b/bevy_proto_backend/src/impls/bevy_impls/render/mod.rs new file mode 100644 index 0000000..34e58c0 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/mod.rs @@ -0,0 +1,5 @@ +pub use registrations::*; +pub use shapes::*; + +mod registrations; +mod shapes; diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/registrations.rs b/bevy_proto_backend/src/impls/bevy_impls/render/registrations.rs new file mode 100644 index 0000000..af71c6b --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/registrations.rs @@ -0,0 +1,188 @@ +use bevy::app::App; +use bevy::math::Mat4; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::camera::{ + Camera, CameraRenderGraph, OrthographicProjection, PerspectiveProjection, Projection, +}; +use bevy::render::mesh::shape::{ + Box, Capsule, Circle, Cube, Cylinder, Icosphere, Plane, Quad, RegularPolygon, Torus, UVSphere, +}; +use bevy::render::mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}; +use bevy::render::mesh::Mesh; +use bevy::render::primitives::Aabb; +use bevy::render::view::{ColorGrading, RenderLayers, Visibility}; + +use crate::assets::AssetSchematicAppExt; +use bevy_proto_derive::{impl_external_asset_schematic, impl_external_schematic}; + +use crate::impls::macros::{from_to_default, register_schematic}; +use crate::tree::ProtoEntityList; + +use super::shapes::*; + +pub(crate) fn register(app: &mut App) { + register_schematic!( + app, + Aabb, + Camera, + CameraRenderGraph, + ColorGrading, + OrthographicProjection, + PerspectiveProjection, + Projection, + RenderLayers, + SkinnedMesh, + Visibility, + ); + + app.register_asset_schematic::() + .register_asset_schematic::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::(); +} + +impl_external_schematic! { + struct Aabb {} +} + +impl_external_schematic! { + struct Camera {} +} + +impl_external_schematic! { + struct CameraRenderGraph {} +} + +impl_external_schematic! { + #[schematic(from = ColorGradingInput)] + struct ColorGrading {} + // --- + #[derive(Reflect)] + #[reflect(Default)] + pub struct ColorGradingInput { + pub exposure: f32, + pub gamma: f32, + pub pre_saturation: f32, + pub post_saturation: f32, + } + from_to_default! { + ColorGrading, + ColorGradingInput, + |value: Input| Self { + exposure: value.exposure, + gamma: value.gamma, + pre_saturation: value.pre_saturation, + post_saturation: value.post_saturation, + } + } +} + +impl_external_schematic! { + struct OrthographicProjection {} +} + +impl_external_schematic! { + struct PerspectiveProjection {} +} + +impl_external_schematic! { + #[schematic(from = ProjectionInput)] + enum Projection {} + // --- + #[derive(Reflect)] + #[reflect(Default)] + pub enum ProjectionInput { + Perspective(PerspectiveProjection), + Orthographic(OrthographicProjection), + } + from_to_default! { + Projection, + ProjectionInput, + |value: Input| match value { + Input::Perspective(projection) => Self::Perspective(projection), + Input::Orthographic(projection) => Self::Orthographic(projection), + } + } +} + +impl_external_schematic! { + #[schematic(from = RenderLayersInput)] + struct RenderLayers(); + // --- + #[derive(Reflect)] + pub struct RenderLayersInput(u8); + impl From for RenderLayers { + fn from(value: RenderLayersInput) -> Self { + Self::layer(value.0) + } + } +} + +impl_external_schematic! { + pub struct SkinnedMesh { + #[schematic(asset(inline))] + pub inverse_bindposes: Handle, + #[schematic(from = ProtoEntityList)] + pub joints: Vec, + } +} + +impl_external_schematic! { + enum Visibility {} +} + +// === Assets === // + +impl_external_asset_schematic! { + #[asset_schematic(from = Vec)] + struct SkinnedMeshInverseBindposes {} +} + +impl_external_asset_schematic! { + #[asset_schematic(from = MeshInput)] + struct Mesh {} +} + +/// The schematic input type for [`Mesh`]. +#[derive(Reflect)] +pub enum MeshInput { + Box(BoxInput), + Capsule(CapsuleInput), + Circle(CircleInput), + Cube(CubeInput), + Cylinder(CylinderInput), + Icosphere(IcosphereInput), + Plane(PlaneInput), + Quad(QuadInput), + RegularPolygon(RegularPolygonInput), + Torus(TorusInput), + UvSphere(UVSphereInput), +} + +impl From for Mesh { + fn from(value: MeshInput) -> Self { + match value { + MeshInput::Box(input) => Box::from(input).into(), + MeshInput::Capsule(input) => Capsule::from(input).into(), + MeshInput::Circle(input) => Circle::from(input).into(), + MeshInput::Cube(input) => Cube::from(input).into(), + MeshInput::Cylinder(input) => Cylinder::from(input).into(), + MeshInput::Icosphere(input) => Icosphere::from(input).try_into().unwrap(), + MeshInput::Plane(input) => Plane::from(input).into(), + MeshInput::Quad(input) => Quad::from(input).into(), + MeshInput::RegularPolygon(input) => RegularPolygon::from(input).into(), + MeshInput::Torus(input) => Torus::from(input).into(), + MeshInput::UvSphere(input) => UVSphere::from(input).into(), + } + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/boxes.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/boxes.rs new file mode 100644 index 0000000..0e32af0 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/boxes.rs @@ -0,0 +1,42 @@ +use bevy::math::Vec3; +use bevy::reflect::Reflect; +use bevy::render::mesh::shape::Box; + +/// The schematic input type for [`Box`]. +#[derive(Reflect)] +pub enum BoxInput { + Size(Vec3), + Corners(Vec3, Vec3), +} + +impl Default for BoxInput { + fn default() -> Self { + Box::default().into() + } +} + +impl From for Box { + fn from(value: BoxInput) -> Self { + match value { + BoxInput::Size(size) => Box::new(size.x, size.y, size.z), + BoxInput::Corners(a, b) => Box::from_corners(a, b), + } + } +} + +impl From for BoxInput { + fn from(value: Box) -> Self { + let Box { + min_x, + min_y, + min_z, + max_x, + max_y, + max_z, + } = value; + Self::Corners( + Vec3::new(min_x, min_y, min_z), + Vec3::new(max_x, max_y, max_z), + ) + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/capsules.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/capsules.rs new file mode 100644 index 0000000..bd4bd8d --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/capsules.rs @@ -0,0 +1,55 @@ +use crate::from_to_default; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::{Capsule, CapsuleUvProfile}; + +/// The schematic input type for [`Capsule`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct CapsuleInput { + /// Radius on the `XZ` plane. + pub radius: f32, + /// Number of sections in cylinder between hemispheres. + pub rings: usize, + /// Height of the middle cylinder on the `Y` axis, excluding the hemispheres. + pub depth: f32, + /// Number of latitudes, distributed by inclination. Must be even. + pub latitudes: usize, + /// Number of longitudes, or meridians, distributed by azimuth. + pub longitudes: usize, + /// Manner in which UV coordinates are distributed vertically. + pub uv_profile: CapsuleUvProfileInput, +} + +/// The schematic input type for [`CapsuleUvProfile`]. +#[derive(Reflect, Copy, Clone)] +pub enum CapsuleUvProfileInput { + Aspect, + /// Hemispheres get UV space according to the ratio of latitudes to rings. + Uniform, + /// Upper third of the texture goes to the northern hemisphere, middle third to the cylinder + /// and lower third to the southern one. + Fixed, +} + +from_to_default! { + CapsuleUvProfile, + CapsuleUvProfileInput, + |value: Input| match value { + Input::Aspect => Self::Aspect, + Input::Uniform => Self::Uniform, + Input::Fixed => Self::Fixed, + } +} + +from_to_default! { + Capsule, + CapsuleInput, + |value: Input| Self { + radius: value.radius, + rings: value.rings, + depth: value.depth, + latitudes: value.latitudes, + longitudes: value.longitudes, + uv_profile: value.uv_profile.into(), + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/circles.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/circles.rs new file mode 100644 index 0000000..fe90a63 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/circles.rs @@ -0,0 +1,22 @@ +use crate::from_to_default; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::Circle; + +/// The schematic input type for [`Circle`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct CircleInput { + /// Inscribed radius in the `XY` plane. + pub radius: f32, + /// The number of vertices used. + pub vertices: usize, +} + +from_to_default! { + Circle, + CircleInput, + |value: Input| Self { + radius: value.radius, + vertices: value.vertices, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/cubes.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/cubes.rs new file mode 100644 index 0000000..d426960 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/cubes.rs @@ -0,0 +1,18 @@ +use crate::from_to_default; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::Cube; + +/// The schematic input type for [`Cube`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct CubeInput { + pub size: f32, +} + +from_to_default! { + Cube, + CubeInput, + |value: Input| Self { + size: value.size, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/cylinders.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/cylinders.rs new file mode 100644 index 0000000..8e81ab9 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/cylinders.rs @@ -0,0 +1,32 @@ +use crate::from_to_default; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::Cylinder; + +/// The schematic input type for [`Cylinder`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct CylinderInput { + /// Radius in the XZ plane. + pub radius: f32, + /// Height of the cylinder in the Y axis. + pub height: f32, + /// The number of vertices around each horizontal slice of the cylinder. If you are looking at the cylinder from + /// above, this is the number of points you will see on the circle. + /// A higher number will make it appear more circular. + pub resolution: u32, + /// The number of segments between the two ends. Setting this to 1 will have triangles spanning the full + /// height of the cylinder. Setting it to 2 will have two sets of triangles with a horizontal slice in the middle of + /// cylinder. Greater numbers increase triangles/slices in the same way. + pub segments: u32, +} + +from_to_default! { + Cylinder, + CylinderInput, + |value: Input| Self { + radius: value.radius, + height: value.height, + resolution: value.resolution, + segments: value.segments, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/mod.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/mod.rs new file mode 100644 index 0000000..5d22ec5 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/mod.rs @@ -0,0 +1,21 @@ +pub use boxes::*; +pub use capsules::*; +pub use circles::*; +pub use cubes::*; +pub use cylinders::*; +pub use planes::*; +pub use polygons::*; +pub use quads::*; +pub use spheres::*; +pub use toruses::*; + +mod boxes; +mod capsules; +mod circles; +mod cubes; +mod cylinders; +mod planes; +mod polygons; +mod quads; +mod spheres; +mod toruses; diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/planes.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/planes.rs new file mode 100644 index 0000000..4600985 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/planes.rs @@ -0,0 +1,30 @@ +use crate::from_to_default; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::Plane; + +/// The schematic input type for [`Plane`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct PlaneInput { + /// The total side length of the square. + pub size: f32, + /// The number of subdivisions in the mesh. + /// + /// 0 - is the original plane geometry, the 4 points in the XZ plane. + /// + /// 1 - is split by 1 line in the middle of the plane on both the X axis and the Z axis, resulting in a plane with 4 quads / 8 triangles. + /// + /// 2 - is a plane split by 2 lines on both the X and Z axes, subdividing the plane into 3 equal sections along each axis, resulting in a plane with 9 quads / 18 triangles. + /// + /// and so on... + pub subdivisions: u32, +} + +from_to_default! { + Plane, + PlaneInput, + |value: Input| Self { + size: value.size, + subdivisions: value.subdivisions, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/polygons.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/polygons.rs new file mode 100644 index 0000000..2e2cc2d --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/polygons.rs @@ -0,0 +1,24 @@ +use crate::from_to_default; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::RegularPolygon; + +/// The schematic input type for [`RegularPolygon`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct RegularPolygonInput { + /// Circumscribed radius in the `XY` plane. + /// + /// In other words, the vertices of this polygon will all touch a circle of this radius. + pub radius: f32, + /// Number of sides. + pub sides: usize, +} + +from_to_default! { + RegularPolygon, + RegularPolygonInput, + |value: Input| Self { + radius: value.radius, + sides: value.sides, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/quads.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/quads.rs new file mode 100644 index 0000000..12f565e --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/quads.rs @@ -0,0 +1,23 @@ +use crate::from_to_default; +use bevy::math::Vec2; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::Quad; + +/// The schematic input type for [`Quad`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct QuadInput { + /// Full width and height of the rectangle. + pub size: Vec2, + /// Horizontally-flip the texture coordinates of the resulting mesh. + pub flip: bool, +} + +from_to_default! { + Quad, + QuadInput, + |value: Input| Self { + size: value.size, + flip: value.flip, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/spheres.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/spheres.rs new file mode 100644 index 0000000..7449cc5 --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/spheres.rs @@ -0,0 +1,42 @@ +use crate::from_to_default; +use bevy::prelude::shape::Icosphere; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::UVSphere; + +/// The schematic input type for [`UVSphere`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct UVSphereInput { + pub radius: f32, + pub sectors: usize, + pub stacks: usize, +} + +from_to_default! { + UVSphere, + UVSphereInput, + |value: Input| Self { + radius: value.radius, + sectors: value.sectors, + stacks: value.stacks, + } +} + +/// The schematic input type for [`Icosphere`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct IcosphereInput { + /// The radius of the sphere. + pub radius: f32, + /// The number of subdivisions applied. + pub subdivisions: usize, +} + +from_to_default! { + Icosphere, + IcosphereInput, + |value: Input| Self { + radius: value.radius, + subdivisions: value.subdivisions, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/render/shapes/toruses.rs b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/toruses.rs new file mode 100644 index 0000000..8565e3d --- /dev/null +++ b/bevy_proto_backend/src/impls/bevy_impls/render/shapes/toruses.rs @@ -0,0 +1,24 @@ +use crate::from_to_default; +use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::render::mesh::shape::Torus; + +/// The schematic input type for [`Torus`]. +#[derive(Reflect, Copy, Clone)] +#[reflect(Default)] +pub struct TorusInput { + pub radius: f32, + pub ring_radius: f32, + pub subdivisions_segments: usize, + pub subdivisions_sides: usize, +} + +from_to_default! { + Torus, + TorusInput, + |value: Input| Self { + radius: value.radius, + ring_radius: value.ring_radius, + subdivisions_segments: value.subdivisions_segments, + subdivisions_sides: value.subdivisions_sides, + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/sprite.rs b/bevy_proto_backend/src/impls/bevy_impls/sprite.rs index 315b0ca..fb7564b 100644 --- a/bevy_proto_backend/src/impls/bevy_impls/sprite.rs +++ b/bevy_proto_backend/src/impls/bevy_impls/sprite.rs @@ -1,14 +1,25 @@ use bevy::app::App; use bevy::math::Vec2; +use bevy::prelude::{Color, Image}; use bevy::reflect::{std_traits::ReflectDefault, Reflect}; -use bevy::sprite::{Anchor, Mesh2dHandle, Sprite, TextureAtlasSprite}; +use bevy::render::prelude::Mesh; +use bevy::sprite::{Anchor, ColorMaterial, Mesh2dHandle, Sprite, TextureAtlas, TextureAtlasSprite}; +use crate::assets::{AssetSchematicAppExt, InlinableProtoAsset, ProtoAsset}; +use crate::deps::DependenciesBuilder; use crate::impls::macros::{from_to_default, register_schematic}; -use crate::proto::{ProtoAsset, ProtoColor}; -use bevy_proto_derive::impl_external_schematic; +use crate::proto::ProtoColor; +use crate::schematics::{ + FromSchematicInput, FromSchematicPreloadInput, SchematicContext, SchematicId, +}; +use bevy_proto_derive::{impl_external_asset_schematic, impl_external_schematic}; pub(super) fn register(app: &mut App) { register_schematic!(app, Anchor, Mesh2dHandle, Sprite, TextureAtlasSprite); + + app.register_asset_schematic::() + .register_asset_schematic::() + .register_type::>(); } impl_external_schematic! { @@ -17,12 +28,12 @@ impl_external_schematic! { impl_external_schematic! { #[schematic(input(vis = pub))] - struct Mesh2dHandle(#[schematic(asset)] pub Handle); + struct Mesh2dHandle(#[schematic(asset(inline))] pub Handle); // --- + #[allow(clippy::derivable_impls)] impl Default for Mesh2dHandleInput { fn default() -> Self { - let base = ::default(); - Self(ProtoAsset::HandleId(base.0.id())) + Self(InlinableProtoAsset::default()) } } } @@ -58,3 +69,160 @@ impl_external_schematic! { } } } + +// === Assets === // + +impl_external_asset_schematic! { + #[asset_schematic(from = ColorMaterialInput)] + struct ColorMaterial {} +} + +/// The schematic input type for [`ColorMaterial`]. +#[derive(Reflect)] +#[reflect(Default)] +pub struct ColorMaterialInput { + pub color: Color, + pub texture: Option>, +} + +impl FromSchematicInput for ColorMaterial { + fn from_input( + input: ColorMaterialInput, + id: SchematicId, + context: &mut SchematicContext, + ) -> Self { + Self { + color: input.color, + texture: input.texture.map(|value| { + FromSchematicInput::from_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x6df96d568e7642c8bc7b9274ef732591, + )), + context, + ) + }), + } + } +} + +impl FromSchematicPreloadInput for ColorMaterial { + fn from_preload_input( + input: ColorMaterialInput, + id: SchematicId, + dependencies: &mut DependenciesBuilder, + ) -> Self { + Self { + color: input.color, + texture: input.texture.map(|value| { + FromSchematicPreloadInput::from_preload_input( + value, + id.next(bevy::utils::Uuid::from_u128( + 0x6df96d568e7642c8bc7b9274ef732591, + )), + dependencies, + ) + }), + } + } +} + +impl Default for ColorMaterialInput { + fn default() -> Self { + let base = ColorMaterial::default(); + Self { + color: base.color, + texture: base.texture.map(ProtoAsset::from), + } + } +} + +impl_external_asset_schematic! { + #[asset_schematic(from = TextureAtlasInput)] + struct TextureAtlas {} +} + +/// The schematic input type for [`TextureAtlas`]. +#[derive(Reflect)] +pub enum TextureAtlasInput { + Grid { + #[reflect(default)] + texture: ProtoAsset, + #[reflect(default)] + tile_size: Vec2, + #[reflect(default = "default_rows")] + columns: usize, + #[reflect(default = "default_columns")] + rows: usize, + #[reflect(default)] + padding: Option, + #[reflect(default)] + offset: Option, + }, +} + +fn default_rows() -> usize { + 1 +} + +fn default_columns() -> usize { + 1 +} + +impl FromSchematicInput for TextureAtlas { + fn from_input( + input: TextureAtlasInput, + id: SchematicId, + context: &mut SchematicContext, + ) -> Self { + match input { + TextureAtlasInput::Grid { + texture, + tile_size, + columns, + rows, + padding, + offset, + } => { + let texture = FromSchematicInput::from_input( + texture, + id.next(bevy::utils::Uuid::from_u128( + 0x9d0bb563b0fe45d689404b63567c597b, + )), + context, + ); + + Self::from_grid(texture, tile_size, columns, rows, padding, offset) + } + } + } +} + +impl FromSchematicPreloadInput for TextureAtlas { + fn from_preload_input( + input: TextureAtlasInput, + id: SchematicId, + dependencies: &mut DependenciesBuilder, + ) -> Self { + match input { + TextureAtlasInput::Grid { + texture, + tile_size, + columns, + rows, + padding, + offset, + } => { + let texture = FromSchematicPreloadInput::from_preload_input( + texture, + id.next(bevy::utils::Uuid::from_u128( + 0x9d0bb563b0fe45d689404b63567c597b, + )), + dependencies, + ); + + Self::from_grid(texture, tile_size, columns, rows, padding, offset) + } + } + } +} diff --git a/bevy_proto_backend/src/impls/bevy_impls/text.rs b/bevy_proto_backend/src/impls/bevy_impls/text.rs index 3ac42a0..ca49667 100644 --- a/bevy_proto_backend/src/impls/bevy_impls/text.rs +++ b/bevy_proto_backend/src/impls/bevy_impls/text.rs @@ -1,14 +1,18 @@ use bevy::app::App; use bevy::math::Vec2; +use bevy::prelude::Font; use bevy::reflect::{std_traits::ReflectDefault, Reflect}; +use bevy::sprite::TextureAtlas; use bevy::text::{ BreakLineOn, GlyphAtlasInfo, PositionedGlyph, Text, Text2dBounds, TextAlignment, TextLayoutInfo, TextSection, TextStyle, }; use crate::impls::macros::{from_to_default, from_to_input, register_schematic}; -use crate::proto::{ProtoAsset, ProtoColor}; -use crate::schematics::{FromSchematicInput, SchematicContext}; +use crate::proto::ProtoColor; +use crate::schematics::{FromSchematicInput, SchematicContext, SchematicId}; + +use crate::assets::ProtoAsset; use bevy_proto_derive::impl_external_schematic; pub(super) fn register(app: &mut App) { @@ -34,10 +38,14 @@ impl_external_schematic! { from_to_input! { Text, TextInput, - move |input: Input, context: &mut SchematicContext| { + move |input: Input, id: SchematicId, context: &mut SchematicContext| { let mut sections = Vec::with_capacity(input.sections.len()); for section in input.sections { - sections.push(FromSchematicInput::from_input(section, &mut *context)); + sections.push(FromSchematicInput::from_input( + section, + id.next(bevy::utils::Uuid::from_u128(0x529d5df317584b1dbede8eb45d2e2144)), + &mut *context + )); } Self { @@ -66,23 +74,31 @@ impl_external_schematic! { from_to_input! { TextSection, TextSectionInput, - |input: Input, context| Self { + |input: Input, id: SchematicId, context| Self { value: input.value, - style: FromSchematicInput::from_input(input.style, context) + style: FromSchematicInput::from_input( + input.style, + id.next(bevy::utils::Uuid::from_u128(0x1526927b34b34337b4141868f98d52cd)), + context + ) } } #[derive(Reflect)] pub struct TextStyleInput { - pub font: ProtoAsset, + pub font: ProtoAsset, pub font_size: f32, pub color: ProtoColor, } from_to_input! { TextStyle, TextStyleInput, - |input: Input, context| Self { - font: FromSchematicInput::from_input(input.font, context), + |input: Input, id: SchematicId, context| Self { + font: FromSchematicInput::from_input( + input.font, + id.next(bevy::utils::Uuid::from_u128(0xeae7ac5b229c4f58883dfc11bb02031b)), + context + ), font_size: input.font_size, color: input.color.into(), } @@ -115,12 +131,19 @@ pub struct TextLayoutInfoInput { } impl FromSchematicInput for TextLayoutInfo { - fn from_input(input: TextLayoutInfoInput, context: &mut SchematicContext) -> Self { + fn from_input( + input: TextLayoutInfoInput, + id: SchematicId, + context: &mut SchematicContext, + ) -> Self { Self { glyphs: input .glyphs .into_iter() - .map(|glyph| FromSchematicInput::from_input(glyph, context)) + .enumerate() + .map(|(index, glyph)| { + FromSchematicInput::from_input(glyph, id.next(index), context) + }) .collect(), size: input.size, } @@ -137,11 +160,15 @@ pub struct PositionedGlyphInput { } impl FromSchematicInput for PositionedGlyph { - fn from_input(input: PositionedGlyphInput, context: &mut SchematicContext) -> Self { + fn from_input( + input: PositionedGlyphInput, + id: SchematicId, + context: &mut SchematicContext, + ) -> Self { Self { position: input.position, size: input.size, - atlas_info: FromSchematicInput::from_input(input.atlas_info, context), + atlas_info: FromSchematicInput::from_input(input.atlas_info, id, context), section_index: input.section_index, byte_index: input.byte_index, } @@ -150,14 +177,18 @@ impl FromSchematicInput for PositionedGlyph { #[derive(Reflect)] pub struct GlyphAtlasInfoInput { - pub texture_atlas: ProtoAsset, + pub texture_atlas: ProtoAsset, pub glyph_index: usize, } impl FromSchematicInput for GlyphAtlasInfo { - fn from_input(input: GlyphAtlasInfoInput, context: &mut SchematicContext) -> Self { + fn from_input( + input: GlyphAtlasInfoInput, + id: SchematicId, + context: &mut SchematicContext, + ) -> Self { Self { - texture_atlas: FromSchematicInput::from_input(input.texture_atlas, context), + texture_atlas: FromSchematicInput::from_input(input.texture_atlas, id, context), glyph_index: input.glyph_index, } } diff --git a/bevy_proto_backend/src/impls/bevy_impls/ui.rs b/bevy_proto_backend/src/impls/bevy_impls/ui.rs index 2e3155a..52b3622 100644 --- a/bevy_proto_backend/src/impls/bevy_impls/ui.rs +++ b/bevy_proto_backend/src/impls/bevy_impls/ui.rs @@ -1,6 +1,6 @@ use bevy::app::App; use bevy::math::{Rect, Vec2}; -use bevy::prelude::{BackgroundColor, Button, Label}; +use bevy::prelude::{BackgroundColor, Button, Image, Label}; use bevy::reflect::{std_traits::ReflectDefault, Reflect}; use bevy::ui::widget::TextFlags; use bevy::ui::{ @@ -10,8 +10,9 @@ use bevy::ui::{ PositionType, RelativeCursorPosition, RepeatedGridTrack, Style, UiImage, UiRect, Val, ZIndex, }; +use crate::assets::ProtoAsset; use crate::impls::macros::{from_to_default, register_schematic}; -use crate::proto::{ProtoAsset, ProtoColor}; +use crate::proto::ProtoColor; use bevy_proto_derive::impl_external_schematic; pub(super) fn register(app: &mut App) { @@ -257,7 +258,7 @@ impl_external_schematic! { impl_external_schematic! { #[schematic(input(vis = pub))] pub struct UiImage { - #[schematic(asset(lazy))] + #[schematic(asset)] pub texture: Handle, #[reflect(default)] pub flip_x: bool, @@ -269,7 +270,7 @@ impl_external_schematic! { fn default() -> Self { let base = UiImage::default(); Self { - texture: ProtoAsset::HandleId(base.texture.id()), + texture: ProtoAsset::Handle(base.texture.clone_weak()), flip_x: base.flip_x, flip_y: base.flip_y, } diff --git a/bevy_proto_backend/src/impls/macros.rs b/bevy_proto_backend/src/impls/macros.rs index 72dc3e8..9007b72 100644 --- a/bevy_proto_backend/src/impls/macros.rs +++ b/bevy_proto_backend/src/impls/macros.rs @@ -79,10 +79,11 @@ macro_rules! from_to_input { impl $crate::schematics::FromSchematicInput for $mock { fn from_input( input: Input, + id: $crate::schematics::SchematicId, context: &mut $crate::schematics::SchematicContext, ) -> Self { #[allow(clippy::redundant_closure_call)] - $body(input, context) + $body(input, id, context) } } }; @@ -93,10 +94,11 @@ macro_rules! from_to_input { impl $crate::schematics::FromSchematicInput for $real { fn from_input( input: Input, + id: $crate::schematics::SchematicId, context: &mut $crate::schematics::SchematicContext, ) -> Self { #[allow(clippy::redundant_closure_call)] - $body(input, context) + $body(input, id, context) } } }; diff --git a/bevy_proto_backend/src/lib.rs b/bevy_proto_backend/src/lib.rs index d00db0f..75e5546 100644 --- a/bevy_proto_backend/src/lib.rs +++ b/bevy_proto_backend/src/lib.rs @@ -1,9 +1,9 @@ pub use plugin::*; +pub mod assets; pub mod children; pub mod cycles; pub mod deps; -#[doc(hidden)] pub mod impls; pub mod load; pub mod path; diff --git a/bevy_proto_backend/src/load/load_context.rs b/bevy_proto_backend/src/load/load_context.rs index 021323a..105a3f7 100644 --- a/bevy_proto_backend/src/load/load_context.rs +++ b/bevy_proto_backend/src/load/load_context.rs @@ -12,6 +12,7 @@ use crate::deps::DependenciesBuilder; use crate::load::{Loader, ProtoLoadMeta}; use crate::path::ProtoPathContext; use crate::proto::Prototypical; +use crate::schematics::SchematicId; /// The context when loading a [prototype]. /// @@ -145,7 +146,8 @@ impl<'a, 'ctx, T: Prototypical, L: Loader> ProtoLoadContext<'a, 'ctx, T, L> { // 1. Track schematic dependencies for (_, schematic) in prototype.schematics_mut().iter_mut() { - schematic.preload_dependencies(&mut deps)?; + let id = SchematicId::new(meta.handle.id(), schematic.type_info().type_id()); + schematic.preload_dependencies(id, &mut deps)?; } prototype.dependencies_mut().combine(deps.build()); diff --git a/bevy_proto_backend/src/plugin.rs b/bevy_proto_backend/src/plugin.rs index 3f5a415..384c7ad 100644 --- a/bevy_proto_backend/src/plugin.rs +++ b/bevy_proto_backend/src/plugin.rs @@ -1,5 +1,6 @@ use std::marker::PhantomData; +use crate::assets::ProtoAssetEvent; use bevy::app::{App, Plugin}; use bevy::asset::AddAsset; use bevy::prelude::{FromWorld, Update}; @@ -7,7 +8,7 @@ use parking_lot::Mutex; use crate::impls; use crate::load::{Loader, ProtoAssetLoader}; -use crate::proto::{Config, ProtoAsset, ProtoAssetEvent, ProtoStorage, Prototypical}; +use crate::proto::{Config, ProtoStorage, Prototypical}; use crate::registration::{on_proto_asset_event, ProtoRegistry}; use crate::tree::{AccessOp, ChildAccess, EntityAccess, ProtoEntity}; @@ -48,9 +49,9 @@ impl, C: Config> Plugin for ProtoBackendPlugin< #[cfg(feature = "bevy_render")] app.register_type::(); - app.register_type::() - .register_type::() + app.register_type::() .register_type::() + .register_type::>() .register_type::() .register_type::(); impls::register_impls(app); diff --git a/bevy_proto_backend/src/proto/assets.rs b/bevy_proto_backend/src/proto/assets.rs deleted file mode 100644 index b2e12bb..0000000 --- a/bevy_proto_backend/src/proto/assets.rs +++ /dev/null @@ -1,39 +0,0 @@ -use bevy::asset::{Asset, AssetPath, Handle, HandleId}; -use bevy::prelude::Reflect; - -/// Replacement type for asset handles in a [`Schematic::Input`] generated by the -/// [derive macro]. -/// -/// [`Schematic::Input`]: crate::schematics::Schematic::Input -/// [derive macro]: bevy_proto_derive::Schematic -#[derive(Clone, Debug, Eq, PartialEq, Hash, Reflect)] -pub enum ProtoAsset { - /// The path to an asset relative to the `assets` directory. - AssetPath(String), - /// An existing [`HandleId`]. - HandleId(HandleId), -} - -impl ProtoAsset { - /// Returns the [`AssetPath`] pointed to by this enum, if any. - pub fn to_asset_path(&self) -> Option> { - match self { - ProtoAsset::AssetPath(path) => Some(AssetPath::from(path)), - ProtoAsset::HandleId(_) => None, - } - } - - /// Returns a [`ProtoAsset::HandleId`] containing a default [`Handle`] for `T`. - /// - /// This can be used as `#[reflect(default = "ProtoAsset::default_handle_id::")]` - /// to denote a default value for a [`ProtoAsset`] field. - pub fn default_handle_id() -> Self { - Self::HandleId(Handle::::default().id()) - } -} - -impl From> for ProtoAsset { - fn from(value: Handle) -> Self { - Self::HandleId(value.id()) - } -} diff --git a/bevy_proto_backend/src/proto/commands.rs b/bevy_proto_backend/src/proto/commands.rs index 96520f7..4660cbd 100644 --- a/bevy_proto_backend/src/proto/commands.rs +++ b/bevy_proto_backend/src/proto/commands.rs @@ -6,7 +6,7 @@ use bevy::prelude::{Commands, Entity, Mut, World}; use crate::proto::{Config, Prototypical}; use crate::registration::ProtoRegistry; -use crate::schematics::{DynamicSchematic, SchematicContext}; +use crate::schematics::{DynamicSchematic, SchematicContext, SchematicId}; use crate::tree::EntityTreeNode; /// A system parameter similar to [`Commands`], but catered towards [prototypes]. @@ -190,8 +190,8 @@ impl> Command for ProtoInsertCommand { self.data.assert_is_registered(world); self.data - .for_each_schematic(world, true, |schematic, context| { - schematic.apply(context).unwrap(); + .for_each_schematic(world, true, |schematic, id, context| { + schematic.apply(id, context).unwrap(); }); } } @@ -221,8 +221,8 @@ impl> Command for ProtoRemoveCommand { self.data.assert_is_registered(world); self.data - .for_each_schematic(world, false, |schematic, context| { - schematic.remove(context).unwrap(); + .for_each_schematic(world, false, |schematic, id, context| { + schematic.remove(id, context).unwrap(); }); } } @@ -294,7 +294,7 @@ impl> ProtoCommandData { /// [prototype]: Prototypical fn for_each_schematic(&self, world: &mut World, is_apply: bool, callback: F) where - F: Fn(&DynamicSchematic, &mut SchematicContext), + F: Fn(&DynamicSchematic, SchematicId, &mut SchematicContext), { self.for_each_entity(world, is_apply, |node, context, prototypes, config| { let on_before_prototype = if is_apply { @@ -332,9 +332,11 @@ impl> ProtoCommandData { on_before_prototype(config, proto, context); for (_, schematic) in proto.schematics().iter() { - on_before_schematic(config, schematic, context); - callback(schematic, context); - on_after_schematic(config, schematic, context); + let id = SchematicId::new(*handle_id, schematic.type_info().type_id()); + + on_before_schematic(config, schematic, id.clone(), context); + callback(schematic, id.clone(), context); + on_after_schematic(config, schematic, id.clone(), context); } on_after_prototype(config, proto, context); diff --git a/bevy_proto_backend/src/proto/config.rs b/bevy_proto_backend/src/proto/config.rs index 81128ef..e55ba5d 100644 --- a/bevy_proto_backend/src/proto/config.rs +++ b/bevy_proto_backend/src/proto/config.rs @@ -3,7 +3,7 @@ use bevy::prelude::{FromWorld, Resource}; use crate::cycles::{Cycle, CycleResponse}; use crate::proto::Prototypical; -use crate::schematics::{DynamicSchematic, SchematicContext}; +use crate::schematics::{DynamicSchematic, SchematicContext, SchematicId}; /// Configuration for a [prototype]. /// @@ -87,6 +87,7 @@ pub trait Config: Resource + FromWorld { fn on_before_apply_schematic( &mut self, schematic: &DynamicSchematic, + id: SchematicId, context: &mut SchematicContext, ) { } @@ -101,6 +102,7 @@ pub trait Config: Resource + FromWorld { fn on_after_apply_schematic( &mut self, schematic: &DynamicSchematic, + id: SchematicId, context: &mut SchematicContext, ) { } @@ -115,6 +117,7 @@ pub trait Config: Resource + FromWorld { fn on_before_remove_schematic( &mut self, schematic: &DynamicSchematic, + id: SchematicId, context: &mut SchematicContext, ) { } @@ -129,6 +132,7 @@ pub trait Config: Resource + FromWorld { fn on_after_remove_schematic( &mut self, schematic: &DynamicSchematic, + id: SchematicId, context: &mut SchematicContext, ) { } diff --git a/bevy_proto_backend/src/proto/mod.rs b/bevy_proto_backend/src/proto/mod.rs index 1bcc0e0..1ebdcc5 100644 --- a/bevy_proto_backend/src/proto/mod.rs +++ b/bevy_proto_backend/src/proto/mod.rs @@ -1,25 +1,21 @@ //! The core of prototypes. -pub use assets::*; #[cfg(feature = "bevy_render")] pub use color::*; pub use commands::*; pub use component::*; pub use config::*; pub use error::*; -pub use event::*; pub use prototypes::*; pub use prototypical::*; pub(crate) use storage::*; -mod assets; #[cfg(feature = "bevy_render")] mod color; mod commands; mod component; mod config; mod error; -mod event; mod prototypes; mod prototypical; mod storage; diff --git a/bevy_proto_backend/src/registration/params.rs b/bevy_proto_backend/src/registration/params.rs index 6382178..cff6e6b 100644 --- a/bevy_proto_backend/src/registration/params.rs +++ b/bevy_proto_backend/src/registration/params.rs @@ -1,4 +1,5 @@ -use crate::proto::{Config, ProtoAssetEvent, ProtoError, Prototypical}; +use crate::assets::ProtoAssetEvent; +use crate::proto::{Config, ProtoError, Prototypical}; use bevy::asset::{Assets, Handle, HandleId}; use bevy::ecs::system::SystemParam; use bevy::prelude::{EventWriter, Res, ResMut}; diff --git a/bevy_proto_backend/src/registration/registry.rs b/bevy_proto_backend/src/registration/registry.rs index b1d2024..0f452ac 100644 --- a/bevy_proto_backend/src/registration/registry.rs +++ b/bevy_proto_backend/src/registration/registry.rs @@ -7,10 +7,11 @@ use crate::registration::params::RegistryParams; use bevy::asset::{Handle, HandleId}; use bevy::prelude::Resource; +use crate::assets::ProtoAssetEvent; use bevy::utils::{HashMap, HashSet}; use parking_lot::RwLock; -use crate::proto::{Config, ProtoAssetEvent, ProtoError, Prototypical}; +use crate::proto::{Config, ProtoError, Prototypical}; use crate::tree::{ProtoTree, ProtoTreeBuilder}; /// Resource used to track load states, store mappings, and generate cached data. diff --git a/bevy_proto_backend/src/schematics/dynamic.rs b/bevy_proto_backend/src/schematics/dynamic.rs index c5f0e40..8cd1562 100644 --- a/bevy_proto_backend/src/schematics/dynamic.rs +++ b/bevy_proto_backend/src/schematics/dynamic.rs @@ -5,7 +5,7 @@ use bevy::reflect::{FromType, GetTypeRegistration, TypeInfo, TypeRegistration, T use crate::deps::DependenciesBuilder; use crate::schematics::schematic::Schematic; -use crate::schematics::{SchematicContext, SchematicError}; +use crate::schematics::{SchematicContext, SchematicError, SchematicId}; /// A dynamic representation of a [`Schematic`]. /// @@ -44,21 +44,30 @@ impl DynamicSchematic { } /// Dynamically call the corresponding [`Schematic::apply`] method. - pub fn apply(&self, context: &mut SchematicContext) -> Result<(), SchematicError> { - (self.reflect_schematic.apply)(&*self.input, context) + pub fn apply( + &self, + id: SchematicId, + context: &mut SchematicContext, + ) -> Result<(), SchematicError> { + (self.reflect_schematic.apply)(&*self.input, id, context) } /// Dynamically call the corresponding [`Schematic::remove`] method. - pub fn remove(&self, context: &mut SchematicContext) -> Result<(), SchematicError> { - (self.reflect_schematic.remove)(&*self.input, context) + pub fn remove( + &self, + id: SchematicId, + context: &mut SchematicContext, + ) -> Result<(), SchematicError> { + (self.reflect_schematic.remove)(&*self.input, id, context) } /// Dynamically call the corresponding [`Schematic::preload_dependencies`] method. pub fn preload_dependencies( &mut self, + id: SchematicId, dependencies: &mut DependenciesBuilder, ) -> Result<(), SchematicError> { - (self.reflect_schematic.preload_dependencies)(&mut *self.input, dependencies) + (self.reflect_schematic.preload_dependencies)(&mut *self.input, id, dependencies) } /// The type info of the corresponding [`Schematic`]. @@ -101,10 +110,19 @@ pub struct ReflectSchematic { input_registration: fn() -> TypeRegistration, create_dynamic: fn(Box, ReflectSchematic) -> Result, - apply: fn(input: &dyn Reflect, context: &mut SchematicContext) -> Result<(), SchematicError>, - remove: fn(input: &dyn Reflect, context: &mut SchematicContext) -> Result<(), SchematicError>, + apply: fn( + input: &dyn Reflect, + id: SchematicId, + context: &mut SchematicContext, + ) -> Result<(), SchematicError>, + remove: fn( + input: &dyn Reflect, + id: SchematicId, + context: &mut SchematicContext, + ) -> Result<(), SchematicError>, preload_dependencies: fn( input: &mut dyn Reflect, + id: SchematicId, dependencies: &mut DependenciesBuilder, ) -> Result<(), SchematicError>, clone_input: fn(input: &dyn Reflect) -> Result, SchematicError>, @@ -149,27 +167,27 @@ impl FromType for ReflectSchematic { reflect_schematic: data, }) }, - apply: |reflect_input, context| { + apply: |reflect_input, id, context| { let input = reflect_input.downcast_ref::().ok_or_else(|| { SchematicError::TypeMismatch { expected: std::any::type_name::(), found: reflect_input.type_name().to_string(), } })?; - ::apply(input, context); + ::apply(input, id, context); Ok(()) }, - remove: |reflect_input, context| { + remove: |reflect_input, id, context| { let input = reflect_input.downcast_ref::().ok_or_else(|| { SchematicError::TypeMismatch { expected: std::any::type_name::(), found: reflect_input.type_name().to_string(), } })?; - ::remove(input, context); + ::remove(input, id, context); Ok(()) }, - preload_dependencies: |reflect_input, dependencies| { + preload_dependencies: |reflect_input, id, dependencies| { let type_name = reflect_input.type_name().to_string(); let input = reflect_input.downcast_mut::().ok_or_else(|| { SchematicError::TypeMismatch { @@ -177,7 +195,7 @@ impl FromType for ReflectSchematic { found: type_name, } })?; - ::preload_dependencies(input, dependencies); + ::preload_dependencies(input, id, dependencies); Ok(()) }, clone_input: |reflect_input| { diff --git a/bevy_proto_backend/src/schematics/id.rs b/bevy_proto_backend/src/schematics/id.rs new file mode 100644 index 0000000..a7d6767 --- /dev/null +++ b/bevy_proto_backend/src/schematics/id.rs @@ -0,0 +1,53 @@ +use bevy::asset::HandleId; +use bevy::utils::{AHasher, FixedState}; +use std::any::TypeId; +use std::hash::{BuildHasher, Hash, Hasher}; + +/// A unique identifier for a [`Schematic`]. +/// +/// This can be used to generate stable references for items in a `Schematic`, +/// including deeply nested items. +/// +/// This can be achieved by ensuring the same value is passed to [`SchematicId::next`]. +/// +/// [`Schematic`]: crate::schematics::Schematic +#[derive(Debug, Hash, PartialEq, Eq)] +pub struct SchematicId(u64); + +impl SchematicId { + /// Creates a new [`SchematicId`] from the given [`HandleId`] of the prototype asset + /// and the [`TypeId`] of the schematic itself. + pub(crate) fn new(proto: HandleId, schematic: TypeId) -> Self { + Self(Self::generate(|hasher| { + Hash::hash(&proto, hasher); + Hash::hash(&schematic, hasher); + })) + } + + /// The hash value of this [`SchematicId`]. + pub fn value(&self) -> u64 { + self.0 + } + + /// Generate a new [`SchematicId`] from this one and the given seed. + pub fn next(&self, seed: impl Hash) -> Self { + Self(Self::generate(|hasher| { + Hash::hash(self, hasher); + Hash::hash(&seed, hasher); + })) + } + + /// Allows cloning this [`SchematicId`]. + /// + /// This is used instead of the [`Clone`] trait to prevent consumers of this crate + /// from accidentally cloning this ID and subsequently breaking stability guarantees. + pub(crate) fn clone(&self) -> Self { + Self(self.0) + } + + fn generate(f: impl FnOnce(&mut AHasher)) -> u64 { + let mut hasher = FixedState.build_hasher(); + f(&mut hasher); + hasher.finish() + } +} diff --git a/bevy_proto_backend/src/schematics/mod.rs b/bevy_proto_backend/src/schematics/mod.rs index 9d5135f..4d1fcb5 100644 --- a/bevy_proto_backend/src/schematics/mod.rs +++ b/bevy_proto_backend/src/schematics/mod.rs @@ -42,10 +42,12 @@ pub use collection::*; pub use context::*; pub use dynamic::*; pub use error::*; +pub use id::*; pub use schematic::*; mod collection; mod context; mod dynamic; mod error; +mod id; mod schematic; diff --git a/bevy_proto_backend/src/schematics/schematic.rs b/bevy_proto_backend/src/schematics/schematic.rs index 371282a..d2d2d26 100644 --- a/bevy_proto_backend/src/schematics/schematic.rs +++ b/bevy_proto_backend/src/schematics/schematic.rs @@ -2,7 +2,7 @@ use bevy::prelude::{FromReflect, Reflect}; use bevy::reflect::{GetTypeRegistration, Typed}; use crate::deps::DependenciesBuilder; -use crate::schematics::SchematicContext; +use crate::schematics::{SchematicContext, SchematicId}; /// Trait used to create a [prototype] schematic for modifying an [entity] /// (or the [world] in general). @@ -15,19 +15,19 @@ use crate::schematics::SchematicContext; /// /// ``` /// use bevy::prelude::{Component, Reflect}; -/// use bevy_proto_backend::schematics::{Schematic, SchematicContext}; +/// use bevy_proto_backend::schematics::{Schematic, SchematicContext, SchematicId}; /// #[derive(Component, Reflect)] /// struct PlayerId(usize); /// /// impl Schematic for PlayerId { /// type Input = Self; /// -/// fn apply(input: &Self::Input, context: &mut SchematicContext) { -/// entity.insert(Self(input.0)); +/// fn apply(input: &Self::Input, id: SchematicId, context: &mut SchematicContext) { +/// context.entity_mut().unwrap().insert(Self(input.0)); /// } /// -/// fn remove(input: &Self::Input, context: &mut SchematicContext) { -/// entity.remove::(); +/// fn remove(input: &Self::Input, id: SchematicId, context: &mut SchematicContext) { +/// context.entity_mut().unwrap().remove::(); /// } /// } /// ``` @@ -48,19 +48,25 @@ pub trait Schematic: Reflect + Typed { type Input: FromReflect + GetTypeRegistration; /// Controls how this schematic is applied to the given entity. - fn apply(input: &Self::Input, context: &mut SchematicContext); + fn apply(input: &Self::Input, id: SchematicId, context: &mut SchematicContext); /// Controls how this schematic is removed from the given entity. - fn remove(input: &Self::Input, context: &mut SchematicContext); + fn remove(input: &Self::Input, id: SchematicId, context: &mut SchematicContext); /// Allows dependency assets to be loaded when this schematic is loaded. #[allow(unused_variables)] - fn preload_dependencies(input: &mut Self::Input, dependencies: &mut DependenciesBuilder) {} + fn preload_dependencies( + input: &mut Self::Input, + id: SchematicId, + dependencies: &mut DependenciesBuilder, + ) { + // By default, do nothing. + } } /// A custom [`From`]-like trait used to convert the [input] of a [schematic] /// to itself. /// -/// This is used by [derive macro] to automatically handle the conversion. +/// This is used by the [derive macro] to automatically handle the conversion. /// /// This trait is has a blanket implementation for any type where the input /// type satisfies [`Into`] for the schematic type. @@ -69,11 +75,40 @@ pub trait Schematic: Reflect + Typed { /// [schematic]: Schematic /// [derive macro]: bevy_proto_derive::Schematic pub trait FromSchematicInput { - fn from_input(input: T, context: &mut SchematicContext) -> Self; + fn from_input(input: T, id: SchematicId, context: &mut SchematicContext) -> Self; } impl> FromSchematicInput for S { - fn from_input(input: T, _context: &mut SchematicContext) -> S { + fn from_input(input: T, _id: SchematicId, _context: &mut SchematicContext) -> S { + input.into() + } +} + +/// A custom [`From`]-like trait used to convert the [input] of a [schematic] +/// to itself during the _preload_ phase of a schematic. +/// +/// This is used by the [derive macro] to automatically handle the conversion. +/// +/// This trait is has a blanket implementation for any type where the input +/// type satisfies [`Into`] for the schematic type. +/// +/// [input]: Schematic::Input +/// [schematic]: Schematic +/// [derive macro]: bevy_proto_derive::Schematic +pub trait FromSchematicPreloadInput { + fn from_preload_input( + input: T, + id: SchematicId, + dependencies: &mut DependenciesBuilder, + ) -> Self; +} + +impl> FromSchematicPreloadInput for S { + fn from_preload_input( + input: T, + _id: SchematicId, + _dependencies: &mut DependenciesBuilder, + ) -> Self { input.into() } } diff --git a/bevy_proto_backend/src/tree/access.rs b/bevy_proto_backend/src/tree/access.rs index df0e404..42e76c7 100644 --- a/bevy_proto_backend/src/tree/access.rs +++ b/bevy_proto_backend/src/tree/access.rs @@ -8,7 +8,7 @@ use bevy::prelude::Entity; use bevy::reflect::{std_traits::ReflectDefault, Reflect, ReflectDeserialize}; use serde::Deserialize; -use crate::schematics::{FromSchematicInput, SchematicContext}; +use crate::schematics::{FromSchematicInput, SchematicContext, SchematicId}; /// A deserializable prototype entity reference. /// @@ -343,7 +343,7 @@ impl> From for EntityAccess { } impl FromSchematicInput for Entity { - fn from_input(input: EntityAccess, context: &mut SchematicContext) -> Self { + fn from_input(input: EntityAccess, _id: SchematicId, context: &mut SchematicContext) -> Self { context .find_entity(&input) .unwrap_or_else(|| panic!("entity should exist at path {:?}", input.to_path())) @@ -351,7 +351,7 @@ impl FromSchematicInput for Entity { } impl FromSchematicInput for Entity { - fn from_input(input: ProtoEntity, context: &mut SchematicContext) -> Self { + fn from_input(input: ProtoEntity, _id: SchematicId, context: &mut SchematicContext) -> Self { let access: EntityAccess = input.into(); context .find_entity(&access) @@ -360,13 +360,13 @@ impl FromSchematicInput for Entity { } impl FromSchematicInput for Option { - fn from_input(input: EntityAccess, context: &mut SchematicContext) -> Self { + fn from_input(input: EntityAccess, _id: SchematicId, context: &mut SchematicContext) -> Self { context.find_entity(&input) } } impl FromSchematicInput for Option { - fn from_input(input: ProtoEntity, context: &mut SchematicContext) -> Self { + fn from_input(input: ProtoEntity, _id: SchematicId, context: &mut SchematicContext) -> Self { context.find_entity(&input.into()) } } @@ -393,7 +393,11 @@ impl FromSchematicInput for Option { pub struct ProtoEntityList(pub Vec); impl FromSchematicInput for Vec { - fn from_input(input: ProtoEntityList, context: &mut SchematicContext) -> Self { + fn from_input( + input: ProtoEntityList, + _id: SchematicId, + context: &mut SchematicContext, + ) -> Self { input .0 .into_iter() diff --git a/bevy_proto_derive/Cargo.toml b/bevy_proto_derive/Cargo.toml index abc6e01..fed4000 100644 --- a/bevy_proto_derive/Cargo.toml +++ b/bevy_proto_derive/Cargo.toml @@ -22,5 +22,6 @@ syn = "2.0" quote = "1.0" proc-macro2 = "1.0" proc-macro-crate = "1.3" +uuid = "1.3" optional-error = "0.1" to_phantom = "0.1" \ No newline at end of file diff --git a/bevy_proto_derive/src/asset_schematic/container_attributes.rs b/bevy_proto_derive/src/asset_schematic/container_attributes.rs new file mode 100644 index 0000000..20a0f8f --- /dev/null +++ b/bevy_proto_derive/src/asset_schematic/container_attributes.rs @@ -0,0 +1,40 @@ +use syn::{Attribute, Error}; + +use crate::common::input::{parse_input_meta, InputType, OutputType, SchematicIo}; +use crate::utils::constants::{ASSET_SCHEMATIC_ATTR, FROM_ATTR, INPUT_ATTR, INTO_ATTR}; +use crate::utils::{ + define_attribute, parse_bool, parse_nested_meta, AttrArg, AttrArgValue, AttrTarget, +}; + +define_attribute!("no_preload" => NoPreloadArg(bool) for AttrTarget::Asset); + +/// Attribute information on the container type. +#[derive(Default)] +pub(super) struct ContainerAttributes { + no_preload: NoPreloadArg, +} + +impl ContainerAttributes { + pub fn new(attrs: &[Attribute], io: &mut SchematicIo) -> Result { + let mut this = Self::default(); + + for attr in attrs { + if !attr.path().is_ident(ASSET_SCHEMATIC_ATTR) { + continue; + } + + parse_nested_meta!(attr, |meta| { + FROM_ATTR => io.try_set_input_ty(InputType::Existing(meta.value()?.parse()?), None), + INTO_ATTR => io.try_set_output_ty(OutputType::Custom(meta.value()?.parse()?), None), + INPUT_ATTR => parse_input_meta(meta, io), + NoPreloadArg::NAME => this.no_preload.try_set(Some(parse_bool(&meta)?), meta.input.span()), + })?; + } + + Ok(this) + } + + pub fn no_preload(&self) -> bool { + self.no_preload.get().copied().unwrap_or_default() + } +} diff --git a/bevy_proto_derive/src/asset_schematic/derive.rs b/bevy_proto_derive/src/asset_schematic/derive.rs new file mode 100644 index 0000000..1d09c6e --- /dev/null +++ b/bevy_proto_derive/src/asset_schematic/derive.rs @@ -0,0 +1,164 @@ +use crate::asset_schematic::container_attributes::ContainerAttributes; +use crate::common::data::{DeriveType, SchematicData}; +use crate::common::input::{ + generate_from_reflect_conversion, generate_input, generate_input_conversion, + generate_preload_input_conversion, InputType, OutputType, SchematicIo, +}; +use crate::utils::constants::{CONTEXT_IDENT, DEPENDENCIES_IDENT, ID_IDENT, INPUT_IDENT}; +use crate::utils::exports::{ + AssetSchematic, DependenciesBuilder, PreloadAssetSchematic, SchematicContext, SchematicId, +}; +use proc_macro2::{Ident, TokenStream}; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream}; +use syn::{DeriveInput, Error, Generics, Visibility}; + +pub(crate) struct DeriveAssetSchematic { + attrs: ContainerAttributes, + generics: Generics, + data: SchematicData, + io: SchematicIo, +} + +impl DeriveAssetSchematic { + fn attrs(&self) -> &ContainerAttributes { + &self.attrs + } + + fn generics(&self) -> &Generics { + &self.generics + } + + fn io(&self) -> &SchematicIo { + &self.io + } + + fn output_ty(&self) -> &OutputType { + self.io.output_ty() + } + + fn input_ty(&self) -> &InputType { + self.io.input_ty() + } + + fn data(&self) -> &SchematicData { + &self.data + } + + fn load_def(&self) -> TokenStream { + let from_reflect = generate_from_reflect_conversion(); + let conversion = generate_input_conversion(self.io()); + + quote! { + #from_reflect + + #conversion + + #INPUT_IDENT + } + } + + /// Generates the logic for `PreloadAssetSchematic::preload`. + fn preload_def(&self) -> Option { + if self.attrs.no_preload() { + return None; + } + + let from_reflect = generate_from_reflect_conversion(); + let conversion = generate_preload_input_conversion(self.io()); + + Some(quote! { + let #INPUT_IDENT = &#INPUT_IDENT; + + #from_reflect + + #conversion + + #INPUT_IDENT + }) + } + + fn try_to_tokens(&self) -> Result { + let ident: &Ident = self.io.ident(); + let (impl_generics, ty_generics, where_clause) = self.generics().split_for_impl(); + + let input = generate_input( + self.io(), + self.data(), + self.generics(), + self.attrs().no_preload(), + )?; + let load_def = self.load_def(); + + let input_vis = self.io.input_vis(); + let input_ty = match self.input_ty() { + InputType::Generated(ident) => { + quote!(#ident #ty_generics) + } + input_ty => input_ty.to_token_stream(), + }; + let output_ty = self.output_ty(); + + let preload_impl = self.preload_def().map( |preload_def| { + quote! { + impl #impl_generics #PreloadAssetSchematic for #ident #ty_generics #where_clause { + fn preload(#INPUT_IDENT: Self::Input, #ID_IDENT: #SchematicId, #DEPENDENCIES_IDENT: &mut #DependenciesBuilder) -> Self::Output { + #preload_def + } + } + } + }); + + let output = quote! { + #input + + impl #impl_generics #AssetSchematic for #ident #ty_generics #where_clause { + type Input = #input_ty; + type Output = #output_ty; + + fn load(#INPUT_IDENT: &Self::Input, #ID_IDENT: #SchematicId, #CONTEXT_IDENT: &mut #SchematicContext) -> Self::Output { + #load_def + } + } + + #preload_impl + }; + + Ok(match input_vis { + None | Some(Visibility::Inherited) => quote! { + const _: () = { + #output + }; + }, + _ => output, + }) + } +} + +impl Parse for DeriveAssetSchematic { + fn parse(input: ParseStream) -> syn::Result { + let input = input.parse::()?; + + let mut io = SchematicIo::new(&input); + + let attrs = ContainerAttributes::new(&input.attrs, &mut io)?; + + let data = SchematicData::new(input.data, &mut io, DeriveType::AssetSchematic)?; + + Ok(Self { + attrs, + generics: input.generics, + data, + io, + }) + } +} + +impl ToTokens for DeriveAssetSchematic { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self.try_to_tokens() { + Ok(output) => tokens.extend(output), + Err(err) => tokens.extend(err.to_compile_error()), + } + } +} diff --git a/bevy_proto_derive/src/asset_schematic/external.rs b/bevy_proto_derive/src/asset_schematic/external.rs new file mode 100644 index 0000000..d6cd57f --- /dev/null +++ b/bevy_proto_derive/src/asset_schematic/external.rs @@ -0,0 +1,44 @@ +use crate::asset_schematic::DeriveAssetSchematic; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::parse::{Parse, ParseStream}; + +/// Definition struct for the [`impl_external_asset_schematic`] macro. +/// +/// # Example +/// +/// ```ignore +/// impl_external_asset_schematic!{ +/// struct ExternalType { +/// #[schematic(asset)] +/// image: Handle +/// } +/// } +/// ``` +/// +/// [`impl_external_asset_schematic`]: crate::impl_external_asset_schematic +pub(crate) struct ExternalAssetSchematic { + schematic_data: DeriveAssetSchematic, + other_content: TokenStream, +} + +impl Parse for ExternalAssetSchematic { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + schematic_data: input.parse::()?, + other_content: input.parse::()?, + }) + } +} + +impl ToTokens for ExternalAssetSchematic { + fn to_tokens(&self, tokens: &mut TokenStream) { + let impls = &self.schematic_data; + let other = &self.other_content; + tokens.extend(quote! { + #impls + + #other + }); + } +} diff --git a/bevy_proto_derive/src/asset_schematic/mod.rs b/bevy_proto_derive/src/asset_schematic/mod.rs new file mode 100644 index 0000000..4a5d72a --- /dev/null +++ b/bevy_proto_derive/src/asset_schematic/mod.rs @@ -0,0 +1,6 @@ +pub(crate) use derive::DeriveAssetSchematic; +pub(crate) use external::ExternalAssetSchematic; + +mod container_attributes; +mod derive; +mod external; diff --git a/bevy_proto_derive/src/common/data/derive_type.rs b/bevy_proto_derive/src/common/data/derive_type.rs new file mode 100644 index 0000000..1bdcba7 --- /dev/null +++ b/bevy_proto_derive/src/common/data/derive_type.rs @@ -0,0 +1,19 @@ +use crate::utils::constants::{ASSET_SCHEMATIC_ATTR, SCHEMATIC_ATTR}; + +/// Indicates which derive macro is being used. +#[derive(Copy, Clone)] +pub(crate) enum DeriveType { + /// The `Schematic` derive macro. + Schematic, + /// The `AssetSchematic` derive macro. + AssetSchematic, +} + +impl DeriveType { + pub fn attr_name(&self) -> &'static str { + match self { + Self::Schematic => SCHEMATIC_ATTR, + Self::AssetSchematic => ASSET_SCHEMATIC_ATTR, + } + } +} diff --git a/bevy_proto_derive/src/common/data/mod.rs b/bevy_proto_derive/src/common/data/mod.rs new file mode 100644 index 0000000..9e84fbd --- /dev/null +++ b/bevy_proto_derive/src/common/data/mod.rs @@ -0,0 +1,7 @@ +pub(crate) use derive_type::*; +pub(crate) use schematic_data::*; +pub(crate) use variants::*; + +mod derive_type; +mod schematic_data; +mod variants; diff --git a/bevy_proto_derive/src/common/data/schematic_data.rs b/bevy_proto_derive/src/common/data/schematic_data.rs new file mode 100644 index 0000000..b03468d --- /dev/null +++ b/bevy_proto_derive/src/common/data/schematic_data.rs @@ -0,0 +1,35 @@ +use crate::common::data::{DeriveType, SchematicVariant}; +use crate::common::fields::SchematicFields; +use crate::common::input::SchematicIo; +use syn::{Data, Error}; + +/// The shape and data of a schematic. +pub(crate) enum SchematicData { + Struct(SchematicFields), + Enum(Vec), +} + +impl SchematicData { + pub fn new(data: Data, io: &mut SchematicIo, derive_type: DeriveType) -> Result { + match data { + Data::Struct(data) => Ok(Self::Struct(SchematicFields::new( + &data.fields, + io, + derive_type, + )?)), + Data::Enum(data) => Ok(Self::Enum( + data.variants + .into_iter() + .map(|variant| { + let fields = SchematicFields::new(&variant.fields, io, derive_type)?; + Ok(SchematicVariant { + ident: variant.ident, + fields, + }) + }) + .collect::>()?, + )), + Data::Union(data) => Err(Error::new(data.union_token.span, "unions not supported")), + } + } +} diff --git a/bevy_proto_derive/src/common/data/variants.rs b/bevy_proto_derive/src/common/data/variants.rs new file mode 100644 index 0000000..c2eec1c --- /dev/null +++ b/bevy_proto_derive/src/common/data/variants.rs @@ -0,0 +1,153 @@ +use crate::common::fields::{FieldKind, SchematicField, SchematicFields}; +use crate::common::input::SchematicIo; +use proc_macro2::{Ident, TokenStream}; +use quote::{format_ident, quote}; +use syn::{Error, Member}; + +pub(crate) struct SchematicVariant { + pub ident: Ident, + pub fields: SchematicFields, +} + +impl SchematicVariant { + pub fn generate_conversion_arm(&self, io: &SchematicIo) -> Result { + let input_ty = io.input_ty(); + let output_ty = io.output_ty(); + let variant = &self.ident; + + Ok(match &self.fields { + SchematicFields::Unit => quote! { + #input_ty::#variant => #output_ty::#variant + }, + SchematicFields::Named(fields) | SchematicFields::Unnamed(fields) => { + let mut patterns = Vec::new(); + let mut conversions = Vec::new(); + for field in fields.iter() { + match field.member() { + Member::Named(ident) => { + patterns.push(quote!(#ident)); + + let conversion = field.generate_conversion(Some(quote!(#ident)))?; + conversions.push(quote!(#ident: #conversion)) + } + Member::Unnamed(index) => { + let ident = format_ident!("field_{}", index); + patterns.push(quote!(#index: #ident)); + + let conversion = field.generate_conversion(Some(quote!(#ident)))?; + conversions.push(quote!(#index: #conversion)) + } + } + } + + quote! { + #input_ty::#variant{ #(#patterns,)* .. } => Self::#variant{ #(#conversions),* } + } + } + }) + } + + pub fn generate_preload_conversion_arm(&self, io: &SchematicIo) -> Result { + let input_ty = io.input_ty(); + let output_ty = io.output_ty(); + let variant = &self.ident; + + Ok(match &self.fields { + SchematicFields::Unit => quote! { + #input_ty::#variant => #output_ty::#variant + }, + SchematicFields::Named(fields) | SchematicFields::Unnamed(fields) => { + let mut patterns = Vec::new(); + let mut conversions = Vec::new(); + for field in fields.iter() { + match field.member() { + Member::Named(ident) => { + patterns.push(quote!(#ident)); + + let conversion = + field.generate_preload_conversion(Some(quote!(#ident)))?; + conversions.push(quote!(#ident: #conversion)) + } + Member::Unnamed(index) => { + let ident = format_ident!("field_{}", index); + patterns.push(quote!(#index: #ident)); + + let conversion = + field.generate_preload_conversion(Some(quote!(#ident)))?; + conversions.push(quote!(#index: #conversion)) + } + } + } + + quote! { + #input_ty::#variant{ #(#patterns,)* .. } => Self::#variant{ #(#conversions),* } + } + } + }) + } + + pub fn generate_preload_arm(&self, io: &SchematicIo) -> Result { + let input_ty = io.input_ty(); + let variant = &self.ident; + + Ok(match &self.fields { + SchematicFields::Unit => quote! { + #input_ty::#variant => {/* Do nothing */}, + }, + SchematicFields::Named(fields) | SchematicFields::Unnamed(fields) => { + let mut patterns = Vec::new(); + let mut preloads = Vec::new(); + let preload_fields = fields.iter().filter(|field| match field.config().kind() { + Some(FieldKind::Asset(config)) => config.preload(), + _ => false, + }); + for field in preload_fields { + match field.member() { + Member::Named(ident) => { + patterns.push(quote!(#ident)); + preloads.push(field.generate_preload(Some(quote!(*#ident)))?) + } + Member::Unnamed(index) => { + let ident = format_ident!("field_{}", index); + patterns.push(quote!(#index: #ident)); + preloads.push(field.generate_preload(Some(quote!(*#ident)))?) + } + } + } + + quote! { + #input_ty::#variant{ #(#patterns,)* .. } => { + #(#preloads)* + } + } + } + }) + } + + pub fn generate_definition(&self) -> TokenStream { + let variant = &self.ident; + match &self.fields { + SchematicFields::Unit => quote! { + #variant + }, + SchematicFields::Named(fields) => { + let fields = fields + .iter() + .filter(SchematicField::requires_input_field) + .map(SchematicField::generate_definition); + quote! { + #variant { #(#fields),* } + } + } + SchematicFields::Unnamed(fields) => { + let fields = fields + .iter() + .filter(SchematicField::requires_input_field) + .map(SchematicField::generate_definition); + quote! { + #variant ( #(#fields),* ) + } + } + } + } +} diff --git a/bevy_proto_derive/src/common/fields/asset_config.rs b/bevy_proto_derive/src/common/fields/asset_config.rs new file mode 100644 index 0000000..2ee5619 --- /dev/null +++ b/bevy_proto_derive/src/common/fields/asset_config.rs @@ -0,0 +1,203 @@ +use std::fmt::{Debug, Formatter}; + +use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; +use syn::spanned::Spanned; +use syn::{Error, GenericArgument, LitStr, PathArguments, Type}; + +use crate::utils::constants::ASSET_ATTR; +use crate::utils::{debug_attribute, NextId, RandomId}; +use crate::utils::{define_attribute, AttrArg, AttrArgValue, AttrTarget}; + +define_attribute!("preload" => AssetPreloadArg(bool) for AttrTarget::Asset); +define_attribute!("inline" => AssetInlineArg(bool) for AttrTarget::Asset); +define_attribute!("untyped" => AssetUntypedArg(bool) for AttrTarget::Asset); +define_attribute!("unique" => AssetUniqueArg(bool) for AttrTarget::Asset); +define_attribute!("type" => AssetTypeArg(Type) for AttrTarget::Asset); +define_attribute!("path" => AssetPathArg(LitStr) for AttrTarget::Asset); + +/// Specifies the configuration for asset-containing fields. +#[derive(Default)] +pub(crate) struct AssetConfig { + /// Used to specify whether the asset should be preloaded. + /// + /// Form: `#[asset_schematic(asset(preload))]`. + preload: AssetPreloadArg, + /// Used to specify whether the asset should result in a new asset handle being created. + /// + /// Form: `#[asset_schematic(asset(unique))]`. + unique: AssetUniqueArg, + /// Used to specify whether the asset should be allowed to be defined inlined. + /// + /// Form: `#[asset_schematic(asset(inline))]`. + inline: AssetInlineArg, + /// Used to specify a static path to an asset. + /// + /// Form: `#[asset_schematic(asset(path = "path/to/asset.png"))]`. + path: AssetPathArg, + /// Used to set the type of the asset. + /// + /// Form: `#[asset_schematic(asset(type = path::to::Foo))]`. + custom_type: AssetTypeArg, + /// Used to specify whether the asset should be defined as a `HandleUntyped`. + /// + /// Form: `#[asset_schematic(asset(untyped))]`. + untyped: AssetUntypedArg, +} + +impl AssetConfig { + pub fn preload(&self) -> bool { + self.preload.get().copied().unwrap_or_default() + } + + pub fn try_set_preload(&mut self, value: bool, span: Span) -> Result<(), Error> { + if self.unique() { + return Err(self.preload.invariant_error::(span)); + } + + self.preload.try_set(Some(value), span) + } + + pub fn unique(&self) -> bool { + self.unique.get().copied().unwrap_or_default() + } + + pub fn try_set_unique(&mut self, value: bool, span: Span) -> Result<(), Error> { + if self.preload() { + return Err(self.unique.invariant_error::(span)); + } + + self.unique.try_set(Some(value), span) + } + + pub fn inline(&self) -> bool { + self.inline.get().copied().unwrap_or_default() + } + + pub fn try_set_inline(&mut self, value: bool, span: Span) -> Result<(), Error> { + if self.path().is_some() { + return Err(self.inline.invariant_error::(span)); + } + + self.inline.try_set(Some(value), span) + } + + pub fn path(&self) -> Option<&LitStr> { + self.path.get() + } + + pub fn try_set_path(&mut self, value: LitStr, span: Span) -> Result<(), Error> { + if self.inline() { + return Err(self.inline.invariant_error::(span)); + } + + self.path.try_set(Some(value), span) + } + + pub fn custom_type(&self) -> Option<&Type> { + self.custom_type.get() + } + + pub fn try_set_custom_type(&mut self, value: Type, span: Span) -> Result<(), Error> { + if self.untyped() { + return Err(self.custom_type.invariant_error::(span)); + } + + self.custom_type.try_set(Some(value), span) + } + + pub fn untyped(&self) -> bool { + self.untyped.get().copied().unwrap_or_default() + } + + // TODO: Handle untyped assets + // pub fn try_set_untyped(&mut self, value: bool, span: Span) -> Result<(), Error> { + // if self.custom_type().is_some() { + // return Err(self.untyped.invariant_error::(span)); + // } + // + // self.untyped.try_set(Some(value), span) + // } + + /// Create a token generator that creates schematic IDs. + /// + /// This will choose between a random or stable ID based on the asset's `unique` attribute. + pub fn asset_id(&self) -> AssetIdGenerator { + AssetIdGenerator { + random: self.unique(), + } + } + + pub fn try_extract_asset_type<'a>(&'a self, defined_ty: &'a Type) -> Result<&'a Type, Error> { + if let Some(ty) = self.custom_type() { + Ok(ty) + } else { + AssetConfig::extract_asset_type(defined_ty) + } + } + + /// Attempts to extract the asset type from either a `Handle` or an `Option`. + fn extract_asset_type(ty: &Type) -> Result<&Type, Error> { + let create_error = || { + Error::new(ty.span(), format_args!( + "could not automatically extract asset type: please specify it manually using `{}({} = path::to::YourAssetType)`", + ASSET_ATTR, + AssetTypeArg::NAME + )) + }; + + match ty { + Type::Path(type_path) if type_path.qself.is_none() => { + if let Some(segment) = type_path.path.segments.last() { + let PathArguments::AngleBracketed(args) = &segment.arguments else { + return Err(create_error()); + }; + let Some(GenericArgument::Type(ty)) = args.args.first() else { + return Err(create_error()); + }; + + if segment.ident == "Handle" { + return Ok(ty); + } else if segment.ident == "Option" { + return Self::extract_asset_type(ty); + } + } + } + _ => {} + } + + Err(create_error()) + } +} + +impl Debug for AssetConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(")?; + + debug_attribute(f, |write| { + write(format_args!("{:?}", self.preload))?; + write(format_args!("{:?}", self.inline))?; + write(format_args!("{:?}", self.untyped))?; + write(format_args!("{:?}", self.custom_type))?; + write(format_args!("{:?}", self.path))?; + + Ok(()) + })?; + + write!(f, ")") + } +} + +pub(crate) struct AssetIdGenerator { + random: bool, +} + +impl ToTokens for AssetIdGenerator { + fn to_tokens(&self, tokens: &mut TokenStream) { + if self.random { + RandomId.to_tokens(tokens); + } else { + NextId.to_tokens(tokens); + } + } +} diff --git a/bevy_proto_derive/src/common/fields/config.rs b/bevy_proto_derive/src/common/fields/config.rs new file mode 100644 index 0000000..68e6161 --- /dev/null +++ b/bevy_proto_derive/src/common/fields/config.rs @@ -0,0 +1,160 @@ +use crate::common::data::DeriveType; +use crate::common::fields::{AssetConfig, EntityConfig}; +use crate::utils::constants::{ASSET_ATTR, ENTITY_ATTR, FROM_ATTR}; +use crate::utils::debug_attribute; +use crate::utils::{define_attribute, AttrArgValue, AttrTarget}; +use proc_macro2::Span; +use quote::ToTokens; +use std::fmt::{Debug, Formatter}; +use syn::{Error, Type}; + +define_attribute!("optional" => OptionalArg(bool) for AttrTarget::Field); + +/// The base configuration for the field of a `Schematic` or `AssetSchematic`. +pub(crate) struct FieldConfig { + /// The type of the derive. + /// + /// This isn't configured by the user, but is instead inferred from the derive type. + /// + /// The only reason this is stored is so that we can use it in error messages. + derive_type: DeriveType, + /// The kind of the field. + /// + /// This controls what the field does and all of its configuration. + kind: Option, + /// Whether the field should be generated as wrapped in an `Option`. + /// + /// The [`ProtoFieldBuilder`] will automatically try to infer this from the field's type, + /// but this attribute allows the user to configure it manually if needed. + /// + /// # Example + /// + /// ```ignore + /// #[derive(Schematic)] + /// struct Foo { + /// #[schematic(optional, entity)] + /// bar: Option, + /// } + /// ``` + /// + /// [`ProtoFieldBuilder`]: crate::common::fields::ProtoFieldBuilder + optional: OptionalArg, +} + +impl FieldConfig { + pub fn try_init_from_kind(&mut self, ty: Type, span: Span) -> Result<(), Error> { + match &self.kind { + None => { + self.kind = Some(FieldKind::From(ty)); + } + Some(current) => { + return Err(Error::new( + span, + format!("field already configured as `{:?}`", current), + )); + } + } + + Ok(()) + } + + pub fn try_init_entity_kind(&mut self, span: Span) -> Result<&mut EntityConfig, Error> { + match &self.kind { + None => { + self.kind = Some(FieldKind::Entity(EntityConfig::default())); + } + Some(FieldKind::Entity(_)) => {} + Some(current) => { + return Err(Error::new( + span, + format!("field already configured as `{:?}`", current), + )); + } + } + + let FieldKind::Entity(config) = self.kind.as_mut().unwrap() else { + unreachable!() + }; + Ok(config) + } + + pub fn try_init_asset_kind(&mut self, span: Span) -> Result<&mut AssetConfig, Error> { + match &self.kind { + None => { + self.kind = Some(FieldKind::Asset(AssetConfig::default())); + } + Some(FieldKind::Asset(_)) => {} + Some(current) => { + return Err(Error::new( + span, + format!("field already configured as `{:?}`", current), + )); + } + } + + let FieldKind::Asset(config) = self.kind.as_mut().unwrap() else { + unreachable!() + }; + Ok(config) + } + + pub fn kind(&self) -> Option<&FieldKind> { + self.kind.as_ref() + } + + pub fn optional(&self) -> bool { + self.optional.get().copied().unwrap_or_default() + } + + pub fn try_set_optional(&mut self, value: bool, span: Span) -> Result<(), Error> { + match self.kind() { + None | Some(FieldKind::From(_)) => Err(Error::new( + span, + "cannot set `optional` on a field that is not marked as an `entity` or `asset`", + )), + _ => self.optional.try_set(Some(value), span), + } + } +} + +impl Default for FieldConfig { + fn default() -> Self { + Self { + derive_type: DeriveType::Schematic, + kind: None, + optional: OptionalArg::default(), + } + } +} + +impl Debug for FieldConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let attr = self.derive_type.attr_name(); + write!(f, "#[{attr}(")?; + + debug_attribute(f, |write| { + write(format_args!("{:?}", self.optional))?; + write(format_args!("{:?}", self.kind))?; + + Ok(()) + })?; + + write!(f, ")]") + } +} + +pub(crate) enum FieldKind { + From(Type), + Entity(EntityConfig), + Asset(AssetConfig), +} + +impl Debug for FieldKind { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + Self::From(ty) => write!(f, "{FROM_ATTR} = {}", ty.to_token_stream()), + Self::Entity(config) => write!(f, "{ENTITY_ATTR}{:?}", config), + Self::Asset(config) => write!(f, "{ASSET_ATTR}{:?}", config), + } + } +} diff --git a/bevy_proto_derive/src/common/fields/entity_config.rs b/bevy_proto_derive/src/common/fields/entity_config.rs new file mode 100644 index 0000000..1fad942 --- /dev/null +++ b/bevy_proto_derive/src/common/fields/entity_config.rs @@ -0,0 +1,37 @@ +use crate::utils::debug_attribute; +use crate::utils::{define_attribute, AttrArgValue, AttrTarget}; +use proc_macro2::Span; +use std::fmt::{Debug, Formatter}; +use syn::{Error, LitStr}; + +define_attribute!("path" => EntityPathArg(LitStr) for AttrTarget::Field); + +#[derive(Default)] +pub(crate) struct EntityConfig { + /// Represents a static path to an entity in the prototype tree. + path: EntityPathArg, +} + +impl EntityConfig { + pub fn path(&self) -> Option<&LitStr> { + self.path.get() + } + + pub fn try_set_path(&mut self, value: LitStr, span: Span) -> Result<(), Error> { + self.path.try_set(Some(value), span) + } +} + +impl Debug for EntityConfig { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "(")?; + + debug_attribute(f, |write| { + write(format_args!("{:?}", self.path))?; + + Ok(()) + })?; + + write!(f, ")") + } +} diff --git a/bevy_proto_derive/src/common/fields/mod.rs b/bevy_proto_derive/src/common/fields/mod.rs new file mode 100644 index 0000000..2e0b807 --- /dev/null +++ b/bevy_proto_derive/src/common/fields/mod.rs @@ -0,0 +1,11 @@ +pub(crate) use asset_config::*; +pub(crate) use config::*; +pub(crate) use entity_config::*; +pub(crate) use schematic_field::*; +pub(crate) use schematic_fields::*; + +mod asset_config; +mod config; +mod entity_config; +mod schematic_field; +mod schematic_fields; diff --git a/bevy_proto_derive/src/common/fields/schematic_field.rs b/bevy_proto_derive/src/common/fields/schematic_field.rs new file mode 100644 index 0000000..e5ad5fc --- /dev/null +++ b/bevy_proto_derive/src/common/fields/schematic_field.rs @@ -0,0 +1,379 @@ +use crate::common::fields::{FieldConfig, FieldKind}; +use crate::utils::constants::{CONTEXT_IDENT, DEPENDENCIES_IDENT, INPUT_IDENT, TEMP_IDENT}; +use crate::utils::exports::{ + AssetServer, EntityAccess, FromReflect, FromSchematicInput, FromSchematicPreloadInput, + InlinableProtoAsset, ProtoAsset, Reflect, +}; +use crate::utils::NextId; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, quote_spanned, ToTokens}; +use syn::spanned::Spanned; +use syn::{parse_quote, Attribute, Error, Field, Member, Type}; + +/// The base field information for fields of a `Schematic` or `AssetSchematic`. +pub(crate) struct SchematicField { + /// The Bevy `#[reflect]` attributes applied to the field. + /// + /// These are used when generating the input type. + reflect_attrs: Vec, + /// The field's configuration. + config: FieldConfig, + /// The member (ident or index) used to access this field. + member: Member, + /// The type of the field, as defined by the user. + /// + /// This might not be what is actually generated in the input type. + defined_ty: Type, +} + +impl SchematicField { + pub fn new(field: &Field, field_index: usize) -> Self { + Self { + defined_ty: field.ty.clone(), + member: field + .ident + .clone() + .map(Member::Named) + .unwrap_or(Member::Unnamed(field_index.into())), + reflect_attrs: Vec::new(), + config: FieldConfig::default(), + } + } + + /// Pushes a `#[reflect]` attribute to the field. + pub fn push_reflect_attr(&mut self, attr: Attribute) { + self.reflect_attrs.push(attr); + } + + /// Returns a reference to the field's configuration. + pub fn config(&self) -> &FieldConfig { + &self.config + } + + /// Returns a mutable reference to the field's configuration. + pub fn config_mut(&mut self) -> &mut FieldConfig { + &mut self.config + } + + /// The member (ident or index) used to access this field. + pub fn member(&self) -> &Member { + &self.member + } + + /// Determines whether or not a generated input type should contain this field. + /// + /// For fields that cannot be configured by a prototype file (e.g. `path` attributes), + /// this should return `false`. + pub fn requires_input_field(self: &&Self) -> bool { + match self.config.kind() { + Some(FieldKind::Entity(config)) => config.path().is_none(), + Some(FieldKind::Asset(config)) => config.path().is_none(), + _ => true, + } + } + + /// The type of the field within a generated input type. + /// + /// This may or may not be the same as the field's user-defined type. + pub fn input_ty(&self) -> Result { + let wrap_option = |ty: Type| -> Type { + if self.config.optional() { + parse_quote!(::core::option::Option<#ty>) + } else { + ty + } + }; + + Ok(match self.config.kind() { + None => self.defined_ty.clone(), + Some(FieldKind::From(ty)) => wrap_option(ty.clone()), + Some(FieldKind::Entity(_)) => wrap_option(parse_quote!(#EntityAccess)), + Some(FieldKind::Asset(config)) => { + let ty = if config.untyped() { + None + } else { + Some(config.try_extract_asset_type(&self.defined_ty)?) + }; + + let inline = config.inline(); + + match (inline, ty) { + (true, Some(ty)) => wrap_option(parse_quote!(#InlinableProtoAsset<#ty>)), + (false, Some(ty)) => wrap_option(parse_quote!(#ProtoAsset<#ty>)), + _ => { + return Err(Error::new( + self.member.span(), + "untyped assets are not yet supported", + )) + } + } + } + }) + } + + /// Generate this field's definition within a generated input type. + pub fn generate_definition(&self) -> TokenStream { + let reflect_attrs = &self.reflect_attrs; + let ty = match self.input_ty() { + Ok(ty) => ty, + Err(err) => return err.to_compile_error(), + }; + match &self.member { + Member::Named(ident) => quote! { + #(#reflect_attrs)* + #ident: #ty + }, + Member::Unnamed(_) => quote! { + #(#reflect_attrs)* + #ty + }, + } + } + + /// Generate this field's conversion from the generated input type to the user-defined type. + /// + /// The generated `TokenStream` will be an expression that accesses the field from the input + /// type and converts it to the user-defined type. + pub fn generate_conversion( + &self, + custom_accessor: Option, + ) -> Result { + // Locate span at the field's member so that error messages point to the offending field. + let span = Span::call_site().located_at(self.member.span()); + + let accessor = custom_accessor.unwrap_or_else(|| { + let member = self.member(); + quote_spanned!(span => #INPUT_IDENT.#member) + }); + + Ok(match self.config.kind() { + Some(FieldKind::From(_)) => { + if self.config.optional() { + quote_spanned! {span => + #accessor.map(|#TEMP_IDENT| #FromSchematicInput::from_input( + #TEMP_IDENT, + #NextId, + #CONTEXT_IDENT, + )) + } + } else { + quote_spanned! {span => + #FromSchematicInput::from_input( + #accessor, + #NextId, + #CONTEXT_IDENT, + ) + } + } + } + Some(FieldKind::Entity(config)) => { + let access = if let Some(path) = config.path() { + quote_spanned!(span => #EntityAccess::from(#path)) + } else { + quote_spanned!(span => #accessor) + }; + + if self.config.optional() { + quote_spanned! {span => + #access.map(|#TEMP_IDENT| #FromSchematicInput::from_input( + #TEMP_IDENT, + #NextId, + #CONTEXT_IDENT, + )) + } + } else { + quote_spanned! {span => + #FromSchematicInput::from_input( + #access, + #NextId, + #CONTEXT_IDENT, + ) + } + } + } + Some(FieldKind::Asset(config)) => { + let id = config.asset_id(); + + if let Some(path) = config.path() { + quote_spanned! {span => + #CONTEXT_IDENT + .world() + .resource::<#AssetServer>() + .load(#path) + } + } else if self.config.optional() { + quote_spanned! {span => + #accessor.map(|#TEMP_IDENT| #FromSchematicInput::from_input( + #TEMP_IDENT, + #id, + #CONTEXT_IDENT, + )) + } + } else { + quote_spanned! {span => + #FromSchematicInput::from_input( + #accessor, + #id, + #CONTEXT_IDENT, + ) + } + } + } + _ => quote_spanned!(span => #accessor), + }) + } + + /// Generate the preload-specific conversion from the generated input type to the user-defined type. + /// + /// The generated `TokenStream` will be an expression that accesses the field from the input + /// type and converts it to the user-defined type. + pub fn generate_preload_conversion( + &self, + custom_accessor: Option, + ) -> Result { + // Locate span at the field's member so that error messages point to the offending field. + let span = Span::call_site().located_at(self.member.span()); + + let accessor = custom_accessor.unwrap_or_else(|| { + let member = self.member(); + quote_spanned!(span => #INPUT_IDENT.#member) + }); + + Ok(match self.config.kind() { + Some(FieldKind::From(_)) => { + if self.config.optional() { + quote_spanned! {span => + #accessor.map(|#TEMP_IDENT| #FromSchematicPreloadInput::from_preload_input( + #TEMP_IDENT, + #NextId, + #DEPENDENCIES_IDENT, + )) + } + } else { + quote_spanned! {span => + #FromSchematicPreloadInput::from_preload_input( + #accessor, + #NextId, + #DEPENDENCIES_IDENT, + ) + } + } + } + Some(FieldKind::Entity(_)) => TokenStream::new(), + Some(FieldKind::Asset(config)) => { + let id = config.asset_id(); + + if let Some(path) = config.path() { + if self.config.optional() { + quote_spanned! {span => + ::core::option::Option::Some(#DEPENDENCIES_IDENT.add_dependency(#path)) + } + } else { + quote_spanned! {span => + #DEPENDENCIES_IDENT.add_dependency(#path) + } + } + } else if self.config.optional() { + quote_spanned! {span => + #accessor.map(|#TEMP_IDENT| #FromSchematicPreloadInput::from_preload_input( + #TEMP_IDENT, + #id, + #DEPENDENCIES_IDENT, + )) + } + } else { + quote_spanned! {span => + #FromSchematicPreloadInput::from_preload_input( + #accessor, + #id, + #DEPENDENCIES_IDENT, + ) + } + } + } + _ => quote_spanned!(span => #accessor), + }) + } + + /// Generates the preload code for the field (assets-only). + /// + /// The generated `TokenStream` will be a statement that sets the user-defined field to the asset + /// loaded using the input type's field. + pub fn generate_preload( + &self, + variant_field_ident: Option, + ) -> Result { + // Locate span at the field's member so that error messages point to the offending field. + let span = Span::call_site().located_at(self.member.span()); + + Ok(match self.config.kind() { + Some(FieldKind::Asset(config)) if config.preload() => { + let accessor = variant_field_ident.unwrap_or_else(|| { + let member = self.member(); + quote_spanned!(span => #INPUT_IDENT.#member) + }); + + let asset_enum = if config.inline() { + InlinableProtoAsset.to_token_stream() + } else { + ProtoAsset.to_token_stream() + }; + + if let Some(path) = config.path() { + if self.config.optional() { + quote_spanned! {span => + #accessor = ::core::option::Option::Some( + #asset_enum::Handle(#DEPENDENCIES_IDENT.add_dependency(#path)) + ); + } + } else { + quote_spanned! {span => + #accessor = #asset_enum::Handle(#DEPENDENCIES_IDENT.add_dependency(#path)); + } + } + } else { + let input_ty = self.input_ty()?; + let asset_ty = config.try_extract_asset_type(&self.defined_ty)?; + let id = config.asset_id(); + + let convert = if self.config.optional() { + quote_spanned! {span => + #TEMP_IDENT.map(|#TEMP_IDENT| { + #asset_enum::Handle(#FromSchematicPreloadInput::from_preload_input( + #TEMP_IDENT, + #id, + #DEPENDENCIES_IDENT, + )) + }) + + } + } else { + quote_spanned! {span => + #asset_enum::Handle(#FromSchematicPreloadInput::from_preload_input( + #TEMP_IDENT, + #id, + #DEPENDENCIES_IDENT, + )) + } + }; + + quote_spanned! {span => + #accessor = { + let #TEMP_IDENT = <#input_ty as #FromReflect>::from_reflect( + &*#Reflect::clone_value(&#accessor) + ).unwrap_or_else(|| { + panic!( + "{} should have a functioning `FromReflect` impl", + ::std::any::type_name::<#asset_ty>() + ) + }); + + #convert + }; + } + } + } + _ => TokenStream::new(), + }) + } +} diff --git a/bevy_proto_derive/src/common/fields/schematic_fields.rs b/bevy_proto_derive/src/common/fields/schematic_fields.rs new file mode 100644 index 0000000..0168b2f --- /dev/null +++ b/bevy_proto_derive/src/common/fields/schematic_fields.rs @@ -0,0 +1,216 @@ +use crate::common::data::DeriveType; +use crate::common::fields::{ + AssetInlineArg, AssetPathArg, AssetPreloadArg, AssetTypeArg, AssetUniqueArg, EntityPathArg, + OptionalArg, SchematicField, +}; +use crate::common::input::{InputType, SchematicIo}; +use crate::utils::constants::{ASSET_ATTR, ENTITY_ATTR, FROM_ATTR}; +use crate::utils::{parse_bool, parse_nested_meta, AttrArg}; +use proc_macro2::Span; +use syn::meta::ParseNestedMeta; +use syn::spanned::Spanned; +use syn::{Error, Field, Fields, Type}; + +/// The collection of fields for a struct or enum. +pub(crate) enum SchematicFields { + Unit, + Named(Vec), + Unnamed(Vec), +} + +impl SchematicFields { + /// Creates a [`SchematicFields`] from a [`Fields`]. + /// + /// The `container_ident` is the name of the user-defined struct or enum that contains the fields + /// and will be used to generate the input type if necessary. + pub fn new( + fields: &Fields, + io: &mut SchematicIo, + derive_type: DeriveType, + ) -> Result { + Ok(match fields { + Fields::Unit => Self::Unit, + fields => { + let is_named = matches!(fields, Fields::Named(_)); + + let fields = fields + .iter() + .enumerate() + .map(|(field_index, field)| { + let proto_field = SchematicField::new(field, field_index); + + let builder = ProtoFieldBuilder { + field, + proto_field, + io, + derive_type, + }; + + builder.build() + }) + .collect::>()?; + + if is_named { + Self::Named(fields) + } else { + Self::Unnamed(fields) + } + } + }) + } +} + +/// Builds a [`SchematicField`] from a [`Field`]. +struct ProtoFieldBuilder<'a> { + field: &'a Field, + proto_field: SchematicField, + io: &'a mut SchematicIo, + derive_type: DeriveType, +} + +impl<'a> ProtoFieldBuilder<'a> { + fn build(mut self) -> Result { + for attr in &self.field.attrs { + if attr.path().is_ident("reflect") { + self.proto_field.push_reflect_attr(attr.clone()); + } + + if !attr.path().is_ident(self.derive_type.attr_name()) { + continue; + } + + match self.derive_type { + DeriveType::Schematic => { + parse_nested_meta!(attr, |meta| { + FROM_ATTR => self.parse_from_meta(meta), + ASSET_ATTR => self.parse_asset_meta(meta), + ENTITY_ATTR => self.parse_entity_meta(meta), + OptionalArg::NAME => self.parse_optional_meta(meta), + })?; + } + DeriveType::AssetSchematic => { + parse_nested_meta!(attr, |meta| { + FROM_ATTR => self.parse_from_meta(meta), + ASSET_ATTR => self.parse_asset_meta(meta), + OptionalArg::NAME => self.parse_optional_meta(meta), + })?; + } + } + } + + // Automatically detect `Option` types + if self.detect_optional() { + self.proto_field + .config_mut() + .try_set_optional(true, self.field.ty.span()) + .ok(); + } + + Ok(self.proto_field) + } + + /// Detects if the field is an `Option` type. + /// + /// Returns `true` if the field is an `Option` type or if it was manually marked as optional. + fn detect_optional(&mut self) -> bool { + if self.proto_field.config().optional() { + return true; + } + + match &self.field.ty { + Type::Path(ty_path) => ty_path + .path + .segments + .last() + .map(|segment| segment.ident == "Option" && !segment.arguments.is_empty()) + .unwrap_or_default(), + _ => false, + } + } + + /// Parse a `#[schematic(asset)]` attribute. + /// + /// This takes in the meta starting at `asset`. + fn parse_asset_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { + self.require_input(meta.path.span())?; + + let config = self + .proto_field + .config_mut() + .try_init_asset_kind(meta.path.span())?; + + if meta.input.is_empty() { + return Ok(()); + } + + match self.derive_type { + DeriveType::Schematic => { + parse_nested_meta!(meta, |meta| { + AssetPreloadArg::NAME => config.try_set_preload(parse_bool(&meta)?, meta.input.span()), + AssetInlineArg::NAME => config.try_set_inline(parse_bool(&meta)?, meta.input.span()), + AssetUniqueArg::NAME => config.try_set_unique(parse_bool(&meta)?, meta.input.span()), + AssetPathArg::NAME => config.try_set_path(meta.value()?.parse()?, meta.input.span()), + AssetTypeArg::NAME => config.try_set_custom_type(meta.value()?.parse()?, meta.input.span()), + }) + } + DeriveType::AssetSchematic => { + parse_nested_meta!(meta, |meta| { + AssetInlineArg::NAME => config.try_set_inline(parse_bool(&meta)?, meta.input.span()), + AssetPathArg::NAME => config.try_set_path(meta.value()?.parse()?, meta.input.span()), + AssetTypeArg::NAME => config.try_set_custom_type(meta.value()?.parse()?, meta.input.span()), + }) + } + } + } + + /// Parse a `#[schematic(entity)]` attribute. + /// + /// This takes in the meta starting at `entity`. + fn parse_entity_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { + self.require_input(meta.path.span())?; + + let config = self + .proto_field + .config_mut() + .try_init_entity_kind(meta.path.span())?; + + if meta.input.is_empty() { + return Ok(()); + } + + parse_nested_meta!(meta, |meta| { + EntityPathArg::NAME => config.try_set_path(meta.value()?.parse()?, meta.input.span()), + }) + } + + /// Parse a `#[schematic(from)]` attribute. + /// + /// This takes in the meta starting at `from`. + fn parse_from_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { + self.require_input(meta.path.span())?; + + self.proto_field + .config_mut() + .try_init_from_kind(meta.value()?.parse()?, meta.input.span()) + } + + /// Parse a `#[schematic(optional)]` attribute. + /// + /// This takes in the meta starting at `optional`. + fn parse_optional_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { + self.proto_field + .config_mut() + .try_set_optional(parse_bool(&meta)?, meta.input.span()) + } + + /// Method used to require that a `Schematic::Input` type be generated for the attribute + /// (identified by the given [`Span`]). + fn require_input(&mut self, span: Span) -> Result<(), Error> { + match &self.io.input_ty() { + InputType::Generated(_) => Ok(()), + _ => self + .io + .try_set_input_ty(InputType::new_generated(self.io.ident()), Some(span)), + } + } +} diff --git a/bevy_proto_derive/src/common/input/attr.rs b/bevy_proto_derive/src/common/input/attr.rs new file mode 100644 index 0000000..2c15e12 --- /dev/null +++ b/bevy_proto_derive/src/common/input/attr.rs @@ -0,0 +1,32 @@ +use crate::common::input::{InputType, SchematicIo}; +use crate::utils::parse_nested_meta; +use crate::utils::{define_attribute, AttrArg, AttrTarget}; +use proc_macro2::Ident; +use quote::ToTokens; +use std::fmt::{Debug, Formatter}; +use syn::meta::ParseNestedMeta; +use syn::{Error, Visibility}; + +define_attribute!("vis" => InputVisArg(Visibility) for AttrTarget::InputVisibility, no_debug); +define_attribute!("name" => InputNameArg(Ident) for AttrTarget::Input); + +impl Debug for InputVisArg { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match &self.0 { + None => Ok(()), + Some(Visibility::Inherited) => panic!("cannot explicitly set visibility to inherited"), + Some(Visibility::Public(_)) => write!(f, "{} = pub", Self::NAME), + Some(Visibility::Restricted(res)) => { + write!(f, "{} = {}", Self::NAME, res.to_token_stream()) + } + } + } +} + +/// Parses input attributes into the given [`SchematicIo`]. +pub(crate) fn parse_input_meta(meta: ParseNestedMeta, io: &mut SchematicIo) -> Result<(), Error> { + parse_nested_meta!(meta, |meta| { + InputVisArg::NAME => io.try_set_input_vis(meta.value()?.parse()?, None), + InputNameArg::NAME => io.try_set_input_ty(InputType::Generated(meta.value()?.parse()?), None), + }) +} diff --git a/bevy_proto_derive/src/common/input/converters.rs b/bevy_proto_derive/src/common/input/converters.rs new file mode 100644 index 0000000..2916c52 --- /dev/null +++ b/bevy_proto_derive/src/common/input/converters.rs @@ -0,0 +1,89 @@ +use crate::common::input::{InputType, OutputType, SchematicIo}; +use crate::utils::constants::{CONTEXT_IDENT, DEPENDENCIES_IDENT, INPUT_IDENT}; +use crate::utils::exports::{FromReflect, FromSchematicInput, FromSchematicPreloadInput, Reflect}; +use crate::utils::NextId; +use proc_macro2::TokenStream; +use quote::quote; + +/// Generates a statement that sets [`INPUT_IDENT`] to the output type using `FromSchematicInput`. +/// +/// Returns `None` if no conversion is necessary. +pub(crate) fn generate_input_conversion(io: &SchematicIo) -> Option { + let input_ty = io.input_ty(); + let output_ty = io.output_ty(); + + match (input_ty, output_ty) { + (InputType::Reflexive, OutputType::Reflexive) => { + // === No Conversion Necessary === // + None + } + (_, OutputType::Custom(output_ty)) => { + // === Input -> Self -> Output === // + Some(quote! { + let #INPUT_IDENT = >::from_input( + #INPUT_IDENT, #NextId, #CONTEXT_IDENT + ); + let #INPUT_IDENT = <#output_ty as #FromSchematicInput>::from_input( + #INPUT_IDENT, #NextId, #CONTEXT_IDENT + ); + }) + } + _ => { + // === Input -> Self === // + Some(quote! { + let #INPUT_IDENT = >::from_input( + #INPUT_IDENT, #NextId, #CONTEXT_IDENT + ); + }) + } + } +} + +/// Generates a statement that sets [`INPUT_IDENT`] to the output type using `FromSchematicPreloadInput`. +/// +/// Returns `None` if no conversion is necessary. +pub(crate) fn generate_preload_input_conversion(io: &SchematicIo) -> Option { + let input_ty = io.input_ty(); + let output_ty = io.output_ty(); + + match (input_ty, output_ty) { + (InputType::Reflexive, OutputType::Reflexive) => { + // === No Conversion Necessary === // + None + } + (_, OutputType::Custom(output_ty)) => { + // === Input -> Self -> Output === // + Some(quote! { + let #INPUT_IDENT = >::from_preload_input( + #INPUT_IDENT, #NextId, #DEPENDENCIES_IDENT + ); + let #INPUT_IDENT = <#output_ty as #FromSchematicPreloadInput>::from_preload_input( + #INPUT_IDENT, #NextId, #DEPENDENCIES_IDENT + ); + }) + } + _ => { + // === Input -> Self === // + Some(quote! { + let #INPUT_IDENT = >::from_preload_input( + #INPUT_IDENT, #NextId, #DEPENDENCIES_IDENT + ); + }) + } + } +} + +/// Generates a statement that sets [`INPUT_IDENT`] to a cloned instance of its +/// concrete type using `FromReflect`. +pub(crate) fn generate_from_reflect_conversion() -> TokenStream { + quote! { + let #INPUT_IDENT = ::from_reflect( + &*#Reflect::clone_value(#INPUT_IDENT) + ).unwrap_or_else(|| { + panic!( + "{} should have a functioning `FromReflect` impl", + std::any::type_name::() + ) + }); + } +} diff --git a/bevy_proto_derive/src/common/input/generate.rs b/bevy_proto_derive/src/common/input/generate.rs new file mode 100644 index 0000000..f7aa3d8 --- /dev/null +++ b/bevy_proto_derive/src/common/input/generate.rs @@ -0,0 +1,227 @@ +use proc_macro2::TokenStream; +use quote::quote; +use syn::{Error, Generics}; +use to_phantom::ToPhantom; + +use crate::common::data::{SchematicData, SchematicVariant}; +use crate::common::fields::{SchematicField, SchematicFields}; +use crate::common::input::{InputType, SchematicIo}; +use crate::utils::constants::{CONTEXT_IDENT, DEPENDENCIES_IDENT, ID_IDENT, INPUT_IDENT}; +use crate::utils::exports::{ + DependenciesBuilder, FromSchematicInput, FromSchematicPreloadInput, Reflect, SchematicContext, + SchematicId, +}; + +/// Generates the input type for the schematic. +pub(crate) fn generate_input( + io: &SchematicIo, + data: &SchematicData, + generics: &Generics, + no_preload: bool, +) -> Result, Error> { + let input_ident = match io.input_ty() { + InputType::Generated(ident) => ident, + _ => return Ok(None), + }; + + let base_ident = io.ident(); + // Defaults to the visibility of the schematic to avoid "private type in public interface" errors + let vis = io.input_vis().unwrap_or(io.vis()); + + let (impl_generics, impl_ty_generics, where_clause) = generics.split_for_impl(); + + // Ex: FooInput + let input_ty = quote!(#input_ident #impl_ty_generics); + // Ex: FooInput + let input_ty_def = quote!(#input_ident #impl_generics); + // Used to allow generics to be defined on the input type + let phantom_ty = if generics.params.is_empty() { + None + } else { + Some(generics.to_phantom()) + }; + + let make_from_impl = |body: TokenStream| { + quote! { + impl #impl_generics #FromSchematicInput<#input_ty> for #base_ident #impl_ty_generics #where_clause { + fn from_input(#INPUT_IDENT: #input_ty, #ID_IDENT: #SchematicId, #CONTEXT_IDENT: &mut #SchematicContext) -> Self { + #body + } + } + } + }; + + let make_from_preload_impl = |body: TokenStream| { + if no_preload { + None + } else { + Some(quote! { + impl #impl_generics #FromSchematicPreloadInput<#input_ty> for #base_ident #impl_ty_generics #where_clause { + fn from_preload_input(#INPUT_IDENT: #input_ty, #ID_IDENT: #SchematicId, #DEPENDENCIES_IDENT: &mut #DependenciesBuilder) -> Self { + #body + } + } + }) + } + }; + + Ok(match data { + SchematicData::Struct(SchematicFields::Unit) => { + let from_impl = make_from_impl(quote!(Self)); + let from_preload_impl = make_from_preload_impl(quote!(Self)); + + Some(quote! { + #[derive(#Reflect)] + #vis struct #input_ty_def #where_clause; + + #from_impl + + #from_preload_impl + }) + } + SchematicData::Struct(SchematicFields::Unnamed(fields)) => { + let definitions = fields + .iter() + .filter(SchematicField::requires_input_field) + .map(|field| field.generate_definition()); + + let conversions = fields + .iter() + .map(|field| field.generate_conversion(None)) + .collect::, Error>>()?; + + let from_impl = make_from_impl(quote! { + Self( + #(#conversions),* + ) + }); + + let preload_conversions = fields + .iter() + .map(|field| field.generate_preload_conversion(None)) + .collect::, Error>>()?; + + let from_preload_impl = make_from_preload_impl(quote! { + Self( + #(#preload_conversions),* + ) + }); + + let phantom_ty = phantom_ty.map(|phantom_ty| { + quote! { + #[reflect(ignore)] + #phantom_ty, + } + }); + + Some(quote! { + #[derive(#Reflect)] + #vis struct #input_ty_def ( + #(#definitions,)* + #phantom_ty + ) #where_clause; + + #from_impl + + #from_preload_impl + }) + } + SchematicData::Struct(SchematicFields::Named(fields)) => { + let definitions = fields + .iter() + .filter(SchematicField::requires_input_field) + .map(|field| field.generate_definition()); + + let members = fields + .iter() + .map(SchematicField::member) + .collect::>(); + + let conversions = fields + .iter() + .map(|field| field.generate_conversion(None)) + .collect::, Error>>()?; + + let from_impl = make_from_impl(quote! { + Self { + #(#members: #conversions),* + } + }); + + let preload_conversions = fields + .iter() + .map(|field| field.generate_preload_conversion(None)) + .collect::, Error>>()?; + + let from_preload_impl = make_from_preload_impl(quote! { + Self { + #(#members: #preload_conversions),* + } + }); + + let phantom_ty = phantom_ty.map(|phantom_ty| { + quote! { + #[reflect(ignore)] + __phantom_ty__: #phantom_ty, + } + }); + + Some(quote! { + #[derive(#Reflect)] + #vis struct #input_ty_def #where_clause { + #(#definitions,)* + #phantom_ty + } + + #from_impl + + #from_preload_impl + }) + } + SchematicData::Enum(variants) => { + let definitions = variants.iter().map(SchematicVariant::generate_definition); + + let conversions = variants + .iter() + .map(|variant| variant.generate_conversion_arm(io)) + .collect::, Error>>()?; + + let from_impl = make_from_impl(quote! { + match #INPUT_IDENT { + #(#conversions,)* + _ => unreachable!(), + } + }); + + let preload_conversions = variants + .iter() + .map(|variant| variant.generate_preload_conversion_arm(io)) + .collect::, Error>>()?; + + let from_preload_impl = make_from_preload_impl(quote! { + match #INPUT_IDENT { + #(#preload_conversions,)* + _ => unreachable!(), + } + }); + + let phantom_ty = phantom_ty.map(|phantom_ty| { + quote! { + _Phantom(#[reflect(ignore)] #phantom_ty), + } + }); + + Some(quote! { + #[derive(#Reflect)] + #vis enum #input_ty_def #where_clause { + #(#definitions,)* + #phantom_ty + } + + #from_impl + + #from_preload_impl + }) + } + }) +} diff --git a/bevy_proto_derive/src/common/input/input_ty.rs b/bevy_proto_derive/src/common/input/input_ty.rs new file mode 100644 index 0000000..0428a12 --- /dev/null +++ b/bevy_proto_derive/src/common/input/input_ty.rs @@ -0,0 +1,38 @@ +use proc_macro2::{Ident, Span, TokenStream}; +use quote::{format_ident, ToTokens}; +use syn::{Token, TypePath}; + +/// The type of the `Schematic::Input`. +#[derive(Default)] +pub(crate) enum InputType { + /// Corresponds to using `Self`. + #[default] + Reflexive, + /// Specifies an existing type. + /// + /// Most often this will be a user-generated type made to be used in attribute arguments + /// like `from = path::to::Type`. + Existing(TypePath), + /// Indicates that a new input type will need to be generated with the given identifier. + Generated(Ident), +} + +impl InputType { + /// Creates a new [`InputType::Generated`] based on the given identifier. + /// + /// The new identifier will be suffixed with `Input` + /// (e.g. `Foo` becomes `FooInput`). + pub fn new_generated(ident: &Ident) -> Self { + Self::Generated(format_ident!("{}Input", ident)) + } +} + +impl ToTokens for InputType { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + InputType::Reflexive => Token![Self](Span::call_site()).to_tokens(tokens), + InputType::Existing(path) => path.to_tokens(tokens), + InputType::Generated(ident) => ident.to_tokens(tokens), + } + } +} diff --git a/bevy_proto_derive/src/common/input/io.rs b/bevy_proto_derive/src/common/input/io.rs new file mode 100644 index 0000000..07edbbd --- /dev/null +++ b/bevy_proto_derive/src/common/input/io.rs @@ -0,0 +1,107 @@ +use crate::common::input::{InputType, InputVisArg, OutputType}; +use crate::utils::AttrArgValue; +use proc_macro2::{Ident, Span}; +use quote::ToTokens; +use syn::spanned::Spanned; +use syn::{DeriveInput, Error, Visibility}; + +/// The input and output types of a schematic. +/// +/// An input type is the type that the schematic is deserialized from, +/// while an output type is the type that the schematic is applied as. +pub(crate) struct SchematicIo { + /// The name of the schematic. + ident: Ident, + /// The visibility of the schematic. + vis: Visibility, + /// The visibility of the input type. + input_vis: InputVisArg, + /// The input type. + input_ty: InputType, + /// The output type. + output_ty: OutputType, +} + +impl SchematicIo { + pub fn new(input: &DeriveInput) -> Self { + Self { + ident: input.ident.clone(), + vis: input.vis.clone(), + input_vis: InputVisArg::default(), + input_ty: InputType::Reflexive, + output_ty: OutputType::Reflexive, + } + } + + /// The user-defined name of the schematic. + pub fn ident(&self) -> &Ident { + &self.ident + } + + /// The user-defined visibility of the schematic. + pub fn vis(&self) -> &Visibility { + &self.vis + } + + pub fn input_ty(&self) -> &InputType { + &self.input_ty + } + + pub fn try_set_input_ty(&mut self, value: InputType, span: Option) -> Result<(), Error> { + match &self.input_ty { + InputType::Reflexive => { + self.input_ty = value; + Ok(()) + } + InputType::Existing(ty) => { + let message = if matches!(value, InputType::Generated(_)) { + format!( + "an input type needs to be generated, but it's already specified as {}", + ty.to_token_stream() + ) + } else { + format!("input type already specified as {}", ty.to_token_stream()) + }; + Err(Error::new(span.unwrap_or_else(|| value.span()), message)) + } + InputType::Generated(ident) => Err(Error::new( + span.unwrap_or_else(|| value.span()), + format_args!("input type already specified as {}", ident), + )), + } + } + + pub fn output_ty(&self) -> &OutputType { + &self.output_ty + } + + pub fn try_set_output_ty( + &mut self, + value: OutputType, + span: Option, + ) -> Result<(), Error> { + match &self.output_ty { + OutputType::Reflexive => { + self.output_ty = value; + Ok(()) + } + OutputType::Custom(ty) => Err(Error::new( + span.unwrap_or_else(|| value.span()), + format_args!("output type already specified as {}", ty.to_token_stream()), + )), + } + } + + pub fn input_vis(&self) -> Option<&Visibility> { + self.input_vis.get() + } + + pub fn try_set_input_vis( + &mut self, + value: Visibility, + span: Option, + ) -> Result<(), Error> { + let span = span.unwrap_or_else(|| value.span()); + self.input_vis.try_set(Some(value), span) + } +} diff --git a/bevy_proto_derive/src/common/input/mod.rs b/bevy_proto_derive/src/common/input/mod.rs new file mode 100644 index 0000000..054c2e9 --- /dev/null +++ b/bevy_proto_derive/src/common/input/mod.rs @@ -0,0 +1,13 @@ +pub(crate) use attr::*; +pub(crate) use converters::*; +pub(crate) use generate::*; +pub(crate) use input_ty::*; +pub(crate) use io::*; +pub(crate) use output_ty::*; + +mod attr; +mod converters; +mod generate; +mod input_ty; +mod io; +mod output_ty; diff --git a/bevy_proto_derive/src/common/input/output_ty.rs b/bevy_proto_derive/src/common/input/output_ty.rs new file mode 100644 index 0000000..b34a2d1 --- /dev/null +++ b/bevy_proto_derive/src/common/input/output_ty.rs @@ -0,0 +1,22 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::TypePath; + +/// The output type of the `Schematic` or `AssetSchematic`. +#[derive(Default)] +pub(crate) enum OutputType { + /// Outputs as `Self`. + #[default] + Reflexive, + /// Outputs as the given type. + Custom(TypePath), +} + +impl ToTokens for OutputType { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Reflexive => tokens.extend(quote!(Self)), + Self::Custom(ty) => tokens.extend(quote!(#ty)), + } + } +} diff --git a/bevy_proto_derive/src/common/mod.rs b/bevy_proto_derive/src/common/mod.rs new file mode 100644 index 0000000..1760cbe --- /dev/null +++ b/bevy_proto_derive/src/common/mod.rs @@ -0,0 +1,3 @@ +pub(crate) mod data; +pub(crate) mod fields; +pub(crate) mod input; diff --git a/bevy_proto_derive/src/lib.rs b/bevy_proto_derive/src/lib.rs index 18e23b2..51bc8df 100644 --- a/bevy_proto_derive/src/lib.rs +++ b/bevy_proto_derive/src/lib.rs @@ -1,10 +1,13 @@ use proc_macro::TokenStream; +use crate::asset_schematic::{DeriveAssetSchematic, ExternalAssetSchematic}; use quote::ToTokens; use syn::parse_macro_input; use crate::schematic::{DeriveSchematic, ExternalSchematic}; +mod asset_schematic; +mod common; mod schematic; mod utils; @@ -95,15 +98,41 @@ mod utils; /// /// #### Arguments /// -/// ##### `({lazy|preload})` +/// ##### `(preload)` /// /// _Optional_ /// -/// If `lazy`, then the asset won't be loaded until the schematic is actually used. -/// If `preload`, then the asset will be preloaded as a dependency of the schematic. +/// If present, then the asset will be preloaded as a dependency of the schematic. /// -/// The `lazy` variant is not strictly needed, since it corresponds to the default behavior. -/// However, it exists for users who may want the distinction to be extra explicit. +/// Cannot be used with the `unique` argument. +/// +/// ##### `(unique)` +/// +/// _Optional_ +/// +/// Note: This attribute does nothing without the `inline` argument. +/// +/// By default, when an asset is loaded inline it will only create a single asset. +/// Additional loads will point to the same asset. +/// +/// In some cases, it's desirable to have each load create a new asset. +/// This attribute allows a unique asset to be created upon each load. +/// +/// Cannot be used with the `preload` argument. +/// +/// ##### `(inline)` +/// +/// _Optional_ +/// +/// Normally, assets are defined in their own unique file and referenced by path. +/// +/// However, there are times when it would be better to define an asset inline with a schematic. +/// This attribute allows an asset to either be defined by path or inline. +/// +/// The asset type within the `Handle` or the type specified by the `type` argument must implement `AssetSchematic`. +/// This will result in the field being generated as an `InlinableProtoAsset`. +/// +/// Cannot be used with the `path` argument. /// /// ##### `(path = "path/to/asset.png")` /// @@ -114,6 +143,16 @@ mod utils; /// This can be used when a particular asset should always be used. /// When this argument is found, the corresponding field will be removed from the generated input type. /// +/// Cannot be used with the `inline` argument. +/// +/// ##### `(type = path::to::AssetType)` +/// +/// _Optional_ +/// +/// When using a typed handle, this macro will attempt to infer the asset type from the handle's type. +/// However, if this fails or if defining an asset schematic type that is not the asset itself, +/// this attribute can be used with the desired type. +/// /// ### `#[schematic(entity)]` /// /// This attribute is used with an `Entity` or `Option` field to easily setup basic entity relations. @@ -141,6 +180,14 @@ mod utils; /// - `FromSchematicInput for MyFieldType` /// /// This is useful for defining custom logic or controlling the serialized representation. +/// +/// ### `#[schematic(optional)]` +/// +/// Entity and asset fields are able to be defined as optional. +/// Normally, this macro will determine whether this is the case by the type. +/// If this fails, this attribute can be used to force the field to be optional. +/// +/// It can also be used to opt-out by specifying `#[schematic(optional = false)]`. #[proc_macro_derive(Schematic, attributes(schematic))] pub fn derive_schematic(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as DeriveSchematic); @@ -190,3 +237,67 @@ pub fn impl_external_schematic(input: TokenStream) -> TokenStream { let input = parse_macro_input!(input as ExternalSchematic); input.to_token_stream().into() } + +/// Derive the `AssetSchematic` trait. +/// +/// This macro will generate the impl for `AssetSchematic` as well as a corresponding +/// `Schematic::Input` type if one is needed. +/// +/// # Attributes +/// +/// This macro supports most of the attributes that the [`Schematic` derive macro] supports, +/// but with the `asset_schematic` namespace rather than `schematic`. +/// +/// ## Container Attributes +/// +/// This macro shares many of the same container attributes as the `Schematic` derive macro including: +/// - `#[asset_schematic(input)]` +/// - `#[asset_schematic(from = path::to::Type)]` +/// - `#[asset_schematic(into = path::to::Type)]` +/// +/// ### `#[asset_schematic(no_preload)]` +/// +/// By default, this macro will also generate the impl for `PreloadAssetSchematic` to allow +/// the asset to be preloaded. +/// +/// This is useful for assets that are used frequently and/or are small in size, +/// but sometimes this isn't always desirable or even possible. +/// +/// This attribute disables the generation of the `PreloadAssetSchematic` impl. +/// +/// ## Field Attributes +/// +/// This macro shares many of the same field attributes as the `Schematic` derive macro including: +/// - `#[asset_schematic(asset)]` +/// - `#[asset_schematic(from = path::to::FieldType)]` +/// - `#[asset_schematic(optional)]` +/// +/// For the `asset` attribute, the following arguments are supported: +/// - `(inline)` +/// - `(path = "path/to/asset.png")` +/// - `(type = path::to::AssetType)` +/// +/// [`Schematic` derive macro]: derive_schematic +#[proc_macro_derive(AssetSchematic, attributes(asset_schematic))] +pub fn derive_asset_schematic(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveAssetSchematic); + input.into_token_stream().into() +} + +/// Internal macro used to easily implement `AssetSchematic` on external types. +/// +/// Takes a type definition: +/// +/// ```ignore +/// impl_external_asset_schematic! { +/// struct SomeExternalAssetType { +/// foo: usize +/// } +/// } +/// ``` +#[doc(hidden)] +#[proc_macro] +pub fn impl_external_asset_schematic(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as ExternalAssetSchematic); + input.to_token_stream().into() +} diff --git a/bevy_proto_derive/src/schematic/container_attributes.rs b/bevy_proto_derive/src/schematic/container_attributes.rs index 7d2df28..ca860bf 100644 --- a/bevy_proto_derive/src/schematic/container_attributes.rs +++ b/bevy_proto_derive/src/schematic/container_attributes.rs @@ -1,22 +1,11 @@ use std::fmt::{Debug, Formatter}; -use proc_macro2::TokenStream; -use quote::ToTokens; +use crate::common::input::{parse_input_meta, InputType, OutputType, SchematicIo}; +use crate::utils::constants::{FROM_ATTR, INPUT_ATTR, INTO_ATTR, SCHEMATIC_ATTR}; use syn::meta::ParseNestedMeta; -use syn::spanned::Spanned; -use syn::{Attribute, Error, LitStr, Visibility}; +use syn::{Attribute, Error, LitStr}; -use crate::schematic::input::InputType; -use crate::schematic::self_type::SelfType; -use crate::schematic::utils::filter_attributes; - -const INPUT_ATTR: &str = "input"; -const INPUT_VIS: &str = "vis"; -const INPUT_NAME: &str = "name"; - -const FROM_ATTR: &str = "from"; - -const INTO_ATTR: &str = "into"; +use crate::utils::{parse_nested_meta, unsupported_arg}; const KIND_ATTR: &str = "kind"; const KIND_BUNDLE: &str = "bundle"; @@ -24,172 +13,55 @@ const KIND_RESOURCE: &str = "resource"; /// Attribute information on the container type. #[derive(Default)] -pub(crate) struct ContainerAttributes { - input_vis: Option, +pub(super) struct ContainerAttributes { kind: SchematicKind, } impl ContainerAttributes { - pub fn new( - attrs: &[Attribute], - self_ty: &mut SelfType, - input_ty: &mut InputType, - ) -> Result { - let mut data = ContainerAttributesBuilder { - attrs: Self::default(), - self_ty, - input_ty, - }; + pub fn new(attrs: &[Attribute], io: &mut SchematicIo) -> Result { + let mut this = Self::default(); - for attr in filter_attributes(attrs) { - attr.parse_nested_meta(|meta| { - if meta.path.is_ident(FROM_ATTR) { - data.try_set_input_ty(InputType::Existing(meta.value()?.parse()?)) - } else if meta.path.is_ident(INTO_ATTR) { - data.try_set_self_ty(SelfType::Into(meta.value()?.parse()?)) - } else if meta.path.is_ident(INPUT_ATTR) { - data.parse_input_meta(meta) - } else if meta.path.is_ident(KIND_ATTR) { - data.parse_kind_meta(meta) - } else { - Err(meta.error(format_args!( - "unsupported argument, expected one of: {:?}", - [INPUT_ATTR, FROM_ATTR, INTO_ATTR, KIND_ATTR] - ))) - } + for attr in attrs { + if !attr.path().is_ident(SCHEMATIC_ATTR) { + continue; + } + + parse_nested_meta!(attr, |meta| { + FROM_ATTR => io.try_set_input_ty(InputType::Existing(meta.value()?.parse()?), None), + INTO_ATTR => io.try_set_output_ty(OutputType::Custom(meta.value()?.parse()?), None), + INPUT_ATTR => parse_input_meta(meta, io), + KIND_ATTR => this.parse_kind_meta(meta), })?; } - Ok(data.attrs) - } - - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - None - } - - pub fn input_vis(&self) -> Option<&Visibility> { - self.input_vis.as_ref() + Ok(this) } pub fn kind(&self) -> &SchematicKind { &self.kind } -} - -pub(crate) struct ContainerAttributesBuilder<'a> { - attrs: ContainerAttributes, - self_ty: &'a mut SelfType, - input_ty: &'a mut InputType, -} - -impl<'a> ContainerAttributesBuilder<'a> { - fn parse_input_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { - meta.parse_nested_meta(|meta| { - if meta.path.is_ident(INPUT_VIS) { - match &self.attrs.input_vis { - Some(vis) => Err(meta.error(format_args!( - "visibility already set to {}", - vis.to_token_stream() - ))), - None => { - self.attrs.input_vis = Some(meta.value()?.parse()?); - Ok(()) - } - } - } else if meta.path.is_ident(INPUT_NAME) { - self.try_set_input_ty(InputType::Generated(meta.value()?.parse()?)) - } else { - Err(meta.error(format_args!( - "unsupported argument, expected one of: {:?}", - [INPUT_VIS, INPUT_NAME] - ))) - } - }) - } fn parse_kind_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { - if !matches!(self.attrs.kind, SchematicKind::Undefined) { + if !matches!(self.kind, SchematicKind::Undefined) { return Err(meta.error(format_args!( "schematic kind already configured as #[schematic{:?}]", - &self.attrs.kind + &self.kind ))); } let kind: LitStr = meta.value()?.parse()?; let kind_str = kind.value(); - match kind_str.as_str() { - KIND_BUNDLE => { - self.attrs.kind = SchematicKind::Bundle; + match &kind_str { + _ if kind_str == KIND_BUNDLE => { + self.kind = SchematicKind::Bundle; Ok(()) } - KIND_RESOURCE => { - self.attrs.kind = SchematicKind::Resource; + _ if kind_str == KIND_RESOURCE => { + self.kind = SchematicKind::Resource; Ok(()) } - _ => Err(Error::new( - kind.span(), - format_args!( - "unsupported argument, expected one of: {:?}", - [KIND_BUNDLE, KIND_RESOURCE] - ), - )), - } - } - - fn try_set_input_ty(&mut self, value: InputType) -> Result<(), Error> { - match self.self_ty { - SelfType::Into(ty) => Err(Error::new( - value.span(), - format_args!( - "cannot specify input type when schematic type already set to {}", - ty.to_token_stream() - ), - )), - _ => match &self.input_ty { - InputType::Reflexive => { - *self.input_ty = value; - Ok(()) - } - InputType::Existing(ty) => Err(Error::new( - value.span(), - format_args!("input type already specified as {}", ty.to_token_stream()), - )), - InputType::Generated(ident) => Err(Error::new( - value.span(), - format_args!("input type already specified as {}", ident), - )), - }, - } - } - - fn try_set_self_ty(&mut self, value: SelfType) -> Result<(), Error> { - match self.input_ty { - InputType::Existing(ty) => Err(Error::new( - value.span(), - format_args!( - "cannot specify schematic type when input type already set to {}", - ty.to_token_stream() - ), - )), - _ => match &self.self_ty { - SelfType::Reflexive => { - *self.self_ty = value; - Ok(()) - } - SelfType::Into(ty) => Err(Error::new( - value.span(), - format_args!( - "schematic type already specified as {}", - ty.to_token_stream() - ), - )), - }, + _ => Err(unsupported_arg(&meta, Some(&[KIND_BUNDLE, KIND_RESOURCE]))), } } } diff --git a/bevy_proto_derive/src/schematic/data.rs b/bevy_proto_derive/src/schematic/data.rs deleted file mode 100644 index 1f49e1e..0000000 --- a/bevy_proto_derive/src/schematic/data.rs +++ /dev/null @@ -1,78 +0,0 @@ -use proc_macro2::{Ident, TokenStream}; -use quote::quote; -use syn::{Data, Error, Fields}; - -use crate::schematic::input::InputType; -use crate::schematic::structs::{SchematicStruct, SchematicStructBuilder}; -use crate::schematic::variants::SchematicVariant; - -/// The data of the item deriving `Schematic`. -pub(crate) enum SchematicData { - Struct(SchematicStruct), - Enum(Vec), -} - -impl SchematicData { - pub fn from_data( - data: Data, - ident: &Ident, - input_ty: &mut InputType, - proto_crate: &TokenStream, - bevy_crate: &TokenStream, - ) -> Result { - let mut builder = SchematicStructBuilder { - ident, - input_ty, - proto_crate, - bevy_crate, - }; - - let data = match data { - Data::Struct(data) => SchematicData::Struct(builder.build(data.fields)?), - Data::Enum(data) => SchematicData::Enum( - data.variants - .into_iter() - .map(|variant| { - Ok(match &variant.fields { - Fields::Named(_) => SchematicVariant { - ident: variant.ident, - data: builder.build(variant.fields)?, - }, - Fields::Unnamed(_) => SchematicVariant { - ident: variant.ident, - data: builder.build(variant.fields)?, - }, - Fields::Unit => SchematicVariant { - ident: variant.ident, - data: SchematicStruct::Unit, - }, - }) - }) - .collect::>()?, - ), - Data::Union(data) => { - return Err(Error::new(data.union_token.span, "unions not supported")) - } - }; - - Ok(data) - } - - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - let assertions = match self { - SchematicData::Struct(data) => data.assertions(), - SchematicData::Enum(data) => data.iter().map(|variant| variant.assertions()).collect(), - }?; - - Some(quote! { - mod DataAssertions { - #assertions - } - }) - } -} diff --git a/bevy_proto_derive/src/schematic/derive.rs b/bevy_proto_derive/src/schematic/derive.rs index 03f1cc5..5d2749f 100644 --- a/bevy_proto_derive/src/schematic/derive.rs +++ b/bevy_proto_derive/src/schematic/derive.rs @@ -1,64 +1,50 @@ +use crate::common::data::{DeriveType, SchematicData}; +use crate::common::fields::SchematicFields; +use crate::common::input::{ + generate_from_reflect_conversion, generate_input, generate_input_conversion, InputType, + OutputType, SchematicIo, +}; +use crate::utils::constants::{CONTEXT_IDENT, DEPENDENCIES_IDENT, ID_IDENT, INPUT_IDENT}; +use crate::utils::exports::{DependenciesBuilder, Schematic, SchematicContext, SchematicId}; use proc_macro2::{Ident, TokenStream}; use quote::{quote, ToTokens}; use syn::parse::{Parse, ParseStream}; -use syn::{DeriveInput, Generics, Visibility}; +use syn::{DeriveInput, Error, Generics, Visibility}; use crate::schematic::container_attributes::{ContainerAttributes, SchematicKind}; -use crate::schematic::data::SchematicData; -use crate::schematic::field_attributes::ReplacementType; -use crate::schematic::idents::{CONTEXT_IDENT, DEPENDENCIES_IDENT, INPUT_IDENT}; -use crate::schematic::input::{Input, InputType}; -use crate::schematic::self_type::SelfType; -use crate::schematic::structs::SchematicStruct; -use crate::utils::{get_bevy_crate, get_proto_crate}; pub(crate) struct DeriveSchematic { attrs: ContainerAttributes, - ident: Ident, generics: Generics, data: SchematicData, - self_ty: SelfType, - input_ty: InputType, - assertions: TokenStream, - proto_crate: TokenStream, - bevy_crate: TokenStream, + io: SchematicIo, } impl DeriveSchematic { - pub fn attrs(&self) -> &ContainerAttributes { - &self.attrs - } - - pub fn ident(&self) -> &Ident { - &self.ident - } - - pub fn generics(&self) -> &Generics { + fn generics(&self) -> &Generics { &self.generics } - pub fn proto_crate(&self) -> &TokenStream { - &self.proto_crate + fn io(&self) -> &SchematicIo { + &self.io } - pub fn self_ty(&self) -> &SelfType { - &self.self_ty + fn output_ty(&self) -> &OutputType { + self.io.output_ty() } - pub fn input_ty(&self) -> &InputType { - &self.input_ty + fn input_ty(&self) -> &InputType { + self.io.input_ty() } - pub fn data(&self) -> &SchematicData { + fn data(&self) -> &SchematicData { &self.data } /// Generate the logic for `Schematic::apply`. - pub fn apply_def(&self) -> TokenStream { - let conversion = self - .input_ty - .generate_conversion(self.self_ty(), &self.proto_crate); - let construct_input = self.generate_from_reflect_input(); + fn apply_def(&self) -> TokenStream { + let from_reflect = generate_from_reflect_conversion(); + let conversion = generate_input_conversion(self.io()); let insert = if matches!(self.attrs.kind(), SchematicKind::Resource) { quote!(#CONTEXT_IDENT.world_mut().insert_resource(#INPUT_IDENT);) @@ -72,7 +58,7 @@ impl DeriveSchematic { }; quote! { - let #INPUT_IDENT = #construct_input; + #from_reflect #conversion @@ -82,154 +68,57 @@ impl DeriveSchematic { } /// Generate the logic for `Schematic::remove`. - pub fn remove_def(&self) -> TokenStream { - let self_ty = self.self_ty(); + fn remove_def(&self) -> TokenStream { + let output_ty = self.output_ty(); if matches!(self.attrs.kind(), SchematicKind::Resource) { - quote!(#CONTEXT_IDENT.world_mut().remove_resource::<#self_ty>();) + quote!(#CONTEXT_IDENT.world_mut().remove_resource::<#output_ty>();) } else { quote!( #CONTEXT_IDENT .entity_mut() .unwrap_or_else(|| panic!("schematic `{}` expected entity", std::any::type_name::())) - .remove::<#self_ty>(); + .remove::<#output_ty>(); ) } } /// Generates the logic for `Schematic::preload`. - pub fn preload_def(&self) -> Option { - let proto_crate = &self.proto_crate; - match &self.data { - SchematicData::Struct(SchematicStruct::Unit) => None, + fn preload_def(&self) -> Result { + Ok(match &self.data { + SchematicData::Struct(SchematicFields::Unit) => TokenStream::new(), SchematicData::Struct( - SchematicStruct::Named(fields) | SchematicStruct::Unnamed(fields), - ) => Some( - fields - .iter() - .filter_map(|field| match field.attrs().replacement_ty() { - ReplacementType::Asset(config) if config.is_preload() => { - let ty = field.defined_ty(); - let member = field.member(); - let name_str = field.member().to_token_stream().to_string(); - - Some(if let Some(path) = config.path() { - quote!( - let _: #ty = #DEPENDENCIES_IDENT.add_dependency(#path); - ) - } else { - quote!( - match #INPUT_IDENT.#member { - #proto_crate::proto::ProtoAsset::AssetPath(ref path) => { - let _: #ty = #DEPENDENCIES_IDENT.add_dependency(path.to_owned()); - } - #proto_crate::proto::ProtoAsset::HandleId(handle_id) => { - panic!( - "expected `ProtoAsset::AssetPath` in field `{}` of `{}`, but found `ProtoAsset::HandleId`", - #name_str, - ::core::any::type_name::() - ); - } - } - ) - }) - } - _ => None, - }) - .collect(), - ), + SchematicFields::Named(fields) | SchematicFields::Unnamed(fields), + ) => fields + .iter() + .map(|field| field.generate_preload(None).map(|preload| quote!(#preload))) + .collect::>()?, SchematicData::Enum(variants) => { - let input_ty = self.input_ty(); let arms = variants .iter() - .map(|variant| variant.generate_preload_arm(input_ty)); + .map(|variant| variant.generate_preload_arm(self.io())) + .collect::, Error>>()?; - Some(quote! { + quote! { match #INPUT_IDENT { - #(#arms,)* + #(#arms)* _ => unreachable!(), } - }) + } } - } - } - - /// Generates an expression that clones the `Schematic`'s input argument to - /// a `Box` using its `FromReflect`. - fn generate_from_reflect_input(&self) -> TokenStream { - let bevy_crate = &self.bevy_crate; - quote! { - ::from_reflect( - &*#bevy_crate::reflect::Reflect::clone_value(#INPUT_IDENT) - ).unwrap_or_else(|| { - panic!( - "{} should have a functioning `FromReflect` impl", - std::any::type_name::() - ) - }) - } - } -} - -impl Parse for DeriveSchematic { - fn parse(input: ParseStream) -> syn::Result { - let input = input.parse::()?; - - let mut assertions = TokenStream::new(); - - let mut self_ty = SelfType::default(); - #[cfg(feature = "assertions")] - assertions.extend(self_ty.assertions()); - - let mut input_ty = InputType::default(); - #[cfg(feature = "assertions")] - assertions.extend(input_ty.assertions()); - - let attrs = ContainerAttributes::new(&input.attrs, &mut self_ty, &mut input_ty)?; - #[cfg(feature = "assertions")] - assertions.extend(attrs.assertions()); - - let proto_crate = get_proto_crate(); - let bevy_crate = get_bevy_crate(); - - let data = SchematicData::from_data( - input.data, - &input.ident, - &mut input_ty, - &proto_crate, - &bevy_crate, - )?; - #[cfg(feature = "assertions")] - assertions.extend(data.assertions()); - - Ok(Self { - attrs, - ident: input.ident, - generics: input.generics, - data, - self_ty, - input_ty, - assertions, - proto_crate, - bevy_crate, }) } -} -impl ToTokens for DeriveSchematic { - fn to_tokens(&self, tokens: &mut TokenStream) { - let ident = self.ident(); + fn generate(&self) -> Result { + let ident: &Ident = self.io.ident(); let (impl_generics, ty_generics, where_clause) = self.generics().split_for_impl(); - let proto_crate = self.proto_crate(); - - let attrs = self.attrs(); - - let input = Input::get_input(self); + let input = generate_input(self.io(), self.data(), self.generics(), true)?; let apply_def = self.apply_def(); let remove_def = self.remove_def(); - let preload_def = self.preload_def(); + let preload_def = self.preload_def()?; + let input_vis = self.io.input_vis(); let input_ty = match self.input_ty() { InputType::Generated(ident) => { quote!(#ident #ty_generics) @@ -237,49 +126,61 @@ impl ToTokens for DeriveSchematic { input_ty => input_ty.to_token_stream(), }; - let assertions = if cfg!(feature = "assertions") { - let assertions = &self.assertions; - Some(quote! { - const _: () = { - mod Assertions { - #assertions - } - }; - }) - } else { - None - }; - let output = quote! { #input - impl #impl_generics #proto_crate::schematics::Schematic for #ident #ty_generics #where_clause { + impl #impl_generics #Schematic for #ident #ty_generics #where_clause { type Input = #input_ty; - fn apply(#INPUT_IDENT: &Self::Input, #CONTEXT_IDENT: &mut #proto_crate::schematics::SchematicContext) { + fn apply(#INPUT_IDENT: &Self::Input, #ID_IDENT: #SchematicId, #CONTEXT_IDENT: &mut #SchematicContext) { #apply_def } - fn remove(#INPUT_IDENT: &Self::Input, #CONTEXT_IDENT: &mut #proto_crate::schematics::SchematicContext) { + fn remove(#INPUT_IDENT: &Self::Input, #ID_IDENT: #SchematicId, #CONTEXT_IDENT: &mut #SchematicContext) { #remove_def } - fn preload_dependencies(#INPUT_IDENT: &mut Self::Input, #DEPENDENCIES_IDENT: &mut #proto_crate::deps::DependenciesBuilder) { + fn preload_dependencies(#INPUT_IDENT: &mut Self::Input, #ID_IDENT: #SchematicId, #DEPENDENCIES_IDENT: &mut #DependenciesBuilder) { #preload_def } } - - #assertions }; - if matches!(attrs.input_vis(), None | Some(Visibility::Inherited)) { - tokens.extend(quote! { + Ok(match input_vis { + None | Some(Visibility::Inherited) => quote! { const _: () = { #output }; - }) - } else { - tokens.extend(output); + }, + _ => output, + }) + } +} + +impl Parse for DeriveSchematic { + fn parse(input: ParseStream) -> syn::Result { + let input = input.parse::()?; + + let mut io = SchematicIo::new(&input); + + let attrs = ContainerAttributes::new(&input.attrs, &mut io)?; + + let data = SchematicData::new(input.data, &mut io, DeriveType::Schematic)?; + + Ok(Self { + attrs, + generics: input.generics, + data, + io, + }) + } +} + +impl ToTokens for DeriveSchematic { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self.generate() { + Ok(output) => tokens.extend(output), + Err(err) => err.to_compile_error().to_tokens(tokens), } } } diff --git a/bevy_proto_derive/src/schematic/field_attributes.rs b/bevy_proto_derive/src/schematic/field_attributes.rs deleted file mode 100644 index 83f55c9..0000000 --- a/bevy_proto_derive/src/schematic/field_attributes.rs +++ /dev/null @@ -1,360 +0,0 @@ -use std::fmt::{Debug, Formatter}; - -use proc_macro2::{Ident, Span, TokenStream}; -use quote::ToTokens; -use syn::meta::ParseNestedMeta; -use syn::spanned::Spanned; -use syn::{parse_quote, Attribute, Error, Field, LitStr, Type}; - -use crate::schematic::input::InputType; -use crate::schematic::ATTRIBUTE; - -const ASSET_ATTR: &str = "asset"; -const ASSET_LAZY: &str = "lazy"; -const ASSET_PRELOAD: &str = "preload"; -const ASSET_PATH: &str = "path"; - -const ENTITY_ATTR: &str = "entity"; -const ENTITY_PATH: &str = "path"; - -const FROM_ATTR: &str = "from"; - -/// Specifies how to replace a field in a generated `Schematic::Input` type. -#[derive(Default)] -pub(crate) enum ReplacementType { - /// No replacement needed. - #[default] - None, - /// This field contains a handle to an asset. - Asset(AssetConfig), - /// This field contains a reference to another entity. - Entity(EntityConfig), - From(Type), -} - -impl ReplacementType { - /// Attempts this [`ReplacementType`]. - /// - /// Returns `Ok(())` if the change is valid. - /// Returns `Err(error)` if the change is not valid, such as when trying - /// to convert to a new `ReplacementType` when already designated as a different one. - pub fn try_set(&mut self, value: Self, span: Span) -> Result<(), Error> { - match (&self, value) { - (Self::None, value) => { - *self = value; - Ok(()) - } - (Self::Entity(EntityConfig::Undefined), value @ Self::Entity(_)) => { - *self = value; - Ok(()) - } - (current, _) => Err(Error::new( - span, - format_args!("field already configured as {:?}", current), - )), - } - } - - /// Returns a generated [`Type`] to be used as a field's replacement - /// type for a generated `Schematic::Input`. - /// - /// Returns `None` for [`ReplacementType::None`], denoting that the field does not require - /// a replacement type. - pub fn generate_type(&self, proto_crate: &TokenStream) -> Option { - match self { - ReplacementType::None => None, - ReplacementType::Asset(_) => Some(parse_quote!(#proto_crate::proto::ProtoAsset)), - ReplacementType::Entity(_) => Some(parse_quote!(#proto_crate::tree::EntityAccess)), - ReplacementType::From(ty) => Some(ty.clone()), - } - } -} - -impl Debug for ReplacementType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - ReplacementType::None => Ok(()), - ReplacementType::Asset(config) => write!(f, "#[schematic(asset{:?})]", config), - ReplacementType::Entity(config) => write!(f, "#[schematic(entity{:?})]", config), - ReplacementType::From(ty) => write!(f, "#[schematic(from = {})]", ty.to_token_stream()), - } - } -} - -/// Specifies the configuration for asset-containing fields. -#[derive(Default)] -pub(crate) struct AssetConfig { - path: Option, - load_type: AssetLoadType, -} - -impl AssetConfig { - pub fn path(&self) -> Option<&LitStr> { - self.path.as_ref() - } - - /// Returns true if the asset should be preloaded. - pub fn is_preload(&self) -> bool { - matches!(self.load_type, AssetLoadType::Preload) - } - - pub fn try_set_path(&mut self, path: LitStr, span: Span) -> Result<(), Error> { - if self.path.is_some() { - return Err(self.get_error(span)); - } - - self.path = Some(path); - - Ok(()) - } - - pub fn try_set_load_type(&mut self, load_type: AssetLoadType, span: Span) -> Result<(), Error> { - if !matches!(self.load_type, AssetLoadType::Undefined) { - return Err(self.get_error(span)); - } - - self.load_type = load_type; - - Ok(()) - } - - fn get_error(&self, span: Span) -> Error { - Error::new( - span, - format_args!("field already configured as #[schematic(asset{:?})]", self), - ) - } -} - -impl Debug for AssetConfig { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match (&self.load_type, &self.path) { - (AssetLoadType::Undefined, None) => Ok(()), - (load_type, None) => write!(f, "({:?})", load_type), - (AssetLoadType::Undefined, Some(path)) => { - write!(f, "(path = {:?})", path.value()) - } - (load_type, Some(path)) => { - write!(f, "({:?}, path = {:?})", load_type, path.value()) - } - } - } -} - -#[derive(Default)] -pub(crate) enum AssetLoadType { - #[default] - Undefined, - Preload, - Lazy, -} - -impl Debug for AssetLoadType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Undefined => Ok(()), - Self::Preload => write!(f, "preload"), - Self::Lazy => write!(f, "lazy"), - } - } -} - -#[derive(Default)] -pub(crate) enum EntityConfig { - #[default] - Undefined, - Path(LitStr), -} - -impl Debug for EntityConfig { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - EntityConfig::Undefined => Ok(()), - EntityConfig::Path(path) => write!(f, "(path = {:?})", path.value()), - } - } -} - -#[derive(Default)] -pub(crate) struct FieldAttributes { - reflect_attrs: Vec, - replacement_ty: ReplacementType, -} - -impl FieldAttributes { - /// Create a [`FieldAttributes`] for the given field. - /// - /// # Arguments - /// - /// * `field`: The field to process - /// * `ident`: The [`Ident`] of the `Schematic` - /// * `input_ty`: A mutable reference to the [`InputType`] of the `Schematic` - /// - pub fn new(field: &Field, ident: &Ident, input_ty: &mut InputType) -> Result { - let mut data = FieldAttributesBuilder { - attrs: Self::default(), - ident, - input_ty, - }; - - for attr in &field.attrs { - if attr.path().is_ident("reflect") { - data.attrs.reflect_attrs.push(attr.clone()); - } - - if !attr.path().is_ident(ATTRIBUTE) { - continue; - } - - attr.parse_nested_meta(|meta| { - let ident = meta - .path - .get_ident() - .ok_or_else(|| Error::new(meta.path.span(), "unsupported argument"))? - .to_string(); - - match (ident.as_str(), &data.input_ty) { - (attr @ (ASSET_ATTR | ENTITY_ATTR | FROM_ATTR), InputType::Existing(ty)) => { - Err(meta.error(format_args!( - "cannot use #[schematic({})] when input type is already defined as {}", - attr, - ty.to_token_stream() - ))) - } - (ASSET_ATTR, _) => data.parse_asset_meta(meta), - (ENTITY_ATTR, _) => data.parse_entity_meta(meta), - (FROM_ATTR, _) => data.parse_from_meta(meta), - (_, _) => Err(meta.error(format_args!( - "unsupported argument, expected one of: {:?}", - [ASSET_ATTR, ENTITY_ATTR, FROM_ATTR] - ))), - } - })?; - } - - Ok(data.attrs) - } - - /// The field's [`ReplacementType`]. - pub fn replacement_ty(&self) -> &ReplacementType { - &self.replacement_ty - } - - /// Reflection attributes to pass to the generated input. - pub fn reflect_attrs(&self) -> &[Attribute] { - &self.reflect_attrs - } - - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - None - } -} - -struct FieldAttributesBuilder<'a> { - attrs: FieldAttributes, - ident: &'a Ident, - input_ty: &'a mut InputType, -} - -impl<'a> FieldAttributesBuilder<'a> { - /// Parse a `#[schematic(asset)]` attribute. - /// - /// This takes in the meta starting at `asset`. - fn parse_asset_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { - self.require_input(meta.path.span())?; - - let config = if let ReplacementType::Asset(config) = &mut self.attrs.replacement_ty { - config - } else { - self.attrs.replacement_ty.try_set( - ReplacementType::Asset(AssetConfig::default()), - meta.path.span(), - )?; - let ReplacementType::Asset(config) = &mut self.attrs.replacement_ty else { - unreachable!() - }; - config - }; - - if meta.input.is_empty() { - return Ok(()); - } - - meta.parse_nested_meta(|meta| { - if meta.path.is_ident(ASSET_LAZY) { - config.try_set_load_type(AssetLoadType::Lazy, meta.input.span()) - } else if meta.path.is_ident(ASSET_PRELOAD) { - config.try_set_load_type(AssetLoadType::Preload, meta.input.span()) - } else if meta.path.is_ident(ASSET_PATH) { - config.try_set_path(meta.value()?.parse()?, meta.input.span()) - } else { - Err(meta.error(format_args!( - "unsupported argument, expected one of: {:?}", - [ASSET_LAZY, ASSET_PRELOAD, ASSET_PATH] - ))) - } - }) - } - - /// Parse a `#[schematic(entity)]` attribute. - /// - /// This takes in the meta starting at `entity`. - fn parse_entity_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { - self.attrs.replacement_ty.try_set( - ReplacementType::Entity(EntityConfig::Undefined), - meta.path.span(), - )?; - self.require_input(meta.path.span())?; - - if meta.input.is_empty() { - return Ok(()); - } - - meta.parse_nested_meta(|meta| { - let config = if meta.path.is_ident(ENTITY_PATH) { - EntityConfig::Path(meta.value()?.parse()?) - } else { - return Err(meta.error(format_args!( - "unsupported argument, expected one of: {:?}", - [ENTITY_PATH] - ))); - }; - - self.attrs - .replacement_ty - .try_set(ReplacementType::Entity(config), meta.input.span()) - }) - } - - /// Parse a `#[schematic(from)]` attribute. - /// - /// This takes in the meta starting at `from`. - fn parse_from_meta(&mut self, meta: ParseNestedMeta) -> Result<(), Error> { - self.require_input(meta.path.span())?; - - self.attrs.replacement_ty.try_set( - ReplacementType::From(meta.value()?.parse()?), - meta.input.span(), - ) - } - - /// Method used to require that a `Schematic::Input` type be generated for the attribute - /// (identified by the given [`Span`]). - fn require_input(&mut self, span: Span) -> Result<(), Error> { - match &self.input_ty { - InputType::Reflexive => { - *self.input_ty = InputType::new_generated(self.ident); - Ok(()) - } - InputType::Existing(ty) => Err(Error::new(span, format_args!("attribute requires input type generation, but the input type is already specified as {}", ty.to_token_stream()))), - InputType::Generated(_) => { - Ok(()) - } - } - } -} diff --git a/bevy_proto_derive/src/schematic/fields.rs b/bevy_proto_derive/src/schematic/fields.rs deleted file mode 100644 index f2c5c62..0000000 --- a/bevy_proto_derive/src/schematic/fields.rs +++ /dev/null @@ -1,177 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::{Member, Type}; - -use crate::schematic::field_attributes::{EntityConfig, FieldAttributes, ReplacementType}; -use crate::schematic::idents::{CONTEXT_IDENT, INPUT_IDENT}; - -/// Field data for a `Schematic`. -/// -/// The [`ToTokens`] impl for this type will generate the field definitions for a -/// generated `Schematic::Input` type, and will automatically use the field's -/// [`ReplacementType`], if any. -pub(crate) struct SchematicField { - attrs: FieldAttributes, - member: Member, - ty: Type, - replacement_ty: Option, - bevy_crate: TokenStream, - proto_crate: TokenStream, -} - -impl SchematicField { - pub fn new>( - attrs: FieldAttributes, - member: M, - ty: Type, - proto_crate: &TokenStream, - bevy_crate: &TokenStream, - ) -> Self { - let replacement_ty = attrs.replacement_ty().generate_type(proto_crate); - - Self { - attrs, - member: member.into(), - ty, - replacement_ty, - bevy_crate: bevy_crate.clone(), - proto_crate: proto_crate.clone(), - } - } - - /// The field's `#[schematic]` attributes. - pub fn attrs(&self) -> &FieldAttributes { - &self.attrs - } - - /// The member (ident or index) used to access this field. - pub fn member(&self) -> &Member { - &self.member - } - - /// The type of the field, as defined by the user. - pub fn defined_ty(&self) -> &Type { - &self.ty - } - - /// The type of the field, as determined by its [`ReplacementType`]. - /// - /// If there is no replacement type, returns `None`. - pub fn replacement_ty(&self) -> Option<&Type> { - self.replacement_ty.as_ref() - } - - pub fn bevy_crate(&self) -> &TokenStream { - &self.bevy_crate - } - - pub fn proto_crate(&self) -> &TokenStream { - &self.proto_crate - } - - /// Generate the conversion from the field to its [`ReplacementType`]. - pub fn conversion_def(&self) -> TokenStream { - let proto_crate = &self.proto_crate; - let bevy_crate = &self.bevy_crate; - let member = &self.member; - let ty = &self.ty; - - match self.attrs().replacement_ty() { - ReplacementType::None => quote!(#INPUT_IDENT.#member), - ReplacementType::Asset(config) => { - if let Some(path) = config.path() { - quote!( - #CONTEXT_IDENT - .world() - .resource::<#bevy_crate::asset::AssetServer>() - .load(#path) - ) - } else { - quote!( - match #INPUT_IDENT.#member { - #proto_crate::proto::ProtoAsset::AssetPath(ref path) => { - #CONTEXT_IDENT - .world() - .resource::<#bevy_crate::asset::AssetServer>() - .load(path) - } - #proto_crate::proto::ProtoAsset::HandleId(handle_id) => { - #CONTEXT_IDENT - .world() - .resource::<#bevy_crate::asset::AssetServer>() - .get_handle(handle_id) - } - } - ) - } - } - replacement_ty @ ReplacementType::Entity(EntityConfig::Undefined) => { - let entity_ty = replacement_ty.generate_type(proto_crate).unwrap(); - quote! { - <#ty as #proto_crate::schematics::FromSchematicInput<#entity_ty>>::from_input( - #INPUT_IDENT.#member, - #CONTEXT_IDENT, - ) - } - } - replacement_ty @ ReplacementType::Entity(EntityConfig::Path(path)) => { - let entity_ty = replacement_ty.generate_type(proto_crate).unwrap(); - quote! { - <#ty as #proto_crate::schematics::FromSchematicInput<#entity_ty>>::from_input( - #proto_crate::tree::EntityAccess::from(#path), - #CONTEXT_IDENT, - ) - } - } - ReplacementType::From(replacement_ty) => quote! { - <#ty as #proto_crate::schematics::FromSchematicInput<#replacement_ty>>::from_input( - #INPUT_IDENT.#member, - #CONTEXT_IDENT, - ) - }, - } - } - - /// Returns true if this field should be included in a generated input's type definition. - pub fn should_generate(self: &&Self) -> bool { - match self.attrs().replacement_ty() { - ReplacementType::Asset(config) if config.path().is_some() => false, - ReplacementType::Entity(EntityConfig::Path(_)) => false, - _ => true, - } - } - - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - self.attrs().assertions() - } - - /// Takes a list of fields and returns an iterator over their struct/enum definition stream. - /// - /// This will automatically filter out any field that [should not be generated]. - /// - /// [should not be generated]: Self::should_generate - pub fn iter_definitions(fields: &[Self]) -> impl Iterator + '_ { - fields.iter().filter(Self::should_generate).map(|field| { - let attrs = field.attrs().reflect_attrs(); - quote! { - #(#attrs)* - #field - } - }) - } -} - -impl ToTokens for SchematicField { - fn to_tokens(&self, tokens: &mut TokenStream) { - let ty = self.replacement_ty().unwrap_or(&self.ty); - tokens.extend(match &self.member { - Member::Named(ident) => quote!(#ident: #ty), - Member::Unnamed(_) => quote!(#ty), - }) - } -} diff --git a/bevy_proto_derive/src/schematic/idents.rs b/bevy_proto_derive/src/schematic/idents.rs deleted file mode 100644 index 393f9a5..0000000 --- a/bevy_proto_derive/src/schematic/idents.rs +++ /dev/null @@ -1,27 +0,0 @@ -//! Helper [idents] used in generated macro output. -//! -//! The purpose of these idents is to use their const-likeness so that we -//! can easily make use of them throughout modules without having to pass -//! them around as references (since these should never change). -//! -//! [idents]: Ident - -use proc_macro2::{Ident, Span, TokenStream}; -use quote::ToTokens; - -/// Ident for the `Schematic::Input` argument. -pub(crate) const INPUT_IDENT: ConstIdent = ConstIdent("__input__"); -/// Ident for the `SchematicContext` argument. -pub(crate) const CONTEXT_IDENT: ConstIdent = ConstIdent("__context__"); -/// Ident for the `DependenciesBuilder` argument. -pub(crate) const DEPENDENCIES_IDENT: ConstIdent = ConstIdent("__dependencies__"); - -/// Helper struct used to generate a const-like [`Ident`] with the given name. -#[derive(Copy, Clone, PartialEq)] -pub(crate) struct ConstIdent(&'static str); - -impl ToTokens for ConstIdent { - fn to_tokens(&self, tokens: &mut TokenStream) { - Ident::new(self.0, Span::call_site()).to_tokens(tokens) - } -} diff --git a/bevy_proto_derive/src/schematic/input.rs b/bevy_proto_derive/src/schematic/input.rs deleted file mode 100644 index 2694b92..0000000 --- a/bevy_proto_derive/src/schematic/input.rs +++ /dev/null @@ -1,238 +0,0 @@ -use proc_macro2::{Ident, Span, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use syn::token::Pub; -use syn::{Generics, Token, TypePath, Visibility}; -use to_phantom::ToPhantom; - -use crate::schematic::data::SchematicData; -use crate::schematic::fields::SchematicField; -use crate::schematic::idents::{CONTEXT_IDENT, INPUT_IDENT}; -use crate::schematic::self_type::SelfType; -use crate::schematic::structs::SchematicStruct; -use crate::schematic::DeriveSchematic; -use crate::utils::{get_bevy_crate, get_proto_crate}; - -/// The type of the `Schematic::Input`. -#[derive(Default)] -pub(crate) enum InputType { - /// Corresponds to using `Self`. - #[default] - Reflexive, - /// Specifies an existing type. - /// - /// Most often this will be a user-generated type made to be used in `#[schematic(from = path::to::Type)]`. - Existing(TypePath), - Generated(Ident), -} - -impl InputType { - pub fn new_generated(ident: &Ident) -> Self { - Self::Generated(format_ident!("{}Input", ident)) - } - - /// Generates a conversion statement from the [`InputType`] to the `Schematic`'s type. - /// - /// If no conversion is necessary, returns `None`. - pub fn generate_conversion( - &self, - self_ty: &SelfType, - proto_crate: &TokenStream, - ) -> Option { - match (self, self_ty) { - (InputType::Reflexive, SelfType::Reflexive) => None, - (_, SelfType::Into(self_ty)) => Some(quote! { - let #INPUT_IDENT = >::from_input( - #INPUT_IDENT, #CONTEXT_IDENT - ); - let #INPUT_IDENT = <#self_ty as #proto_crate::schematics::FromSchematicInput>::from_input( - #INPUT_IDENT, #CONTEXT_IDENT - ); - }), - _ => Some(quote! { - let #INPUT_IDENT = >::from_input(#INPUT_IDENT, #CONTEXT_IDENT); - }), - } - } - - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - None - } -} - -impl ToTokens for InputType { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - InputType::Reflexive => Token![Self](Span::call_site()).to_tokens(tokens), - InputType::Existing(path) => path.to_tokens(tokens), - InputType::Generated(ident) => ident.to_tokens(tokens), - } - } -} - -/// A temp struct used to generate a `Schematic::Input` type. -pub(crate) struct Input<'a> { - vis: Visibility, - base_ident: &'a Ident, - input_ident: &'a Ident, - generics: &'a Generics, - data: &'a SchematicData, -} - -impl<'a> Input<'a> { - /// Returns the [`Input`] definition used to generate a `Schematic::Input` type. - /// - /// This will return `None` if no type needs to be generated. - pub fn get_input(derive_data: &'a DeriveSchematic) -> Option> { - let vis = derive_data - .attrs() - .input_vis() - .cloned() - .unwrap_or(Visibility::Public(Pub::default())); - - match derive_data.input_ty() { - InputType::Generated(ident) => Some(Self { - vis, - base_ident: derive_data.ident(), - input_ident: ident, - generics: derive_data.generics(), - data: derive_data.data(), - }), - _ => None, - } - } -} - -impl<'a> ToTokens for Input<'a> { - fn to_tokens(&self, tokens: &mut TokenStream) { - let proto_crate = get_proto_crate(); - let bevy_crate = get_bevy_crate(); - - let vis = &self.vis; - let base_ty = self.base_ident; - let input_ident = self.input_ident; - let (impl_generics, impl_ty_generics, where_clause) = self.generics.split_for_impl(); - - // Ex: Foo - let input_ty = quote!(#input_ident #impl_ty_generics); - // Ex: Foo - let input_ty_def = quote!(#input_ident #impl_generics); - let phantom_ty = if self.generics.params.is_empty() { - None - } else { - Some(self.generics.to_phantom()) - }; - - let make_from_impl = |body: TokenStream| { - quote! { - impl #impl_generics #proto_crate::schematics::FromSchematicInput<#input_ty> for #base_ty #impl_ty_generics #where_clause { - fn from_input( - #INPUT_IDENT: #input_ty, - #CONTEXT_IDENT: &mut #proto_crate::schematics::SchematicContext - ) -> Self { - #body - } - } - } - }; - - match self.data { - SchematicData::Struct(SchematicStruct::Unit) => { - let from_impl = make_from_impl(quote!(Self)); - - tokens.extend(quote! { - #[derive(#bevy_crate::prelude::Reflect)] - #vis struct #input_ty_def #where_clause; - - #from_impl - }) - } - SchematicData::Struct(SchematicStruct::Unnamed(fields)) => { - let conversions = fields.iter().map(SchematicField::conversion_def); - let filtered = SchematicField::iter_definitions(fields); - let from_impl = make_from_impl(quote! { - Self( - #(#conversions),* - ) - }); - - let phantom_ty = phantom_ty.map(|phantom_ty| { - quote! { - #[reflect(ignore)] - #phantom_ty - } - }); - - tokens.extend(quote! { - #[derive(#bevy_crate::prelude::Reflect)] - #vis struct #input_ty_def ( - #(#filtered,)* - #phantom_ty - ) #where_clause; - - #from_impl - }) - } - SchematicData::Struct(SchematicStruct::Named(fields)) => { - let conversions = fields.iter().map(SchematicField::conversion_def); - let members = fields.iter().map(SchematicField::member); - let filtered = SchematicField::iter_definitions(fields); - - let from_impl = make_from_impl(quote! { - Self { - #(#members: #conversions),* - } - }); - - let phantom_ty = phantom_ty.map(|phantom_ty| { - quote! { - #[reflect(ignore)] - __phantom_ty__: #phantom_ty - } - }); - - tokens.extend(quote! { - #[derive(#bevy_crate::prelude::Reflect)] - #vis struct #input_ty_def #where_clause { - #(#filtered,)* - #phantom_ty - } - - #from_impl - }) - } - SchematicData::Enum(variants) => { - let constructors = variants - .iter() - .map(|variant| variant.generate_constructor_arm(input_ident)); - - let from_impl = make_from_impl(quote! { - match #INPUT_IDENT { - #(#constructors,)* - _ => unreachable!(), - } - }); - - let phantom_ty = phantom_ty.map(|phantom_ty| { - quote! { - _Phantom(#[reflect(ignore)] #phantom_ty) - } - }); - - tokens.extend(quote! { - #[derive(#bevy_crate::prelude::Reflect)] - #vis enum #input_ty_def #where_clause { - #(#variants,)* - #phantom_ty - } - - #from_impl - }) - } - } - } -} diff --git a/bevy_proto_derive/src/schematic/mod.rs b/bevy_proto_derive/src/schematic/mod.rs index 1c23bc6..1fe1e1a 100644 --- a/bevy_proto_derive/src/schematic/mod.rs +++ b/bevy_proto_derive/src/schematic/mod.rs @@ -1,17 +1,6 @@ pub(crate) use derive::DeriveSchematic; pub(crate) use external::ExternalSchematic; -const ATTRIBUTE: &str = "schematic"; - mod container_attributes; -mod data; mod derive; mod external; -mod field_attributes; -mod fields; -mod idents; -mod input; -mod self_type; -mod structs; -mod utils; -mod variants; diff --git a/bevy_proto_derive/src/schematic/self_type.rs b/bevy_proto_derive/src/schematic/self_type.rs deleted file mode 100644 index eb84340..0000000 --- a/bevy_proto_derive/src/schematic/self_type.rs +++ /dev/null @@ -1,45 +0,0 @@ -use proc_macro2::TokenStream; -use quote::{quote, ToTokens}; -use syn::TypePath; - -/// The type of the `Schematic`. -#[derive(Default)] -pub(crate) enum SelfType { - /// Corresponds to using `Self`. - #[default] - Reflexive, - /// Specifies that the input type is `Self` but must be converted to the given type. - Into(TypePath), -} - -impl SelfType { - /// Returns the "true" type of the schematic (i.e. `Self`). - pub fn get_true_self(&self) -> TokenStream { - quote!(Self) - } - - /// Returns the "into" type of the schematic. - /// - /// This is the type actually being applied to the entity/world. - pub fn get_into_self(&self) -> TokenStream { - match self { - Self::Reflexive => self.get_true_self(), - Self::Into(ty) => ty.to_token_stream(), - } - } - - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - None - } -} - -impl ToTokens for SelfType { - fn to_tokens(&self, tokens: &mut TokenStream) { - tokens.extend(self.get_into_self()); - } -} diff --git a/bevy_proto_derive/src/schematic/structs.rs b/bevy_proto_derive/src/schematic/structs.rs deleted file mode 100644 index f7bd2c5..0000000 --- a/bevy_proto_derive/src/schematic/structs.rs +++ /dev/null @@ -1,74 +0,0 @@ -use proc_macro2::{Ident, TokenStream}; -use syn::{Error, Fields}; - -use crate::schematic::field_attributes::FieldAttributes; -use crate::schematic::fields::SchematicField; -use crate::schematic::input::InputType; - -pub(crate) enum SchematicStruct { - Unit, - Unnamed(Vec), - Named(Vec), -} - -impl SchematicStruct { - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - match self { - SchematicStruct::Unit => None, - SchematicStruct::Named(fields) | SchematicStruct::Unnamed(fields) => { - fields.iter().map(|field| field.assertions()).collect() - } - } - } -} - -pub(crate) struct SchematicStructBuilder<'a> { - pub ident: &'a Ident, - pub input_ty: &'a mut InputType, - pub proto_crate: &'a TokenStream, - pub bevy_crate: &'a TokenStream, -} - -impl<'a> SchematicStructBuilder<'a> { - pub fn build(&mut self, fields: Fields) -> Result { - Ok(match fields { - Fields::Named(fields) => SchematicStruct::Named( - fields - .named - .into_iter() - .map(|field| { - Ok(SchematicField::new( - FieldAttributes::new(&field, self.ident, self.input_ty)?, - field.ident.unwrap(), - field.ty, - self.proto_crate, - self.bevy_crate, - )) - }) - .collect::>()?, - ), - Fields::Unnamed(fields) => SchematicStruct::Unnamed( - fields - .unnamed - .into_iter() - .enumerate() - .map(|(index, field)| { - Ok(SchematicField::new( - FieldAttributes::new(&field, self.ident, self.input_ty)?, - index, - field.ty, - self.proto_crate, - self.bevy_crate, - )) - }) - .collect::>()?, - ), - Fields::Unit => SchematicStruct::Unit, - }) - } -} diff --git a/bevy_proto_derive/src/schematic/utils.rs b/bevy_proto_derive/src/schematic/utils.rs deleted file mode 100644 index bd1755e..0000000 --- a/bevy_proto_derive/src/schematic/utils.rs +++ /dev/null @@ -1,14 +0,0 @@ -use syn::Attribute; - -use crate::schematic::ATTRIBUTE; - -/// Filters the given attributes down to only the attributes used by [`Schematic`]. -/// -/// [`Schematic`]: crate::Schematic -pub(crate) fn filter_attributes<'a>( - attrs: impl IntoIterator, -) -> impl Iterator { - attrs - .into_iter() - .filter(|attr| attr.path().is_ident(ATTRIBUTE)) -} diff --git a/bevy_proto_derive/src/schematic/variants.rs b/bevy_proto_derive/src/schematic/variants.rs deleted file mode 100644 index 718b0a6..0000000 --- a/bevy_proto_derive/src/schematic/variants.rs +++ /dev/null @@ -1,249 +0,0 @@ -use proc_macro2::{Ident, TokenStream}; -use quote::{format_ident, quote, ToTokens}; -use syn::Member; - -use crate::schematic::field_attributes::{EntityConfig, ReplacementType}; -use crate::schematic::fields::SchematicField; -use crate::schematic::idents::{CONTEXT_IDENT, DEPENDENCIES_IDENT}; -use crate::schematic::input::InputType; -use crate::schematic::structs::SchematicStruct; - -pub(crate) struct SchematicVariant { - pub ident: Ident, - pub data: SchematicStruct, -} - -impl SchematicVariant { - pub fn generate_constructor_arm(&self, input_ty: &Ident) -> TokenStream { - let ident = &self.ident; - match &self.data { - SchematicStruct::Unit => quote! { - #input_ty::#ident => Self::#ident - }, - SchematicStruct::Unnamed(fields) => { - let pat = fields - .iter() - .filter(SchematicField::should_generate) - .map(|field| format_ident!("field_{}", field.member())); - - let field = fields.iter().map(|field| { - let ident = format_ident!("field_{}", field.member()); - let member = Member::Named(ident); - self.convert_field(field, Some(&member)) - }); - - quote! { - #input_ty::#ident( #(#pat,)* .. ) => Self::#ident( #(#field),* ) - } - } - SchematicStruct::Named(fields) => { - let pat = fields - .iter() - .filter(SchematicField::should_generate) - .map(|field| field.member()); - let member = fields.iter().map(|field| field.member()); - let conversions = fields.iter().map(|field| self.convert_field(field, None)); - - quote! { - #input_ty::#ident{ #(#pat,)* .. } => Self::#ident{ #(#member: #conversions),* } - } - } - } - } - - pub fn generate_preload_arm(&self, input_ty: &InputType) -> TokenStream { - let ident = &self.ident; - let ident_str = ident.to_string(); - match &self.data { - SchematicStruct::Unit => quote! { - #input_ty::#ident => {} - }, - SchematicStruct::Unnamed(fields) => { - let (pat, stmt): (Vec<_>, Vec<_>) = fields - .iter() - .filter_map(|field| match field.attrs().replacement_ty() { - ReplacementType::Asset(config) if config.is_preload() => { - let proto_crate = field.proto_crate(); - let ty = field.defined_ty(); - let member = field.member(); - let name = format_ident!("field_{}", member); - let name_str = member.to_token_stream().to_string(); - - Some(if let Some(path) = config.path() { - ( - None, - quote! { - let _: #ty = #DEPENDENCIES_IDENT.add_dependency(#path); - }, - ) - } else { - ( - Some(quote!(#member: #name,)), - quote! { - match #name { - #proto_crate::proto::ProtoAsset::AssetPath(ref path) => { - let _: #ty = #DEPENDENCIES_IDENT.add_dependency(path.to_owned()); - } - #proto_crate::proto::ProtoAsset::HandleId(handle_id) => { - panic!( - "expected `ProtoAsset::AssetPath` in field `{}` of `{}::{}`, but found `ProtoAsset::HandleId`", - #name_str, - ::core::any::type_name::(), - #ident_str - ); - } - } - }, - ) - }) - } - _ => None, - }) - .unzip(); - - quote! { - #input_ty::#ident{ #(#pat)* .. } => { #(#stmt)* } - } - } - SchematicStruct::Named(fields) => { - let (pat, stmt): (Vec<_>, Vec<_>) = fields - .iter() - .filter_map(|field| match field.attrs().replacement_ty() { - ReplacementType::Asset(config) if config.is_preload() => { - let proto_crate = field.proto_crate(); - let ty = field.defined_ty(); - let member = field.member(); - let name_str = member.to_token_stream().to_string(); - - Some(if let Some(path) = config.path() { - ( - None, - quote! { - let _: #ty = #DEPENDENCIES_IDENT.add_dependency(#path); - }, - ) - } else { - ( - Some(quote!(#member,)), - quote! { - match #member { - #proto_crate::proto::ProtoAsset::AssetPath(ref path) => { - let _: #ty = #DEPENDENCIES_IDENT.add_dependency(path.to_owned()); - } - #proto_crate::proto::ProtoAsset::HandleId(handle_id) => { - panic!( - "expected `ProtoAsset::AssetPath` in field `{}` of `{}::{}`, but found `ProtoAsset::HandleId`", - #name_str, - ::core::any::type_name::(), - #ident_str - ); - } - } - }, - ) - }) - } - _ => None, - }) - .unzip(); - - quote! { - #input_ty::#ident{ #(#pat)* .. } => { #(#stmt)* } - } - } - } - } - - fn convert_field(&self, field: &SchematicField, field_name: Option<&Member>) -> TokenStream { - let proto_crate = field.proto_crate(); - let bevy_crate = field.bevy_crate(); - let member = field_name.unwrap_or_else(|| field.member()); - let ty = field.defined_ty(); - - match field.attrs().replacement_ty() { - ReplacementType::None => quote!(#member), - ReplacementType::Asset(config) => { - if let Some(path) = config.path() { - quote!( - #CONTEXT_IDENT - .world() - .resource::<#bevy_crate::asset::AssetServer>() - .load(#path) - ) - } else { - quote!( - match #member { - #proto_crate::proto::ProtoAsset::AssetPath(ref path) => { - #CONTEXT_IDENT - .world() - .resource::<#bevy_crate::asset::AssetServer>() - .load(path) - } - #proto_crate::proto::ProtoAsset::HandleId(handle_id) => { - #CONTEXT_IDENT - .world() - .resource::<#bevy_crate::asset::AssetServer>() - .get_handle(handle_id) - } - } - ) - } - } - ReplacementType::Entity(EntityConfig::Undefined) => quote! { - #CONTEXT_IDENT - .find_entity(&#member) - .unwrap_or_else(|| panic!("entity should exist at path {:?}", &#member)) - }, - ReplacementType::Entity(EntityConfig::Path(path)) => quote! { - #CONTEXT_IDENT - .find_entity(&#proto_crate::tree::EntityAccess::from(#path)) - .unwrap_or_else(|| panic!("entity should exist at path {:?}", #path)) - }, - ReplacementType::From(replacement_ty) => quote! { - <#ty as #proto_crate::schematics::FromSchematicInput<#replacement_ty>>::from_input( - #member, - #CONTEXT_IDENT, - ) - }, - } - } - - /// Compile-time assertions, if any. - /// - /// These are generated within an anonymous context and should either: - /// 1. Enforce invariants at runtime - /// 2. Provide clearer error outputs for users - pub fn assertions(&self) -> Option { - let assertions = self.data.assertions()?; - let mod_ident = format_ident!("{}VariantAssertions", self.ident); - - Some(quote! { - mod #mod_ident { - #assertions - } - }) - } -} - -impl ToTokens for SchematicVariant { - fn to_tokens(&self, tokens: &mut TokenStream) { - let ident = &self.ident; - match &self.data { - SchematicStruct::Unit => ident.to_tokens(tokens), - SchematicStruct::Unnamed(fields) => { - let filtered = SchematicField::iter_definitions(fields); - - tokens.extend(quote! { - #ident( #(#filtered),* ) - }) - } - SchematicStruct::Named(fields) => { - let filtered = SchematicField::iter_definitions(fields); - - tokens.extend(quote! { - #ident{ #(#filtered),* } - }) - } - } - } -} diff --git a/bevy_proto_derive/src/utils.rs b/bevy_proto_derive/src/utils.rs deleted file mode 100644 index 0a9d654..0000000 --- a/bevy_proto_derive/src/utils.rs +++ /dev/null @@ -1,33 +0,0 @@ -use proc_macro2::{Ident, Span, TokenStream}; -use proc_macro_crate::{crate_name, Error, FoundCrate}; -use quote::quote; - -/// Returns a path to either the `bevy_proto_backend` crate or `bevy_proto::backend` module. -pub(crate) fn get_proto_crate() -> TokenStream { - match crate_name("bevy_proto_backend") { - Ok(found_crate) => get_crate_path(found_crate), - Err(Error::CrateNotFound { .. }) => { - let path = get_crate_path( - crate_name("bevy_proto") - .expect("bevy_proto or bevy_proto_backend should be present in `Cargo.toml`"), - ); - quote!(#path::backend) - } - Err(error) => panic!("{}", error), - } -} - -/// Returns a path to the `bevy` crate. -pub(crate) fn get_bevy_crate() -> TokenStream { - get_crate_path(crate_name("bevy").expect("bevy is present in `Cargo.toml`")) -} - -fn get_crate_path(found_crate: FoundCrate) -> TokenStream { - match found_crate { - FoundCrate::Itself => quote!(crate), - FoundCrate::Name(name) => { - let ident = Ident::new(&name, Span::call_site()); - quote!( ::#ident ) - } - } -} diff --git a/bevy_proto_derive/src/utils/attr.rs b/bevy_proto_derive/src/utils/attr.rs new file mode 100644 index 0000000..ea4d8ea --- /dev/null +++ b/bevy_proto_derive/src/utils/attr.rs @@ -0,0 +1,131 @@ +use proc_macro2::Span; +use quote::ToTokens; +use std::fmt::{Debug, Formatter}; +use syn::Error; + +/// Defines the basic information for an attribute argument. +pub(crate) trait AttrArg { + /// The name of the attribute argument. + const NAME: &'static str; + /// The target of the attribute argument. + /// + /// This is used for error messages. + const TARGET: AttrTarget = AttrTarget::Field; +} + +/// Defines common value-handling methods for an attribute argument. +pub(crate) trait AttrArgValue: AttrArg + Debug { + /// The type of the value this argument expects. + type Value: ToTokens; + + fn new(value: Option) -> Self; + + fn get(&self) -> Option<&Self::Value>; + + fn set(&mut self, value: Option); + + /// Tries to set the value of this argument. + /// + /// Returns an error if the argument is already configured. + fn try_set(&mut self, value: Option, span: Span) -> Result<(), Error> { + if self.get().is_some() { + Err(self.already_configured_error(value.as_ref(), span)) + } else { + self.set(value); + Ok(()) + } + } + + /// Generates an error for when this argument is already configured. + #[allow(unused_variables)] + fn already_configured_error(&self, new: Option<&Self::Value>, span: Span) -> Error { + Error::new( + span, + format_args!("{:?} already configured as `{:?}`", Self::TARGET, self), + ) + } + + /// Generates an error for when this argument is used with another argument + /// that it cannot be used with. + fn invariant_error(&self, span: Span) -> Error { + Error::new( + span, + format_args!("cannot use `{}` argument with `{}`", Self::NAME, T::NAME,), + ) + } +} + +/// The target of the attribute argument. +/// +/// This is used for error messages. +#[derive(Default)] +pub(crate) enum AttrTarget { + #[default] + Field, + Asset, + Input, + InputVisibility, +} + +impl Debug for AttrTarget { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AttrTarget::Field => write!(f, "field"), + AttrTarget::Asset => write!(f, "asset"), + AttrTarget::Input => write!(f, "input"), + AttrTarget::InputVisibility => write!(f, "input visibility"), + } + } +} + +/// Helper macro for defining an attribute argument. +/// +/// Generates the implementations for [`AttrArg`], [`AttrArgValue`], and (optionally) [`Debug`]. +macro_rules! define_attribute { + // Generates the implementations for `AttrArg` and `AttrArgValue`, but not `Debug`. + ($name: literal => $ident: ident($ty: ty) for $target: expr, no_debug) => { + #[derive(Default)] + pub(crate) struct $ident(Option<$ty>); + + impl $crate::utils::AttrArg for $ident { + const NAME: &'static str = $name; + const TARGET: $crate::utils::AttrTarget = $target; + } + + impl $crate::utils::AttrArgValue for $ident { + type Value = $ty; + + fn new(value: Option) -> Self { + Self(value) + } + + fn get(&self) -> Option<&Self::Value> { + self.0.as_ref() + } + + fn set(&mut self, value: Option) { + self.0 = value; + } + } + }; + // Generates the implementations for `AttrArg`, `AttrArgValue`, and `Debug`. + ($name: literal => $ident: ident($ty: ty) for $target: expr $(,)?) => { + $crate::utils::define_attribute!($name => $ident($ty) for $target, no_debug); + + impl std::fmt::Debug for $ident { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match $crate::utils::AttrArgValue::get(self) { + None => Ok(()), + Some(value) => write!( + f, + "{} = {}", + ::NAME, + ::quote::ToTokens::to_token_stream(value) + ), + } + } + } + }; +} + +pub(crate) use define_attribute; diff --git a/bevy_proto_derive/src/utils/constants.rs b/bevy_proto_derive/src/utils/constants.rs new file mode 100644 index 0000000..6f32902 --- /dev/null +++ b/bevy_proto_derive/src/utils/constants.rs @@ -0,0 +1,38 @@ +//! Helper constants used in generated macro output. +//! +//! The purpose of these constants is so that we can easily make use +//! of them throughout modules without having to pass them around as +//! references (since these should never change). + +use proc_macro2::{Ident, Span, TokenStream}; +use quote::ToTokens; + +pub(crate) const SCHEMATIC_ATTR: &str = "schematic"; +pub(crate) const ASSET_SCHEMATIC_ATTR: &str = "asset_schematic"; + +pub(crate) const ASSET_ATTR: &str = "asset"; +pub(crate) const ENTITY_ATTR: &str = "entity"; +pub(crate) const INPUT_ATTR: &str = "input"; +pub(crate) const FROM_ATTR: &str = "from"; +pub(crate) const INTO_ATTR: &str = "into"; + +/// Ident for the `Schematic::Input` argument. +pub(crate) const INPUT_IDENT: ConstIdent = ConstIdent("__input__"); +/// Ident for the `SchematicId` argument. +pub(crate) const ID_IDENT: ConstIdent = ConstIdent("__id__"); +/// Ident for the `SchematicContext` and `AssetSchematicContext` argument. +pub(crate) const CONTEXT_IDENT: ConstIdent = ConstIdent("__context__"); +/// Ident for the `DependenciesBuilder` argument. +pub(crate) const DEPENDENCIES_IDENT: ConstIdent = ConstIdent("__context__"); +/// Ident used for temporary variables (to prevent accidental collisions). +pub(crate) const TEMP_IDENT: ConstIdent = ConstIdent("__temp__"); + +/// Helper struct used to generate a const-like [`Ident`] with the given name. +#[derive(Copy, Clone, PartialEq)] +pub(crate) struct ConstIdent(pub &'static str); + +impl ToTokens for ConstIdent { + fn to_tokens(&self, tokens: &mut TokenStream) { + Ident::new(self.0, Span::call_site()).to_tokens(tokens) + } +} diff --git a/bevy_proto_derive/src/utils/crates.rs b/bevy_proto_derive/src/utils/crates.rs new file mode 100644 index 0000000..787f5c7 --- /dev/null +++ b/bevy_proto_derive/src/utils/crates.rs @@ -0,0 +1,43 @@ +use proc_macro2::TokenStream; +use proc_macro_crate::{crate_name, Error, FoundCrate}; +use std::sync::OnceLock; + +static PROTO_CRATE: OnceLock = OnceLock::new(); +static BEVY_CRATE: OnceLock = OnceLock::new(); + +/// Returns a path to either the `bevy_proto_backend` crate or `bevy_proto::backend` module. +pub(crate) fn get_proto_crate() -> TokenStream { + PROTO_CRATE + .get_or_init(|| match crate_name("bevy_proto_backend") { + Ok(found_crate) => get_crate_path(found_crate), + Err(Error::CrateNotFound { .. }) => { + let path = + get_crate_path(crate_name("bevy_proto").expect( + "bevy_proto or bevy_proto_backend should be present in `Cargo.toml`", + )); + format!("{}::backend", path) + } + Err(error) => panic!("{}", error), + }) + .parse() + .expect("`get_proto_crate` should always return a valid `TokenStream`") +} + +/// Returns a path to the `bevy` crate. +pub(crate) fn get_bevy_crate() -> TokenStream { + BEVY_CRATE + .get_or_init(|| { + get_crate_path(crate_name("bevy").expect("bevy is present in `Cargo.toml`")) + }) + .parse() + .expect("`get_bevy_crate` should always return a valid `TokenStream`") +} + +fn get_crate_path(found_crate: FoundCrate) -> String { + match found_crate { + FoundCrate::Itself => String::from("crate"), + FoundCrate::Name(name) => { + format!("::{}", name) + } + } +} diff --git a/bevy_proto_derive/src/utils/exports.rs b/bevy_proto_derive/src/utils/exports.rs new file mode 100644 index 0000000..535b628 --- /dev/null +++ b/bevy_proto_derive/src/utils/exports.rs @@ -0,0 +1,42 @@ +//! Common type paths used in generated macro output. + +use crate::utils::{get_bevy_crate, get_proto_crate}; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +/// Defines a path to a type for the given crate. +macro_rules! create_export { + (bevy :: $($segment: ident ::)* [$name: ident]) => { + create_export!(@export $($segment ::)* [$name] with get_bevy_crate); + }; + (bevy_proto :: $($segment: ident ::)* [$name: ident]) => { + create_export!(@export $($segment ::)* [$name] with get_proto_crate); + }; + (@export $($segment: ident ::)* [$name: ident] with $get_crate: ident) => { + pub(crate) struct $name; + impl ToTokens for $name { + fn to_tokens(&self, tokens: &mut TokenStream) { + let root = $get_crate(); + tokens.extend(quote!(#root :: $($segment ::)* $name)); + } + } + }; +} + +create_export!(bevy_proto::assets::[ProtoAsset]); +create_export!(bevy_proto::assets::[AssetSchematic]); +create_export!(bevy_proto::assets::[PreloadAssetSchematic]); +create_export!(bevy_proto::assets::[InlinableProtoAsset]); +create_export!(bevy_proto::assets::__private::[PreloadProtoAssetInput]); +create_export!(bevy_proto::deps::[DependenciesBuilder]); +create_export!(bevy_proto::schematics::[Schematic]); +create_export!(bevy_proto::schematics::[FromSchematicInput]); +create_export!(bevy_proto::schematics::[FromSchematicPreloadInput]); +create_export!(bevy_proto::schematics::[SchematicId]); +create_export!(bevy_proto::schematics::[SchematicContext]); +create_export!(bevy_proto::tree::[EntityAccess]); + +create_export!(bevy::reflect::[Reflect]); +create_export!(bevy::reflect::[FromReflect]); +create_export!(bevy::assets::[AssetServer]); +create_export!(bevy::utils::[Uuid]); diff --git a/bevy_proto_derive/src/utils/meta.rs b/bevy_proto_derive/src/utils/meta.rs new file mode 100644 index 0000000..4d75786 --- /dev/null +++ b/bevy_proto_derive/src/utils/meta.rs @@ -0,0 +1,69 @@ +use std::fmt::{Arguments, Formatter}; +use syn::meta::ParseNestedMeta; +use syn::{Error, LitBool, Token}; + +/// Parses an attribute as a `bool` value. +/// +/// This will return `true` if the attribute is empty (i.e. `#[foo]`), +/// otherwise it will parse the value as a `bool` (i.e. `#[foo = false]`). +pub(crate) fn parse_bool(meta: &ParseNestedMeta) -> Result { + Ok(!meta.input.peek(Token![=]) || meta.value()?.parse::()?.value()) +} + +/// Helper function for implementing [`Debug`] on attributes. +/// +/// The callback will write out whatever it's given while automatically +/// adding commas between each entry. +/// +/// This does not automatically add opening and closing parentheses. +pub(crate) fn debug_attribute( + f: &mut Formatter, + func: impl FnOnce(&mut dyn FnMut(Arguments) -> std::fmt::Result) -> std::fmt::Result, +) -> std::fmt::Result { + let mut needs_comma = false; + let mut write = |args: Arguments| -> std::fmt::Result { + if needs_comma { + write!(f, ", ")?; + } + + write!(f, "{}", args)?; + needs_comma = true; + + Ok(()) + }; + + func(&mut write) +} + +/// Returns an error for an unsupported attribute argument. +pub(crate) fn unsupported_arg(meta: &ParseNestedMeta, expected: Option<&[&str]>) -> Error { + if let Some(expected) = expected { + meta.error(format_args!( + "unsupported argument, expected one of: {:?}", + expected + )) + } else { + meta.error("unsupported argument") + } +} + +/// Helper macro for quickly parsing nested attributes and returning an error +/// if the attribute is not supported. +macro_rules! parse_nested_meta { + ($attr: ident, |$meta: ident| { $($arg: expr => $expr: expr $(,)?)+ }) => { + $attr.parse_nested_meta(|$meta| { + parse_nested_meta!(|$meta| { $($arg => $expr)+ }) + }) + }; + (|$meta: ident| { $($arg: expr => $expr: expr $(,)?)+ }) => {{ + $( + if $meta.path.is_ident($arg) { + return $expr; + } + )+ + + Err($crate::utils::unsupported_arg(&$meta, Some(&[ $($arg),+ ]))) + }}; +} + +pub(crate) use parse_nested_meta; diff --git a/bevy_proto_derive/src/utils/mod.rs b/bevy_proto_derive/src/utils/mod.rs new file mode 100644 index 0000000..3dff90b --- /dev/null +++ b/bevy_proto_derive/src/utils/mod.rs @@ -0,0 +1,11 @@ +pub(crate) use attr::*; +pub(crate) use crates::*; +pub(crate) use meta::*; +pub(crate) use next_id::*; + +mod attr; +pub(crate) mod constants; +mod crates; +pub(crate) mod exports; +mod meta; +mod next_id; diff --git a/bevy_proto_derive/src/utils/next_id.rs b/bevy_proto_derive/src/utils/next_id.rs new file mode 100644 index 0000000..70a2b08 --- /dev/null +++ b/bevy_proto_derive/src/utils/next_id.rs @@ -0,0 +1,24 @@ +use crate::utils::constants::ID_IDENT; +use crate::utils::exports::Uuid as BevyUuid; +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use uuid::Uuid; + +/// Generate a compile-time-constant ID for use in `SchematicId::next` calls. +pub(crate) struct NextId; + +impl ToTokens for NextId { + fn to_tokens(&self, tokens: &mut TokenStream) { + let uuid = Uuid::new_v4().as_u128(); + tokens.extend(quote!(#ID_IDENT.next(#uuid))); + } +} + +/// Generate a runtime-random ID for use in `SchematicId::next` calls. +pub(crate) struct RandomId; + +impl ToTokens for RandomId { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(quote!(#ID_IDENT.next(#BevyUuid::new_v4()))); + } +} diff --git a/examples/README.md b/examples/README.md index 6ba6aa6..89fd9ef 100644 --- a/examples/README.md +++ b/examples/README.md @@ -14,9 +14,10 @@ The recommended order is: 5. [derive_schematic.rs](./derive_schematic.rs) - A deeper look at the derive macro 6. [custom_schematic.rs](./custom_schematic.rs) - Creating a custom `Schematic`implementation 7. [hierarchy.rs](./hierarchy.rs) - Defining complex prototype hierarchies with children -8. [cycles.rs](./cycles.rs) - How prototype cycles are handled -9. [custom_loader.rs](./custom_loader.rs) - How to create a custom loader -10. [custom_config.rs](./custom_config.rs) - How to create a custom config +8. [asset_schematic.rs](./asset_schematic.rs) - Creating an `AssetSchematic` for inlinable assets +9. [cycles.rs](./cycles.rs) - How prototype cycles are handled +10. [custom_loader.rs](./custom_loader.rs) - How to create a custom loader +11. [custom_config.rs](./custom_config.rs) - How to create a custom config ### Bevy Examples diff --git a/examples/asset_schematic.rs b/examples/asset_schematic.rs new file mode 100644 index 0000000..d995315 --- /dev/null +++ b/examples/asset_schematic.rs @@ -0,0 +1,128 @@ +//! This example demonstrates how to use asset schematics. +//! +//! Asset schematics work similar to schematics, +//! but allow an asset to be defined directly in a prototype file. + +use bevy::prelude::*; +use bevy::reflect::TypeUuid; +use bevy_proto::prelude::*; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, ProtoPlugin::new())) + .register_type::() + // =============== // + // Asset schematics are slightly more complex than standard schematics, + // so we need to make sure we register them properly. + // We could do this manually, but it's easier to use the extension trait + // provided by this crate: + .register_asset_schematic::() + // =============== // + .add_systems(Startup, (setup, load)) + .add_systems( + Update, + ( + spawn.run_if(prototype_ready("Level1").and_then(run_once())), + inspect.before(on_level_load), + on_level_load, + ), + ) + .run(); +} + +/// Normally assets are defined in their own dedicated files. +/// We can then reference these files by path within our prototype files. +/// +/// Sometimes, however, it makes more sense to define an asset directly in +/// the prototype uses it. +/// This can be to avoid having to create a separate file for a single asset +/// or simply to facilitate faster prototyping. +/// +/// To do this, we can derive `AssetSchematic` on the asset type. +#[derive(AssetSchematic, Reflect, TypeUuid)] +#[uuid = "c7f097c0-5ed7-4261-88b5-01f3045c031f"] +struct Level { + name: String, + /// An asset schematic can also reference other assets. + #[asset_schematic(asset)] + player: Handle, +} + +/// We can then use these asset schematics in our standard schematics. +#[derive(Schematic, Component, Reflect)] +#[reflect(Schematic)] +struct CurrentLevel { + /// By default, the `asset` attribute will convert the handle into a + /// `ProtoAsset`, which only allows references by path. + /// + /// But we want to make use of our new asset schematic so we can define + /// the asset inline! + /// + /// To do this, we can add the `inline` argument. + /// This will convert the handle into a `InlinableProtoAsset`, + /// which will allow us to decide between using an asset path or defining + /// the asset inline. + #[schematic(asset(inline))] + level: Handle, +} + +#[derive(Component)] +struct Player; + +#[derive(Component)] +struct LevelName; + +fn load(mut prototypes: PrototypesMut) { + prototypes.load("examples/asset_schematic/Level1.prototype.ron"); +} + +fn setup(mut commands: Commands) { + commands.spawn(Camera2dBundle::default()); + commands.spawn(( + LevelName, + Text2dBundle { + transform: Transform::from_xyz(0.0, 200.0, 0.0), + ..default() + }, + )); + commands.spawn((Player, SpriteBundle::default())); +} + +fn spawn(mut commands: ProtoCommands) { + commands.spawn("Level1"); +} + +fn on_level_load( + mut player_query: Query<&mut Handle, With>, + mut level_name_query: Query<&mut Text, With>, + level_query: Query<&CurrentLevel, Added>, + levels: Res>, + asset_server: Res, +) { + let Ok(current_level) = level_query.get_single() else { + return; + }; + + let level = levels.get(¤t_level.level).unwrap(); + println!("Loaded level: ==[{}]==", level.name); + + let mut level_name = level_name_query.single_mut(); + *level_name = Text::from_section( + &level.name, + TextStyle { + font: asset_server.load("fonts/JetBrainsMono-Regular.ttf"), + font_size: 64.0, + color: Color::WHITE, + }, + ); + + let mut player = player_query.single_mut(); + *player = level.player.clone(); +} + +// This relies on the `auto_name` feature to be useful +fn inspect(query: Query>) { + for name in &query { + println!("Spawned {:?}", name); + } +} diff --git a/examples/bevy/asset_loading.rs b/examples/bevy/asset_loading.rs new file mode 100644 index 0000000..3731afe --- /dev/null +++ b/examples/bevy/asset_loading.rs @@ -0,0 +1,58 @@ +//! This example is a copy of Bevy's [`asset_loading`] example, but powered by the `bevy_proto` plugin. +//! +//! The most notable change is the use of _inlined assets_. +//! Rather than having to define our simple cube and sphere shapes in separate asset files, +//! we can define them directly in the prototype. +//! +//! Prototypes aren't a perfect replacement for Rust code— +//! especially if there's more advanced logic involved— +//! but they can be a great tool for rapid prototyping. +//! +//! [`asset_loading`]: https://github.com/bevyengine/bevy/blob/v0.11.2/examples/asset/asset_loading.rs + +use bevy::prelude::*; +use bevy_proto::prelude::*; + +fn main() { + App::new() + .add_plugins((DefaultPlugins, ProtoPlugin::default())) + .add_systems(Startup, load) + .add_systems( + Update, + ( + spawn_camera.run_if(prototype_ready("Camera").and_then(run_once())), + spawn_light.run_if(prototype_ready("Light").and_then(run_once())), + spawn_models + .run_if(prototypes_ready(["Sphere", "Cube", "Monkey"]).and_then(run_once())), + inspect, + ), + ) + .run(); +} + +fn load(mut prototypes: PrototypesMut) { + prototypes + .load_folder("examples/bevy/asset_loading") + .unwrap(); +} + +fn spawn_camera(mut commands: ProtoCommands) { + commands.spawn("Camera"); +} + +fn spawn_light(mut commands: ProtoCommands) { + commands.spawn("Light"); +} + +fn spawn_models(mut commands: ProtoCommands) { + commands.spawn("Sphere"); + commands.spawn("Cube"); + commands.spawn("Monkey"); +} + +// This relies on the `auto_name` feature to be useful +fn inspect(query: Query>) { + for name in &query { + println!("Spawned {:?}", name); + } +} diff --git a/examples/bevy/sprite_sheet.rs b/examples/bevy/sprite_sheet.rs new file mode 100644 index 0000000..ac022f1 --- /dev/null +++ b/examples/bevy/sprite_sheet.rs @@ -0,0 +1,83 @@ +//! This example is a copy of Bevy's [`sprite_sheet`] example, but powered by the `bevy_proto` plugin. +//! +//! The most notable change is the use of _inlined assets_. +//! Rather than having to define our sprite sheet's [`TextureAtlas`] programmatically, +//! we can define it directly in the prototype. +//! +//! Prototypes aren't a perfect replacement for Rust code— +//! especially if there's more advanced logic involved— +//! but they can be a great tool for rapid prototyping. +//! +//! [`sprite_sheet`]: https://github.com/bevyengine/bevy/blob/v0.11.2/examples/2d/sprite_sheet.rs + +use bevy::prelude::*; +use bevy_proto::prelude::*; + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) // prevents blurry sprites + .add_plugins(ProtoPlugin::new()) + .register_type::() + .register_type::() + .register_type::() // Needs to be registered for `Timer` to be deserializable + .add_systems(Startup, (setup, load)) + .add_systems( + Update, + ( + spawn.run_if(prototype_ready("Player").and_then(run_once())), + animate_sprite, + inspect, + ), + ) + .run(); +} + +#[derive(Component, Reflect, Schematic)] +#[reflect(Schematic)] +struct AnimationIndices { + first: usize, + last: usize, +} + +#[derive(Component, Deref, DerefMut, Reflect, Schematic)] +#[reflect(Schematic)] +struct AnimationTimer(Timer); + +fn animate_sprite( + time: Res