Merge branch 'master' into feature/stride-sdk

This commit is contained in:
Nicolas Musset
2026-03-28 17:27:22 +01:00
162 changed files with 6484 additions and 2019 deletions
+8
View File
@@ -20,5 +20,13 @@ Stride is a game engine project that requires careful code reviews to maintain q
- If you find a bug or performance issue, suggest a concrete fix in the PR.
- For large PRs (20+ C#/*.cs files), do not attempt a full review, only highlight critical or blocking issues.
- Always consider the context and established patterns in the Stride codebase before making suggestions.
- **Always check for missing, incomplete, or incorrect XML documentation comments** on public types, methods, properties, and fields.
- **Provide XML comment suggestions as individual, separate items** so they can be reviewed and approved independently.
- **Do not rewrite existing XML comments unless they contain errors** such as incorrect information, poor grammar, typos, or lack clarity. Preserve well-written documentation.
- When suggesting XML comments, ensure they are:
- Accurate and describe the actual functionality
- Consistent with existing documentation style in the codebase
- Include `<summary>`, `<param>`, `<returns>`, `<exception>` and `<remarks>` tags where appropriate
- Clear and helpful for API consumers
The goal is to minimize noise and maximize helpful, actionable feedback.
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c76d63dbc0c7c64e52f8ac2c5f8d15645956737befdcccac5ca09c5b86240ed3
size 150528
oid sha256:63c65b2d24cd6ae487ea6c9d957343dba83c7992a0a2831d20f0f4da5b78dadc
size 147456
@@ -1 +1 @@
C76D63DBC0C7C64E52F8AC2C5F8D15645956737BEFDCCCAC5CA09C5B86240ED3
63C65B2D24CD6AE487EA6C9D957343DBA83C7992A0A2831D20F0F4DA5B78DADC
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0d24c0c9a848e934363ba72dd325057f3f0f89737b7e0403896abaca860e1981
size 411136
oid sha256:c21b7719e470059842e4525453a248a3dcf033f75c4cc61a50382684c478a33f
size 407040
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:dd9b63a4a434e57ede5727114221868fe77d252fff4c14c3342ba4e74ad3905d
size 158208
oid sha256:6c5bc9da7e5a867c953c362c3419ad3c9554df115baa61baec10ed2c3856b752
size 155136
@@ -1 +1 @@
DD9B63A4A434E57EDE5727114221868FE77D252FFF4C14C3342BA4E74AD3905D
6C5BC9DA7E5A867C953C362C3419AD3C9554DF115BAA61BAEC10ED2C3856B752
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bee19bb3a32f1397b17d0e587ec0c65d88cd9dc5ba09253220df43802a93e806
oid sha256:27df1b2f1b7d6beb16f173e0039a4d0810171d87a4f160c7d3756912dbb4c290
size 550400
+58 -1
View File
@@ -1,4 +1,4 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.13.35919.96
MinimumVisualStudioVersion = 14.0.23107.0
@@ -164,6 +164,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepuSample.Game", "Physics\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepuSample.Windows", "Physics\BepuSample\BepuSample.Windows\BepuSample.Windows.csproj", "{0ED8F265-28F1-A98A-189F-4731E49FAB30}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Platformer2D.Windows", "Templates\Platformer2D\Platformer2D\Platformer2D.Windows\Platformer2D.Windows.csproj", "{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platformer2D", "Platformer2D", "{E67FC5AC-4BC9-41A5-9C9B-B979A734C175}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Platformer2D.Game", "Templates\Platformer2D\Platformer2D\Platformer2D.Game\Platformer2D.csproj", "{6681EADC-8842-414C-88C8-A83BB7EDBFEB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
AppStore|Any CPU = AppStore|Any CPU
@@ -1464,6 +1470,54 @@ Global
{0ED8F265-28F1-A98A-189F-4731E49FAB30}.Testing|Mixed Platforms.Build.0 = Debug|Any CPU
{0ED8F265-28F1-A98A-189F-4731E49FAB30}.Testing|Windows.ActiveCfg = Debug|Any CPU
{0ED8F265-28F1-A98A-189F-4731E49FAB30}.Testing|Windows.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.AppStore|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.AppStore|Mixed Platforms.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.AppStore|Windows.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.AppStore|Windows.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Debug|Windows.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Debug|Windows.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Release|Any CPU.Build.0 = Release|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Release|Windows.ActiveCfg = Release|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Release|Windows.Build.0 = Release|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Testing|Any CPU.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Testing|Any CPU.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Testing|Mixed Platforms.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Testing|Mixed Platforms.Build.0 = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Testing|Windows.ActiveCfg = Debug|Any CPU
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71}.Testing|Windows.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.AppStore|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.AppStore|Mixed Platforms.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.AppStore|Windows.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.AppStore|Windows.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Debug|Windows.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Debug|Windows.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Release|Any CPU.Build.0 = Release|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Release|Windows.ActiveCfg = Release|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Release|Windows.Build.0 = Release|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Testing|Any CPU.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Testing|Any CPU.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Testing|Mixed Platforms.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Testing|Mixed Platforms.Build.0 = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Testing|Windows.ActiveCfg = Debug|Any CPU
{6681EADC-8842-414C-88C8-A83BB7EDBFEB}.Testing|Windows.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1541,6 +1595,9 @@ Global
{02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {ED0924FF-2147-4C47-9DE6-37A549629A9D}
{1A49E176-B829-4F72-80B2-9BED777872B7} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{0ED8F265-28F1-A98A-189F-4731E49FAB30} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
{E67FC5AC-4BC9-41A5-9C9B-B979A734C175} = {1167BFE9-9460-4E2D-9FDB-A084B8961385}
{2CEDF0C2-9927-46DF-A5F2-BEF58BDE3B71} = {E67FC5AC-4BC9-41A5-9C9B-B979A734C175}
{6681EADC-8842-414C-88C8-A83BB7EDBFEB} = {E67FC5AC-4BC9-41A5-9C9B-B979A734C175}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6F8B04D3-3886-4523-9B40-74FAA1948673}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:db0bf70a7efcdf8abcd62315ca7ad510607ca526ec82e97bab3f2b8c38477fc8
size 5870
@@ -0,0 +1,11 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 16
VisualStudioVersion = 16.0.0.0
MinimumVisualStudioVersion = 16.0.0.0
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Platformer2D.Windows", "Platformer2D\Platformer2D.Windows\Platformer2D.Windows.csproj", "{2304F791-B508-4281-94E8-37B0F9CF9839}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Platformer2D.Game", "Platformer2D\Platformer2D.Game\Platformer2D.csproj", "{5836D368-DBA7-4DC6-BBD0-040AF9C52765}"
EndProject
Global
EndGlobal
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e44554ec626707e71e2a08a5f0d0a4f28ad2667f4d34289aaa301a9e4facee98
size 21982
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36484a21efdb6dc1a75bf8e516ad63780497232a3c23ca4424f17c90fc483d67
size 7256
@@ -0,0 +1,8 @@
!Sound
Id: ae74b3ba-e6ab-4b7d-aefa-e5c13aeee712
SerializedVersion: {Stride: 2.0.0.0}
Tags: []
Source: !file ../../Resources/coin_get.wav
CompressionRatio: 15
StreamFromDisk: false
Spatialized: false
@@ -0,0 +1,46 @@
!PrefabAsset
Id: e84eac4c-033e-490b-bf5b-3b0466b56bac
SerializedVersion: {Stride: 3.1.0.1}
Tags: []
Hierarchy:
RootParts:
- ref!! 73a4d210-3371-45d9-ae64-8309c29cd682
Parts:
- Folder: Coins
Entity:
Id: 73a4d210-3371-45d9-ae64-8309c29cd682
Name: Coin
Components:
bd6c862663f431c20e4e71d1008ede5e: !TransformComponent
Id: 4c14e52c-7a5c-4a9c-a660-a49c5b25fef7
Position: {X: 6.0, Y: -3.0, Z: 0.0}
Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
Scale: {X: 1.0, Y: 1.0, Z: 1.0}
Children: {}
d5890e1a6f58646b097a8eb852dce712: !SpriteComponent
Id: 4ec5ec49-f029-41ca-9402-c6802b9bca82
SpriteProvider: !SpriteFromSheet
Sheet: c9c540bb-2671-42de-bf06-b935954cd604:Sprites/coin_sheet
Color: {R: 1.0, G: 1.0, B: 1.0, A: 1.0}
Sampler: PointClamp
227a9894ca38cdbfaba3ceaabddf708f: !Platformer2D.Gameplay.CoinRotation,Platformer2D
Id: efe1a55c-8b48-4e39-891e-e9ee9d93fd06
Sprite: ref!! 4ec5ec49-f029-41ca-9402-c6802b9bca82
00333083b1a4a66664cf165b07be04fd: !Platformer2D.Gameplay.CollectCoin,Platformer2D
Id: 710fb168-8788-47e4-a553-fa3cc1a6db0a
StaticColliderComponent: ref!! 4e5cc1af-9342-4dc7-8d7a-cd17e59dad7a
SoundEffect: ae74b3ba-e6ab-4b7d-aefa-e5c13aeee712:Audio/coin_get
1860df22024defbf44e056d1d0e1464d: !StaticColliderComponent
Id: 4e5cc1af-9342-4dc7-8d7a-cd17e59dad7a
CanSleep: false
Restitution: 0.0
Friction: 0.5
RollingFriction: 0.0
CcdMotionThreshold: 0.0
CcdSweptSphereRadius: 0.0
IsTrigger: true
AlwaysUpdateNaviMeshCache: false
ColliderShapes:
f187d37aff16130ec23d3e708f3632c8: !SphereColliderShapeDesc
Is2D: true
LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0}
@@ -0,0 +1,37 @@
!GameSettingsAsset
Id: 9f0046b5-44b6-4761-871f-4a97d011015a
SerializedVersion: {Stride: 3.1.0.1}
Tags: []
DefaultScene: b97c1868-2681-4e4d-b8b9-a19960649142:MainScene
GraphicsCompositor: 26e6e1f6-f549-49f2-a395-ca58f7e17b89:GraphicsCompositor
Defaults:
- !Stride.Audio.AudioEngineSettings,Stride.Audio
HrtfSupport: false
- !Stride.Assets.EditorSettings,Stride.Assets
RenderingMode: LDR
- !Stride.Graphics.RenderingSettings,Stride.Graphics
DefaultBackBufferWidth: 1280
DefaultBackBufferHeight: 720
AdaptBackBufferToScreen: false
DefaultGraphicsProfile: Level_10_0
ColorSpace: Linear
DisplayOrientation: LandscapeRight
- !Stride.Streaming.StreamingSettings,Stride.Rendering
ManagerUpdatesInterval: 0:00:00:00.0330000
ResourceLiveTimeout: 0:00:00:08.0000000
- !Stride.Assets.Textures.TextureSettings,Stride.Assets
TextureQuality: Fast
Overrides: []
PlatformFilters:
- PowerVR SGX 54[0-9]
- Adreno \(TM\) 2[0-9][0-9]
- Adreno (TM) 320
- Adreno (TM) 330
- Adreno \(TM\) 4[0-9][0-9]
- NVIDIA Tegra
- Intel(R) HD Graphics
- ^Mali\-4
- ^Mali\-T6
- ^Mali\-T7
SplashScreenTexture: d26edb11-10bd-403c-b3c2-9c7fcccf25e5:StrideDefaultSplashScreen
SplashScreenColor: {R: 0, G: 0, B: 0, A: 255}
@@ -0,0 +1,222 @@
!GraphicsCompositorAsset
Id: 26e6e1f6-f549-49f2-a395-ca58f7e17b89
SerializedVersion: {Stride: 3.1.0.1}
Tags: []
Archetype: 823a81bf-bac0-4552-9267-aeed499c40df:DefaultGraphicsCompositorLevel10
Cameras:
de2e75c3b2b23e54162686363f3f138e:
Id: 291722d5-fb04-46ed-b58d-7f4455266d8e
Name: Main
RenderStages:
47116750c1a5d449b4ad3625f71439b3:
Id: 8d4a292b-24b3-4633-8906-dfec11b7c9b8
Name: Opaque
EffectSlotName: Main
SortMode: !SortModeStateChange {}
9105a30fee026d4893472b6aee83d035:
Id: 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
Name: Transparent
EffectSlotName: Main
SortMode: !BackToFrontSortMode {}
554e52c061404d4684dd7c4c70f70e0e:
Id: f0962b6c-387c-454e-9759-721acf05bb80
Name: ShadowMapCaster
EffectSlotName: ShadowMapCaster
SortMode: !FrontToBackSortMode {}
5a50638f5c514dc490c8c4f57cc88b57:
Id: 9d878307-f1a6-4ff9-bc10-2729c6e2cf40
Name: ShadowMapCasterParaboloid
EffectSlotName: ShadowMapCasterParaboloid
SortMode: !FrontToBackSortMode {}
bc1a77d2ab254a6e920f86cff65cd75e:
Id: 65947d6a-5d89-456c-8774-830c2a2da459
Name: ShadowMapCasterCubeMap
EffectSlotName: ShadowMapCasterCubeMap
SortMode: !FrontToBackSortMode {}
33d9d311a1a65601da9ef56775477f95:
Id: a1e7e6ad-7626-4eb6-a125-13275a9531c2
Name: GBuffer
EffectSlotName: GBuffer
SortMode: !FrontToBackSortMode {}
RenderFeatures:
d8fb80b0e7995140a46bca8dc36ee8a2: !Stride.Rendering.MeshRenderFeature,Stride.Rendering
RenderStageSelectors:
44cf4a95ef82544e9ce3c6507d5569a9: !Stride.Rendering.MeshTransparentRenderStageSelector,Stride.Rendering
OpaqueRenderStage: ref!! 8d4a292b-24b3-4633-8906-dfec11b7c9b8
TransparentRenderStage: ref!! 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
EffectName: StrideForwardShadingEffect
6f7224048750e7260ea87c444f74b32c: !Stride.Rendering.Shadows.ShadowMapRenderStageSelector,Stride.Rendering
ShadowMapRenderStage: ref!! f0962b6c-387c-454e-9759-721acf05bb80
EffectName: StrideForwardShadingEffect.ShadowMapCaster
b60663d7cb46417a94341a39c3bc1a12: !Stride.Rendering.Shadows.ShadowMapRenderStageSelector,Stride.Rendering
ShadowMapRenderStage: ref!! 9d878307-f1a6-4ff9-bc10-2729c6e2cf40
EffectName: StrideForwardShadingEffect.ShadowMapCasterParaboloid
f5533b1249b942df8a8aba311cd79532: !Stride.Rendering.Shadows.ShadowMapRenderStageSelector,Stride.Rendering
ShadowMapRenderStage: ref!! 65947d6a-5d89-456c-8774-830c2a2da459
EffectName: StrideForwardShadingEffect.ShadowMapCasterCubeMap
106341b76db9fcda6a033dad16aa708b: !Stride.Rendering.MeshTransparentRenderStageSelector,Stride.Rendering
OpaqueRenderStage: ref!! a1e7e6ad-7626-4eb6-a125-13275a9531c2
EffectName: StrideForwardShadingEffect.ShadowMapCaster
PipelineProcessors:
d70f5aee0616e4ab25081ceaf643290c: !Stride.Rendering.MeshPipelineProcessor,Stride.Rendering
TransparentRenderStage: ref!! 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
26c899b17f88c21ab13bf60a7220ccd1: !Stride.Rendering.ShadowMeshPipelineProcessor,Stride.Rendering
ShadowMapRenderStage: ref!! f0962b6c-387c-454e-9759-721acf05bb80
ff51170a7d1a4761b73ef6a5c9f0cba2: !Stride.Rendering.ShadowMeshPipelineProcessor,Stride.Rendering
ShadowMapRenderStage: ref!! 9d878307-f1a6-4ff9-bc10-2729c6e2cf40
DepthClipping: true
ae4336b0a9514e8488e8e0ccbcef25f4: !Stride.Rendering.ShadowMeshPipelineProcessor,Stride.Rendering
ShadowMapRenderStage: ref!! 65947d6a-5d89-456c-8774-830c2a2da459
DepthClipping: true
RenderFeatures:
86b959cbdf51a1438d4973177c77c627: !Stride.Rendering.TransformRenderFeature,Stride.Rendering {}
8e0351fee9883922648a11016224b195: !Stride.Rendering.SkinningRenderFeature,Stride.Rendering {}
f5a2017030ba4b28784e804807ce7628: !Stride.Rendering.Materials.MaterialRenderFeature,Stride.Rendering {}
83fea7526ebe4893a5bad953d0502bfd: !Stride.Rendering.Shadows.ShadowCasterRenderFeature,Stride.Rendering {}
65743b4380f4cc43b2b4bdc23cd0c07c: !Stride.Rendering.Lights.ForwardLightingRenderFeature,Stride.Rendering
LightRenderers:
7ac2775468f53c4399b2f3f6357c85c9: !Stride.Rendering.Lights.LightAmbientRenderer,Stride.Rendering {}
7b68f9cd17404a4ba9e5f7df72e3b48d: !Stride.Rendering.Lights.LightDirectionalGroupRenderer,Stride.Rendering {}
411fdcfb9fc388449a0443173dfa3f27: !Stride.Rendering.Lights.LightSkyboxRenderer,Stride.Rendering {}
facdcd5b543cf1c6bdf2138aab6cc473: !Stride.Rendering.Lights.LightClusteredPointSpotGroupRenderer,Stride.Rendering {}
79582329a9cf466e960f8920f579de9b: !Stride.Rendering.Lights.LightPointGroupRenderer,Stride.Rendering {}
cf0c6bd4198b4cc4aaaab5b54870bdfd: !Stride.Rendering.Lights.LightSpotGroupRenderer,Stride.Rendering {}
451af18f3f5c4187cf3fe5f33feb46b1: !Stride.Rendering.LightProbes.LightProbeRenderer,Stride.Rendering {}
ShadowMapRenderer: !Stride.Rendering.Shadows.ShadowMapRenderer,Stride.Rendering
Renderers:
7c3d3d4c86834c3551bacde2527b3836: !Stride.Rendering.Shadows.LightDirectionalShadowMapRenderer,Stride.Rendering
ShadowCasterRenderStage: ref!! f0962b6c-387c-454e-9759-721acf05bb80
1c204b09435636256a3fcfd6f9ddb347: !Stride.Rendering.Shadows.LightSpotShadowMapRenderer,Stride.Rendering
ShadowCasterRenderStage: ref!! f0962b6c-387c-454e-9759-721acf05bb80
7c8c69ce27034b4c8bbcab0bcdfe954b: !Stride.Rendering.Shadows.LightPointShadowMapRendererParaboloid,Stride.Rendering
ShadowCasterRenderStage: ref!! 9d878307-f1a6-4ff9-bc10-2729c6e2cf40
d59ef45dd99e49d3af3887763d153aa7: !Stride.Rendering.Shadows.LightPointShadowMapRendererCubeMap,Stride.Rendering
ShadowCasterRenderStage: ref!! 65947d6a-5d89-456c-8774-830c2a2da459
42be209952db58294c9f06c58809b8ca: !Stride.Rendering.InstancingRenderFeature,Stride.Rendering {}
28e9bf54a5adbe063f59fb17acb2723e: !Stride.Rendering.Sprites.SpriteRenderFeature,Stride.Rendering
RenderStageSelectors:
d74665cff080638a2439c4422e542d85: !Stride.Rendering.Sprites.SpriteTransparentRenderStageSelector,Stride.Rendering
OpaqueRenderStage: ref!! 8d4a292b-24b3-4633-8906-dfec11b7c9b8
TransparentRenderStage: ref!! 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
EffectName: Test
60780391e205770513fdd53e07279a01: !Stride.Rendering.Background.BackgroundRenderFeature,Stride.Rendering
RenderStageSelectors:
11c8b8ccb522e3cd1dd6688016062a6d: !Stride.Rendering.SimpleGroupToRenderStageSelector,Stride.Rendering
RenderStage: ref!! 8d4a292b-24b3-4633-8906-dfec11b7c9b8
EffectName: Test
93933ad00d0c357d4915ad462cbfd04c: !Stride.Rendering.UI.UIRenderFeature,Stride.UI
RenderStageSelectors:
14a071694411235038a102ac3794bb4d: !Stride.Rendering.SimpleGroupToRenderStageSelector,Stride.Rendering
RenderStage: ref!! 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
EffectName: Test
9013eab3ea0ef6c98bf133b86c173d45: !Stride.Particles.Rendering.ParticleEmitterRenderFeature,Stride.Particles
RenderStageSelectors:
af1bd241305893ef8ff7952184e1cb0b: !Stride.Particles.Rendering.ParticleEmitterTransparentRenderStageSelector,Stride.Particles
OpaqueRenderStage: ref!! 8d4a292b-24b3-4633-8906-dfec11b7c9b8
TransparentRenderStage: ref!! 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
EffectName: null
PipelineProcessors: {}
SharedRenderers:
60459475d3a3adaf2d1ba5d99913ca75: !Stride.Rendering.Compositing.ForwardRenderer,Stride.Engine
Id: 34c516f8-bdd4-4cd0-9cb9-6d49181207c2
Clear:
Id: 4bc4b2ca-027e-4e4a-94cb-2912709bef5f
Color: {R: 0.40491876, G: 0.41189542, B: 0.43775, A: 1.0}
LightProbes: true
OpaqueRenderStage: ref!! 8d4a292b-24b3-4633-8906-dfec11b7c9b8
TransparentRenderStage: ref!! 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
ShadowMapRenderStages:
fc4d1e0de5c2b0bbc27bcf96e9a848fd: ref!! f0962b6c-387c-454e-9759-721acf05bb80
87ff1d9cdd52418daf76385176a0e316: ref!! 9d878307-f1a6-4ff9-bc10-2729c6e2cf40
5e059d4cc2db4ee8a1f28a40f4ac3ae8: ref!! 65947d6a-5d89-456c-8774-830c2a2da459
GBufferRenderStage: ref!! a1e7e6ad-7626-4eb6-a125-13275a9531c2
PostEffects: !PostProcessingEffects ref!! 1aafafbc-ebfa-40a4-bfff-b1153b651a9f
LightShafts: null
VRSettings:
Enabled: false
RequiredApis: {}
Overlays: {}
RequestPassthrough: false
SubsurfaceScatteringBlurEffect: null
MSAALevel: None
MSAAResolver: {}
d5b2e71c088247e21556decdce138d96: !Stride.Rendering.Compositing.ForwardRenderer,Stride.Engine
Id: 8f3f7aca-0a1d-435c-b142-85a5bf7700a2
Clear:
Id: 66a42307-1985-4316-871a-768449238c11
Color: {R: 0.40491876, G: 0.41189542, B: 0.43775, A: 1.0}
LightProbes: true
OpaqueRenderStage: ref!! 8d4a292b-24b3-4633-8906-dfec11b7c9b8
TransparentRenderStage: ref!! 7e91ee8c-255a-4bc0-90fb-feebfd461e2f
ShadowMapRenderStages:
2323a99a8a983e182f318e55604659b0: ref!! f0962b6c-387c-454e-9759-721acf05bb80
61b557b74893400898c007d5ece5e87f: ref!! 9d878307-f1a6-4ff9-bc10-2729c6e2cf40
5c382f66ee524c498d26d9aaf19fc9c3: ref!! 65947d6a-5d89-456c-8774-830c2a2da459
GBufferRenderStage: ref!! a1e7e6ad-7626-4eb6-a125-13275a9531c2
PostEffects: null
LightShafts: null
VRSettings:
Enabled: false
RequiredApis: {}
Overlays: {}
RequestPassthrough: false
SubsurfaceScatteringBlurEffect: null
MSAALevel: None
MSAAResolver: {}
34ecb9b2633eacfc439ba8744fe05102: !PostProcessingEffects
Id: 1aafafbc-ebfa-40a4-bfff-b1153b651a9f
Outline:
Enabled: false
NormalWeight: 2.0
DepthWeight: 0.2
NormalNearCutoff: 0.1
Fog:
Enabled: false
Density: 0.1
Color: {R: 1.0, G: 1.0, B: 1.0}
FogStart: 10.0
SkipBackground: false
AmbientOcclusion:
Enabled: false
LocalReflections:
Enabled: false
ResolvePassResolution: Full
DepthResolution: Half
DepthOfField:
Enabled: false
DOFAreas: {X: 0.5, Y: 6.0, Z: 50.0, W: 200.0}
BrightFilter:
Color: {R: 1.0, G: 1.0, B: 1.0}
Bloom:
Distortion: {X: 1.0, Y: 1.0}
Afterimage:
Enabled: false
LightStreak:
Attenuation: 0.7
LensFlare: {}
ColorTransforms:
Transforms:
1e06f805f8b2e949a06c30d45fe413ef: !ToneMap
Enabled*: false
Operator: !ToneMapHejl2Operator {}
c57351444609d14ea258b3f511ec8a74: !FilmGrain
Enabled: false
e86e22e9a5d65545b8b55fca26e0afee: !Vignetting
Enabled: false
Color: {R: 0.0, G: 0.0, B: 0.0}
Antialiasing: !FXAAEffect {}
ee80a20a9bd99f2d70711114e15fe7ca: !Stride.Rendering.Compositing.DebugRenderer,Stride.Rendering
Id: 30560a55-2756-4ed5-8bb0-523ec2b44081
DebugRenderStages: {}
Game: !Stride.Rendering.Compositing.SceneCameraRenderer,Stride.Engine
Id: 76fe87cf-f574-4ad6-85b8-e9a9586be0e2
Camera: ref!! 291722d5-fb04-46ed-b58d-7f4455266d8e
Child: !Stride.Rendering.Compositing.SceneRendererCollection,Stride.Rendering
Id: 82568e46-92e7-421a-8dca-114a74e0cd69
Children:
d39c5ddbf8b7d5ca02bafb6496b1cc3c: !Stride.Rendering.Compositing.ForwardRenderer,Stride.Engine ref!! 34c516f8-bdd4-4cd0-9cb9-6d49181207c2
01d338078e9b21121ead0868932613dd: !Stride.Rendering.Compositing.DebugRenderer,Stride.Rendering ref!! 30560a55-2756-4ed5-8bb0-523ec2b44081
RenderMask: All
SingleView: !Stride.Rendering.Compositing.ForwardRenderer,Stride.Engine ref!! 8f3f7aca-0a1d-435c-b142-85a5bf7700a2
Editor: !Stride.Rendering.Compositing.ForwardRenderer,Stride.Engine ref!! 34c516f8-bdd4-4cd0-9cb9-6d49181207c2
BlockPositions: {}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,104 @@
!SpriteSheet
Id: c9c540bb-2671-42de-bf06-b935954cd604
SerializedVersion: {Stride: 2.0.0.0}
Tags: []
Type: Sprite2D
ColorKeyColor: {R: 255, G: 0, B: 255, A: 255}
Packing: {}
Sprites:
11c7232b6b95611a617109444fd2eb30:
Source: !file ../../Resources/coin.png
Name: coin_0
TextureRegion: {X: 0, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
8982b889347b53d50b778103a3359113:
Source: !file ../../Resources/coin.png
Name: coin_1
TextureRegion: {X: 16, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
ea92186c4227d545f91e18983a339cc6:
Source: !file ../../Resources/coin.png
Name: coin_2
TextureRegion: {X: 32, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
049fa447233fea6f9418765b266d17b6:
Source: !file ../../Resources/coin.png
Name: coin_3
TextureRegion: {X: 48, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
0d51d74e8852bda127ac5515853080d7:
Source: !file ../../Resources/coin.png
Name: coin_4
TextureRegion: {X: 64, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
beeefe95329af5c68f8a56d87ce97a3c:
Source: !file ../../Resources/coin.png
Name: coin_5
TextureRegion: {X: 80, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
3a5ba65f13cf86400ceaab226b77ed2b:
Source: !file ../../Resources/coin.png
Name: coin_6
TextureRegion: {X: 96, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
595d05f6fcff29c84db1423286bcca2e:
Source: !file ../../Resources/coin.png
Name: coin_7
TextureRegion: {X: 112, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
ed078696aa11e4b2e4e4a9bf7c47ea09:
Source: !file ../../Resources/coin.png
Name: coin_8
TextureRegion: {X: 128, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
050188a4c71e78165ae1404b4d158dbe:
Source: !file ../../Resources/coin.png
Name: coin_9
TextureRegion: {X: 144, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
82bd686c511a8452b0f2099e21a0e10a:
Source: !file ../../Resources/coin.png
Name: coin_10
TextureRegion: {X: 160, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
ebe87ba1f73954173b812cf66ed48777:
Source: !file ../../Resources/coin.png
Name: coin_11
TextureRegion: {X: 176, Y: 0, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
@@ -0,0 +1,48 @@
!SpriteSheet
Id: 58a69aad-99db-4bb0-a3b8-951270816915
SerializedVersion: {Stride: 2.0.0.0}
Tags: []
Type: Sprite2D
ColorKeyColor: {R: 255, G: 0, B: 255, A: 255}
Packing: {}
Sprites:
d3fd346f77408a63902fe39142b67375:
Source: !file ../../Resources/spring.png
Name: platform_thin_long
TextureRegion: {X: 0, Y: 7, Width: 32, Height: 9}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
0f233344a899bb5a19a36a258bc2bcb9:
Source: !file ../../Resources/spring.png
Name: platform_thin_short
TextureRegion: {X: 32, Y: 7, Width: 16, Height: 9}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
715628101d20f7b9d1f93c0f3ae5a90b:
Source: !file ../../Resources/spring.png
Name: platform_thick_long
TextureRegion: {X: 0, Y: 32, Width: 32, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
39238150bcbf695d39cdf3bff75fc3a5:
Source: !file ../../Resources/spring.png
Name: platform_thick_short
TextureRegion: {X: 32, Y: 32, Width: 16, Height: 16}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
cb058a356cba2a81e57968b56e2e4465:
Source: !file ../../Resources/spring.png
Name: wall
TextureRegion: {X: 128, Y: 48, Width: 16, Height: 32}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
@@ -0,0 +1,232 @@
!SpriteSheet
Id: c805102c-bb90-4d52-9429-dc7e873a001c
SerializedVersion: {Stride: 2.0.0.0}
Tags: []
Type: Sprite2D
ColorKeyColor: {R: 255, G: 0, B: 255, A: 255}
Packing: {}
Sprites:
6a5e893e188fd89658070cf34e33b76c:
Source: !file ../../Resources/lancelot.png
Name: knight_idle_0
TextureRegion: {X: 8, Y: 8, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
872f5652f9a821afff5f4239ec440d66:
Source: !file ../../Resources/lancelot.png
Name: knight_idle_1
TextureRegion: {X: 40, Y: 8, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
d7d3d367a2c91f9ba6dfd6a227979f34:
Source: !file ../../Resources/lancelot.png
Name: knight_idle_2
TextureRegion: {X: 72, Y: 8, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
06128f1c92205be7f6837889971ea15c:
Source: !file ../../Resources/lancelot.png
Name: knight_idle_3
TextureRegion: {X: 104, Y: 8, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
e4dd778f1e42cd7286e6831738633da3:
Source: !file ../../Resources/lancelot.png
Name: knight_run_0
TextureRegion: {X: 8, Y: 40, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
5051b9981963e7ffd497ff48489a7d66:
Source: !file ../../Resources/lancelot.png
Name: knight_run_1
TextureRegion: {X: 40, Y: 40, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
24539a6ed06bb723efa07dd2729cd3c0:
Source: !file ../../Resources/lancelot.png
Name: knight_run_2
TextureRegion: {X: 72, Y: 40, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
0429a09865a03df44f09cf9a89285cbe:
Source: !file ../../Resources/lancelot.png
Name: knight_run_3
TextureRegion: {X: 104, Y: 40, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
88d5c93df11446bc2563cda9b26e6cb0:
Source: !file ../../Resources/lancelot.png
Name: knight_run_4
TextureRegion: {X: 8, Y: 72, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
d66bda5d30f0c9bb576bc9c9a1fd7015:
Source: !file ../../Resources/lancelot.png
Name: knight_run_5
TextureRegion: {X: 40, Y: 72, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
bd52d666201e8bf6b4f05f6463d887db:
Source: !file ../../Resources/lancelot.png
Name: knight_run_6
TextureRegion: {X: 72, Y: 72, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
2e038e01b3671391419ddfb346c8147b:
Source: !file ../../Resources/lancelot.png
Name: knight_run_7
TextureRegion: {X: 104, Y: 72, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
c58dc014a2573861b7a6acf4439fe9b5:
Source: !file ../../Resources/lancelot.png
Name: knight_run_8
TextureRegion: {X: 8, Y: 104, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
e5dcfbdc9f563aee81e62de833e1df5c:
Source: !file ../../Resources/lancelot.png
Name: knight_run_9
TextureRegion: {X: 40, Y: 104, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
adf918ccb83acdaa7c2a8c7d1c80c0b1:
Source: !file ../../Resources/lancelot.png
Name: knight_run_10
TextureRegion: {X: 72, Y: 104, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
256350f8e893e22e4bc65a548955f7c5:
Source: !file ../../Resources/lancelot.png
Name: knight_run_11
TextureRegion: {X: 104, Y: 104, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
05e80bb8f4e3f173708bacac478ab190:
Source: !file ../../Resources/lancelot.png
Name: knight_run_12
TextureRegion: {X: 8, Y: 136, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
0161bd6b447e3ad1d74a5281378430f7:
Source: !file ../../Resources/lancelot.png
Name: knight_run_13
TextureRegion: {X: 40, Y: 136, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
3c6965cce17c4561a3c0844ae0bd75d7:
Source: !file ../../Resources/lancelot.png
Name: knight_run_14
TextureRegion: {X: 72, Y: 136, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
5197a99fbe55a1327fb3d0281c7e5678:
Source: !file ../../Resources/lancelot.png
Name: knight_run_15
TextureRegion: {X: 104, Y: 136, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
13ee02648e7104acb1d26d0fc476476e:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_0
TextureRegion: {X: 8, Y: 168, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
0294d2cde6a0e8326764f148b90aa2a6:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_1
TextureRegion: {X: 40, Y: 168, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
df37eb68c7f59295b540d8c69ddeb1d4:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_2
TextureRegion: {X: 72, Y: 168, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
9ae668af7084cc063b89afb67fcf46fb:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_3
TextureRegion: {X: 105, Y: 169, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
02de2ab116eb08289673451a90a6c282:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_4
TextureRegion: {X: 8, Y: 201, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
c5ec8e40515be423a38564e6c583609e:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_5
TextureRegion: {X: 40, Y: 202, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
4e11af4756909c70a857f1f622a9c4bb:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_6
TextureRegion: {X: 72, Y: 200, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
b16c2e3a9db1208ce9789d58c1f1a0ec:
Source: !file ../../Resources/lancelot.png
Name: knight_jump_7
TextureRegion: {X: 104, Y: 200, Width: 16, Height: 20}
PixelsPerUnit: 16.0
Center: {X: 0.0, Y: 0.0}
Borders: {X: 0.0, Y: 0.0, Z: 0.0, W: 0.0}
BorderColor: {R: 0, G: 0, B: 0, A: 0}
@@ -0,0 +1,9 @@
!Texture
Id: 50562287-8d9a-4613-a14a-ecb46f748419
SerializedVersion: {Stride: 2.0.0.0}
Tags: []
Source: !file ../../Resources/sky.png
IsCompressed: false
Type: !ColorTextureType
ColorKeyColor: {R: 255, G: 0, B: 255, A: 255}
IsStreamable: false
@@ -0,0 +1,42 @@
!PrefabAsset
Id: d8b2fb42-429d-4448-b6cf-5e8d25260d7a
SerializedVersion: {Stride: 3.1.0.1}
Tags: []
Hierarchy:
RootParts:
- ref!! 998cd5e2-baf0-43c8-a300-9df5130ed98b
Parts:
- Folder: Map
Entity:
Id: 998cd5e2-baf0-43c8-a300-9df5130ed98b
Name: Platform_Thick_long
Components:
20f9276efc76d56da73398d0710656f4: !TransformComponent
Id: cf7988cd-4953-4c5d-a7b5-58c56e1ee9a5
Position: {X: 0.0, Y: -8.0, Z: 0.0}
Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
Scale: {X: 1.0, Y: 1.0, Z: 1.0}
Children: {}
a21979cf00278ebc58064c6f81abb412: !SpriteComponent
Id: fcab2d14-3a8a-41cb-b147-198ea50a6cfa
SpriteProvider: !SpriteFromSheet
Sheet: 58a69aad-99db-4bb0-a3b8-951270816915:Sprites/ground_sheet
CurrentFrame: 2
Color: {R: 1.0, G: 1.0, B: 1.0, A: 1.0}
Sampler: PointClamp
2d8f25f180e3182173f8aaa66cf6cd8a: !StaticColliderComponent
Id: 15124571-bfd2-4310-bd9f-09dead973575
CanSleep: false
Restitution: 0.0
Friction: 0.5
RollingFriction: 0.0
CcdMotionThreshold: 0.0
CcdSweptSphereRadius: 0.0
IsTrigger: false
AlwaysUpdateNaviMeshCache: false
ColliderShapes:
871b0d7c5a000160d4688735144c4309: !BoxColliderShapeDesc
Is2D: true
Size: {X: 2.1, Y: 1.0, Z: 1.0}
LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0}
LocalRotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
@@ -0,0 +1,41 @@
!PrefabAsset
Id: a954e461-1843-4050-96cd-6e43e6b82b47
SerializedVersion: {Stride: 3.1.0.1}
Tags: []
Hierarchy:
RootParts:
- ref!! ab043dcf-3513-4a3c-be2d-87125a0bf4fb
Parts:
- Folder: Map/Plattforms
Entity:
Id: ab043dcf-3513-4a3c-be2d-87125a0bf4fb
Name: Platform_Thin_long
Components:
434ef69de74e6501fc61289ef3259ebc: !TransformComponent
Id: 04b94243-5955-4207-a4cd-1930534e527d
Position: {X: 5.0, Y: -5.0, Z: 0.0}
Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
Scale: {X: 1.0, Y: 1.0, Z: 1.0}
Children: {}
5b0a5872e9545d1a89edc9001b489567: !SpriteComponent
Id: 42b0d164-010b-4cfc-a003-c5a215e164ff
SpriteProvider: !SpriteFromSheet
Sheet: 58a69aad-99db-4bb0-a3b8-951270816915:Sprites/ground_sheet
Color: {R: 1.0, G: 1.0, B: 1.0, A: 1.0}
Sampler: PointClamp
10073a8d05bb76a838b430a04a8ed5d7: !StaticColliderComponent
Id: 0b6ee4c5-2730-42d9-a630-61e308948b9f
CanSleep: false
Restitution: 0.0
Friction: 0.5
RollingFriction: 0.0
CcdMotionThreshold: 0.0
CcdSweptSphereRadius: 0.0
IsTrigger: false
AlwaysUpdateNaviMeshCache: false
ColliderShapes:
e1f5063c512e0cee9eda177cf7f75895: !BoxColliderShapeDesc
Is2D: true
Size: {X: 2.01, Y: 0.5, Z: 1.0}
LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0}
LocalRotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
@@ -0,0 +1,42 @@
!PrefabAsset
Id: b4f4dad7-71d0-41ed-96e8-4c44deb4e618
SerializedVersion: {Stride: 3.1.0.1}
Tags: []
Hierarchy:
RootParts:
- ref!! d5871d1f-0974-4d34-8dd7-9955583ffe62
Parts:
- Folder: Map/Plattforms
Entity:
Id: d5871d1f-0974-4d34-8dd7-9955583ffe62
Name: Platform_Thin_short
Components:
89fda52fd0dcd43889322555fec8d1da: !TransformComponent
Id: 812d76b9-5f12-49b9-a46a-1307882a992c
Position: {X: 0.0, Y: 0.0, Z: 0.0}
Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
Scale: {X: 1.0, Y: 1.0, Z: 1.0}
Children: {}
38f388c3d5cc14129696b9b4940105b6: !SpriteComponent
Id: 38a9f3eb-8397-45f9-a56c-e53400c54156
SpriteProvider: !SpriteFromSheet
Sheet: 58a69aad-99db-4bb0-a3b8-951270816915:Sprites/ground_sheet
CurrentFrame: 1
Color: {R: 1.0, G: 1.0, B: 1.0, A: 1.0}
Sampler: PointClamp
fdbc84f119d35b74720fafb625a63045: !StaticColliderComponent
Id: 909b2408-9760-48f5-b976-41c3c0607b5b
CanSleep: false
Restitution: 0.0
Friction: 0.5
RollingFriction: 0.0
CcdMotionThreshold: 0.0
CcdSweptSphereRadius: 0.0
IsTrigger: false
AlwaysUpdateNaviMeshCache: false
ColliderShapes:
a3e271dc2945c92633e135fb0746b73b: !BoxColliderShapeDesc
Is2D: true
Size: {X: 1.0, Y: 0.5, Z: 1.0}
LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0}
LocalRotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
@@ -0,0 +1,42 @@
!PrefabAsset
Id: 6ae6f190-d593-4f6d-b6cf-6bb146502ecb
SerializedVersion: {Stride: 3.1.0.1}
Tags: []
Hierarchy:
RootParts:
- ref!! 67c8a1fc-cdbb-4798-941f-aed2d085334c
Parts:
- Folder: Map/Wall
Entity:
Id: 67c8a1fc-cdbb-4798-941f-aed2d085334c
Name: Wall
Components:
6b042eb4e809616483fdaf58b8c948eb: !TransformComponent
Id: 3c47f86a-2715-448d-9b02-0caa734cb31a
Position: {X: 0.0, Y: 0.0, Z: 0.0}
Rotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
Scale: {X: 1.0, Y: 1.0, Z: 1.0}
Children: {}
e9a80b3e9215ef8e6674eb86dae7c874: !SpriteComponent
Id: c0b665c4-ce75-49ae-a2e1-020ff3ddd197
SpriteProvider: !SpriteFromSheet
Sheet: 58a69aad-99db-4bb0-a3b8-951270816915:Sprites/ground_sheet
CurrentFrame: 4
Color: {R: 1.0, G: 1.0, B: 1.0, A: 1.0}
Sampler: PointClamp
a4ebe4bf882a3966e7cf2dcdc873274d: !StaticColliderComponent
Id: 7fe1158b-5b77-4c8a-bd59-222df956ad23
CanSleep: false
Restitution: 0.0
Friction: 0.5
RollingFriction: 0.0
CcdMotionThreshold: 0.0
CcdSweptSphereRadius: 0.0
IsTrigger: false
AlwaysUpdateNaviMeshCache: false
ColliderShapes:
35efa962fb0e5d173f33a90520ed0753: !BoxColliderShapeDesc
Is2D: true
Size: {X: 1.0, Y: 2.1, Z: 1.0}
LocalOffset: {X: 0.0, Y: 0.0, Z: 0.0}
LocalRotation: {X: 0.0, Y: 0.0, Z: 0.0, W: 1.0}
@@ -0,0 +1,30 @@
using System;
using Stride.Core;
using Stride.Engine;
using Stride.Graphics;
using Stride.Rendering.Sprites;
namespace Platformer2D.Gameplay;
public class CoinRotation : SyncScript
{
public required SpriteComponent Sprite { get; init; }
private float animationTimer = 0f;
// Animation Spped: Every 1/12 passed seconds the next frame will be played. Closer to 1.0 is a slower Animation.
private const float animationInterval = 1f / 12f;
private const int COIN_FRAMES_END = 12;
private SpriteFromSheet SpriteSheet => (SpriteFromSheet)Sprite.SpriteProvider;
public override void Update()
{
animationTimer += (float) Game.UpdateTime.Elapsed.TotalSeconds;
if (animationTimer >= animationInterval)
{
animationTimer -= animationInterval;
SpriteSheet.CurrentFrame = (SpriteSheet.CurrentFrame + 1) % COIN_FRAMES_END;
}
}
}
@@ -0,0 +1,42 @@
using System;
using Stride.Core;
using System.Threading.Tasks;
using Stride.Engine;
using Stride.Audio;
using Stride.Physics;
using Platformer2D.Player;
namespace Platformer2D.Gameplay;
public class CollectCoin : AsyncScript
{
public required StaticColliderComponent StaticColliderComponent { get; init; }
[Display("Collect Coin")]
public Sound? SoundEffect { get; set; }
private SoundInstance? sfxInstance;
public override async Task Execute()
{
while(Game.IsRunning)
{
// Waits for a Collision with the Character
var collision = await StaticColliderComponent.NewCollision();
var otherCollider = StaticColliderComponent == collision.ColliderA ? collision.ColliderB : collision.ColliderA;
if (otherCollider.Entity.Get<PlayerController>() != null)
{
// Disable Sprite
Entity.Get<SpriteComponent>().Enabled = false;
sfxInstance = SoundEffect?.CreateInstance();
// Play Sound
sfxInstance?.Play();
// Disable Coin Object
await Task.Delay(TimeSpan.FromSeconds(3));
Entity.Scene = null;
}
}
}
}
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Stride.Engine" Version="4.3.0.2507" />
<PackageReference Include="Stride.Video" Version="4.3.0.2507" />
<PackageReference Include="Stride.Physics" Version="4.3.0.2507" />
<PackageReference Include="Stride.Navigation" Version="4.3.0.2507" />
<PackageReference Include="Stride.Particles" Version="4.3.0.2507" />
<PackageReference Include="Stride.UI" Version="4.3.0.2507" />
<PackageReference Include="Stride.Core.Assets.CompilerApp" Version="4.3.0.2507" IncludeAssets="build;buildTransitive" />
</ItemGroup>
</Project>
@@ -0,0 +1,18 @@
!Package
SerializedVersion: {Assets: 3.1.0.0}
Meta:
Name: Platformer2D
Version: 1.0.0
Authors: []
Owners: []
Dependencies: null
AssetFolders:
- Path: !dir ../Assets
- Path: !dir Effects
ResourceFolders:
- !dir Resources
OutputGroupDirectories: {}
ExplicitFolders: []
Bundles: []
TemplateFolders: []
RootAssets: []
@@ -0,0 +1,169 @@
using System;
using Stride.Core.Mathematics;
using Stride.Input;
using Stride.Engine;
using Stride.Core;
using Stride.Physics;
using System.Windows.Input;
using Stride.Graphics;
using Stride.Rendering.Sprites;
using System.Collections.Generic;
namespace Platformer2D.Player;
public class PlayerController : SyncScript
{
public required CharacterComponent CharacterComponent { get; init; }
public required SpriteComponent Sprite { get; init; }
private float animationTimer = 0f;
// Animation Spped: Every 1/10 passed seconds the next frame will be played. Closer to 1.0 is a slower Animation.
private const float animationInterval = 1f / 5f;
private int idleFrame = 0;
private int runFrame = 0;
private int jumpFrame = 0;
private const int IDLE_FRAME_END = 4;
private const int RUN_FRAME_END = 16;
private const int JUMP_FRAME_END = 21;
private const int JUMP_FRAME_COUNT = 6;
// units per second (is multiplied with Vector.UnitX which is 1)
private const float MOVE_SPEED = 5f;
private bool isFacingRight = true;
private SpriteFromSheet SpriteSheet => (SpriteFromSheet)Sprite.SpriteProvider;
public override void Update()
{
// HandleAnimation uses bool isMoving from the users Input
HandleAnimation(HandleInput());
}
/// <summary>
/// Handles both the Input of the Player via Keyboard (WAD- & Arrow-Keys) and the resulting Animation at the end according to input
/// </summary>
private bool HandleInput()
{
bool isMoving = false;
if (Input.HasKeyboard)
{
var moveDirection = Vector3.Zero;
CharacterComponent.SetVelocity(Vector3.Zero);
// Left
if (Input.IsKeyDown(Keys.A) || Input.IsKeyDown(Keys.Left))
{
isMoving = true;
if (isFacingRight)
{
isFacingRight = false;
CharacterComponent.Orientation = Quaternion.RotationY(MathUtil.DegreesToRadians(180));
}
moveDirection = -Vector3.UnitX * MOVE_SPEED;
}
// Right
if (Input.IsKeyDown(Keys.D) || Input.IsKeyDown(Keys.Right))
{
isMoving = true;
if (!isFacingRight)
{
isFacingRight = true;
CharacterComponent.Orientation = Quaternion.RotationY(MathUtil.DegreesToRadians(0));
}
moveDirection = Vector3.UnitX * MOVE_SPEED;
}
// Up (Jump)
if ((Input.IsKeyPressed(Keys.W) || Input.IsKeyPressed(Keys.Up)) && CharacterComponent.IsGrounded)
{
CharacterComponent.Jump();
}
CharacterComponent.SetVelocity(moveDirection);
HandleAnimation(isMoving);
}
return isMoving;
}
private void HandleAnimation(bool isMoving)
{
animationTimer += (float) Game.UpdateTime.Elapsed.TotalSeconds;
if (CharacterComponent.IsGrounded && !isMoving)
{
PlayIdleAnimation();
}
if (CharacterComponent.IsGrounded && isMoving)
{
PlayRunAnimation();
}
if (!CharacterComponent.IsGrounded)
{
PlayJumpAnimation();
}
}
/// <summary>
/// When there is no input, play idle-Animation. Which loops frames 0-3.
/// </summary>
private void PlayIdleAnimation()
{
if (animationTimer >= animationInterval)
{
animationTimer -= animationInterval;
SpriteSheet.CurrentFrame = idleFrame;
idleFrame = (idleFrame + 1) % IDLE_FRAME_END;
}
ResetFrameCounts(ref runFrame, ref jumpFrame);
}
/// <summary>
/// When there is movement to either side, play run-animation. Which loops frames 4-19
/// </summary>
private void PlayRunAnimation()
{
if (animationTimer >= animationInterval)
{
animationTimer -= animationInterval;
SpriteSheet.CurrentFrame = IDLE_FRAME_END + runFrame;
runFrame = (runFrame + 1) % RUN_FRAME_END;
}
ResetFrameCounts(ref idleFrame, ref jumpFrame);
}
/// <summary>
/// When the character is not grounded, play jump-animation. Which only plays frames 20-27 once and then resets when the character is grounded.
/// </summary>
private void PlayJumpAnimation()
{
if (animationTimer >= animationInterval)
{
animationTimer -= animationInterval;
SpriteSheet.CurrentFrame = JUMP_FRAME_END + jumpFrame;
if (jumpFrame < JUMP_FRAME_COUNT)
{
jumpFrame++;
}
}
ResetFrameCounts(ref idleFrame, ref runFrame);
}
/// <summary>
/// Resets frame counters to 0.
/// Example: When playing the run animation, this is called to reset the idle and jump animation counter, to ensure they start at their first frame.
/// </summary>
private void ResetFrameCounts(ref int counter, ref int counter2)
{
counter = 0;
counter2 = 0;
}
}
@@ -0,0 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows</TargetFramework>
<ApplicationIcon>Resources\Icon.ico</ApplicationIcon>
<OutputType>WinExe</OutputType>
<RootNamespace>Platformer2D</RootNamespace>
<OutputPath>..\Bin\Windows\$(Configuration)\</OutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<!-- Force msbuild to check to rebuild this assembly instead of letting VS IDE guess -->
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Platformer2D.Game\Platformer2D.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,5 @@
using Stride.Engine;
using var game = new Game();
game.Run();
Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

@@ -0,0 +1,11 @@
!TemplateSample
Id: 7b8f32f7-80ce-41bb-8671-93493d93d1ec
Name: "Template: 2D Platformer"
Scope: Session
Description: A 2D Platformer template
FullDescription: This template implements a static camera, Trigger, basic 2D Character movement and sprite animations via script.
Group: Templates
Icon: ../../.sdtpl/IconPlatformer2D.png
DefaultOutputName: Platformer2D
Screenshots:
- .sdtpl/screenshot_small.jpg
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ff17de8ff447372cac869dce119e8bd587def80e62099e987a8cf721868adb7
size 654
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:020ec2bd2564aa049f0db23c54b36c3fc49d116fe0a8fe1d69d24e64f8b9f0ce
size 92514
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:7ec3ebb39d588b4e9988e36e011b427c46b284016cc66efbf3cbfc7d5a6d68f9
size 10275
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:18a4cea497c30cd3764b97e98e694633b4d954d145ea2dd77c636eb8879b97a3
size 161
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:69432f7ca3f4e4f3a50e3271c5bdf08e2aef426eba7e23a6fbb9144bc484946f
size 13197
+1 -1
View File
@@ -4,7 +4,7 @@
</PropertyGroup>
<!-- Runtime dependencies -->
<ItemGroup>
<PackageVersion Include="BepuPhysics" Version="2.5.0-beta.25" />
<PackageVersion Include="BepuPhysics" Version="2.5.0-beta.27" />
<PackageVersion Include="DotRecast.Core" Version="2025.2.1" />
<PackageVersion Include="DotRecast.Detour" Version="2025.2.1" />
<PackageVersion Include="DotRecast.Recast" Version="2025.2.1" />
@@ -84,7 +84,7 @@ namespace Stride.Core.Assets.CompilerApp
{ "log", "Enable file logging", v => options.EnableFileLogging = v != null },
{ "disable-auto-compile", "Disable auto-compile of projects", v => options.DisableAutoCompileProjects = v != null},
{ "project-configuration=", "Project configuration", v => options.ProjectConfiguration = v },
{ "platform=", "Platform name", v => options.Platform = (PlatformType)Enum.Parse(typeof(PlatformType), v) },
{ "platform=", "Platform name", v => options.Platform = Enum.Parse<PlatformType>(v) },
{ "solution-file=", "Solution File Name", v => options.SolutionFile = v },
{ "package-id=", "Package Id from the solution file", v => options.PackageId = Guid.Parse(v) },
{ "package-file=", "Input Package File Name", v => options.PackageFile = v },
@@ -12,7 +12,7 @@ public class DefaultAssetFactory<T> : AssetFactory<T> where T : Asset
{
public static T Create()
{
return (T)Activator.CreateInstance(typeof(T))!;
return Activator.CreateInstance<T>()!;
}
/// <inheritdoc/>
@@ -1601,6 +1601,11 @@ public sealed partial class PackageSession : IDisposable, IAssetFinder
return Equals(obj as PendingPackageUpgrade);
}
public override int GetHashCode()
{
return HashCode.Combine(PackageUpgrader, Dependency, DependencyPackage, DependencyVersionBeforeUpgrade);
}
public PendingPackageUpgrade Clone()
{
return new PendingPackageUpgrade(PackageUpgrader, Dependency.Clone(), DependencyPackage);
@@ -55,7 +55,7 @@ public static class VSProjectHelper
{
return null;
}
return (T)Enum.Parse(typeof(T), value);
return Enum.Parse<T>(value);
}
public static string GetOrCompileProjectAssembly(string fullProjectLocation, ILogger logger, string targets, bool autoCompileProject, string configuration, string platform = "AnyCPU", Dictionary<string, string>? extraProperties = null, bool onlyErrors = false, BuildRequestDataFlags flags = BuildRequestDataFlags.None)
@@ -10,7 +10,7 @@ public class StepCounter
public StepCounter()
{
stepResults = new int[Enum.GetValues(typeof(ResultStatus)).Length];
stepResults = new int[Enum.GetValues<ResultStatus>().Length];
}
public void AddStepResult(ResultStatus result)
@@ -35,7 +35,7 @@ public class StepCounter
lock (stepResults)
{
Total = 0;
foreach (var value in Enum.GetValues(typeof(ResultStatus)))
foreach (var value in Enum.GetValues<ResultStatus>())
stepResults[(int)value] = 0;
}
}
@@ -1,29 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
/// <summary>
/// Generates array serializer type from a given array type.
/// </summary>
public class CecilArraySerializerFactory : ICecilSerializerFactory
{
private readonly TypeReference genericArraySerializerType;
public CecilArraySerializerFactory(TypeReference genericArraySerializerType)
{
this.genericArraySerializerType = genericArraySerializerType;
}
public TypeReference? GetSerializer(TypeReference objectType)
{
if (objectType.IsArray)
{
return genericArraySerializerType.MakeGenericType(((ArrayType)objectType).ElementType);
}
return null;
}
}
@@ -1,30 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
/// <summary>
/// Generates enum serializer type from a given enum type.
/// </summary>
public class CecilEnumSerializerFactory : ICecilSerializerFactory
{
private readonly TypeReference genericEnumSerializerType;
public CecilEnumSerializerFactory(TypeReference genericEnumSerializerType)
{
this.genericEnumSerializerType = genericEnumSerializerType;
}
public TypeReference? GetSerializer(TypeReference objectType)
{
var resolvedObjectType = objectType.Resolve();
if (resolvedObjectType?.IsEnum == true)
{
return genericEnumSerializerType.MakeGenericType(objectType);
}
return null;
}
}
@@ -1,53 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
/// <summary>
/// Gives the specified serializer type constructed with generic arguments of the serialized type.
/// As an example, this instance with <see cref="ListSerializer{}"/> will give <see cref="ListSerializer{int}"/> from <see cref="int"/>.
/// </summary>
public class CecilGenericSerializerFactory : ICecilSerializerFactory
{
private readonly Type genericSerializableType;
private readonly bool checkInterfaces;
protected Func<TypeReference, TypeReference> CreateSerializer { get; set; }
private CecilGenericSerializerFactory(Type genericSerializableType, Func<TypeReference, TypeReference> createSerializer)
{
this.genericSerializableType = genericSerializableType;
CreateSerializer = createSerializer;
checkInterfaces = genericSerializableType.IsInterface;
}
public CecilGenericSerializerFactory(Type genericSerializableType, TypeReference genericSerializerType)
: this(genericSerializableType, type => genericSerializerType.MakeGenericType([.. ((GenericInstanceType)type).GenericArguments]))
{
if (genericSerializerType == null)
throw new ArgumentNullException(nameof(genericSerializerType));
if (genericSerializableType == null)
throw new ArgumentNullException(nameof(genericSerializableType));
}
#region IDataSerializerFactory Members
public virtual TypeReference? GetSerializer(TypeReference objectType)
{
// Check if objectType matches genericSerializableType.
// Note: Not perfectly valid but hopefully it should be fast enough.
if (objectType.IsGenericInstance && checkInterfaces)
{
if (objectType.GetElementType().Resolve().Interfaces.Any(x => x.InterfaceType.IsGenericInstance && x.InterfaceType.GetElementType().FullName == genericSerializableType.FullName))
return CreateSerializer(objectType);
}
if (objectType.IsGenericInstance && objectType.GetElementType().FullName == genericSerializableType.FullName)
return CreateSerializer(objectType);
return null;
}
#endregion
}
@@ -1,57 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
/// <summary>
/// Enumerates required subtypes the given serializer will use internally.
/// </summary>
public class CecilSerializerDependency : ICecilSerializerDependency
{
readonly string genericSerializerTypeFullName;
readonly TypeReference? genericSerializableType;
/// <summary>
/// Initializes a new instance of the <see cref="CecilSerializerDependency" /> class.
/// It will enumerates T1, T2 from genericSerializerType{T1, T2}.
/// </summary>
/// <param name="genericSerializerTypeFullName">Type of the generic serializer.</param>
public CecilSerializerDependency(string genericSerializerTypeFullName)
{
this.genericSerializerTypeFullName = genericSerializerTypeFullName ?? throw new ArgumentNullException(nameof(genericSerializerTypeFullName));
}
/// <summary>
/// Initializes a new instance of the <see cref="CecilSerializerDependency" /> class.
/// It will enumerates genericSerializableType{T1, T2} from genericSerializerType{T1, T2}.
/// </summary>
/// <param name="genericSerializerTypeFullName">Type of the generic serializer.</param>
/// <param name="genericSerializableType">Type of the generic serializable.</param>
public CecilSerializerDependency(string genericSerializerTypeFullName, TypeReference genericSerializableType)
{
this.genericSerializerTypeFullName = genericSerializerTypeFullName ?? throw new ArgumentNullException(nameof(genericSerializerTypeFullName));
this.genericSerializableType = genericSerializableType ?? throw new ArgumentNullException(nameof(genericSerializableType));
}
public IEnumerable<TypeReference> EnumerateSubTypesFromSerializer(TypeReference serializerType)
{
// Check if serializer type name matches
if (serializerType.IsGenericInstance && serializerType.GetElementType().FullName == genericSerializerTypeFullName)
{
if (genericSerializableType != null)
{
// Transforms genericSerializerType{T1, T2} into genericSerializableType{T1, T2}
return Enumerable.Repeat(genericSerializableType.MakeGenericType([.. ((GenericInstanceType)serializerType).GenericArguments]), 1);
}
else
{
// Transforms genericSerializerType{T1, T2} into T1, T2
return ((GenericInstanceType)serializerType).GenericArguments;
}
}
return [];
}
}
@@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
/// <summary>
/// Enumerates required subtypes the given serializer will use internally.
/// This is useful for generation of serialization assembly, when AOT is performed (all generic serializers must be available).
/// </summary>
public interface ICecilSerializerDependency
{
/// <summary>
/// Enumerates the types this serializer requires.
/// </summary>
/// <param name="serializerType">Type of the serializer.</param>
/// <returns></returns>
IEnumerable<TypeReference> EnumerateSubTypesFromSerializer(TypeReference serializerType);
}
@@ -1,20 +0,0 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
/// <summary>
/// Gives the required generic serializer for a given type.
/// This is useful for generation of serialization assembly, when AOT is performed (all generic serializers must be available).
/// </summary>
public interface ICecilSerializerFactory
{
/// <summary>
/// Gets the serializer type from a given object type.
/// </summary>
/// <param name="objectType">Type of the object to serialize.</param>
/// <returns></returns>
TypeReference? GetSerializer(TypeReference objectType);
}
@@ -0,0 +1,206 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace Stride.Core.AssemblyProcessor;
/// <summary>
/// A thin fluent wrapper over Cecil's IL emission that reduces verbosity.
/// Provides automatic <see cref="ModuleDefinition.ImportReference(TypeReference)"/> and composite patterns
/// like <c>typeof(T)</c> emission.
/// </summary>
internal class ILBuilder
{
private readonly MethodBody body;
private readonly ModuleDefinition module;
public ILBuilder(MethodBody body, ModuleDefinition module)
{
this.body = body;
this.module = module;
}
/// <summary>
/// The underlying method body, for direct access when needed (e.g. adding variables).
/// </summary>
public MethodBody Body => body;
/// <summary>
/// The module used for importing references.
/// </summary>
public ModuleDefinition Module => module;
// ── Core Emit overloads ──────────────────────────────────────────
public ILBuilder Emit(OpCode opcode)
{
body.Instructions.Add(Instruction.Create(opcode));
return this;
}
public ILBuilder Emit(OpCode opcode, int value)
{
body.Instructions.Add(Instruction.Create(opcode, value));
return this;
}
public ILBuilder Emit(OpCode opcode, byte value)
{
body.Instructions.Add(Instruction.Create(opcode, value));
return this;
}
public ILBuilder Emit(OpCode opcode, string value)
{
body.Instructions.Add(Instruction.Create(opcode, value));
return this;
}
public ILBuilder Emit(OpCode opcode, TypeReference type)
{
body.Instructions.Add(Instruction.Create(opcode, type));
return this;
}
public ILBuilder Emit(OpCode opcode, MethodReference method)
{
body.Instructions.Add(Instruction.Create(opcode, method));
return this;
}
public ILBuilder Emit(OpCode opcode, FieldReference field)
{
body.Instructions.Add(Instruction.Create(opcode, field));
return this;
}
public ILBuilder Emit(OpCode opcode, VariableDefinition variable)
{
body.Instructions.Add(Instruction.Create(opcode, variable));
return this;
}
public ILBuilder Emit(OpCode opcode, ParameterDefinition parameter)
{
body.Instructions.Add(Instruction.Create(opcode, parameter));
return this;
}
public ILBuilder Emit(OpCode opcode, Instruction target)
{
body.Instructions.Add(Instruction.Create(opcode, target));
return this;
}
public ILBuilder Emit(OpCode opcode, CallSite callSite)
{
body.Instructions.Add(Instruction.Create(opcode, callSite));
return this;
}
/// <summary>
/// Appends a pre-created instruction to the method body.
/// </summary>
public ILBuilder Append(Instruction instruction)
{
body.Instructions.Add(instruction);
return this;
}
// ── Import helpers ───────────────────────────────────────────────
/// <summary>
/// Imports a <see cref="TypeReference"/> into the current module.
/// Shorthand for <c>module.ImportReference(type)</c>.
/// </summary>
public TypeReference Import(TypeReference type) => module.ImportReference(type);
/// <summary>
/// Imports a <see cref="MethodReference"/> into the current module.
/// Shorthand for <c>module.ImportReference(method)</c>.
/// </summary>
public MethodReference Import(MethodReference method) => module.ImportReference(method);
/// <summary>
/// Imports a <see cref="FieldReference"/> into the current module.
/// Shorthand for <c>module.ImportReference(field)</c>.
/// </summary>
public FieldReference Import(FieldReference field) => module.ImportReference(field);
// ── Composite patterns ───────────────────────────────────────────
/// <summary>
/// Emits <c>typeof(T)</c>: <c>ldtoken type; call GetTypeFromHandle</c>.
/// </summary>
public ILBuilder EmitTypeof(TypeReference type, MethodReference getTypeFromHandle)
{
Emit(OpCodes.Ldtoken, type);
Emit(OpCodes.Call, getTypeFromHandle);
return this;
}
/// <summary>
/// Emits <c>typeof(T).GetTypeInfo().Assembly</c>.
/// </summary>
public ILBuilder EmitTypeofAssembly(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeInfo, MethodReference getAssembly)
{
EmitTypeof(type, getTypeFromHandle);
Emit(OpCodes.Call, getTypeInfo);
Emit(OpCodes.Callvirt, getAssembly);
return this;
}
/// <summary>
/// Emits <c>typeof(T).GetTypeInfo().Module</c>.
/// </summary>
public ILBuilder EmitTypeofModule(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeInfo, MethodReference getModule)
{
EmitTypeof(type, getTypeFromHandle);
Emit(OpCodes.Call, getTypeInfo);
Emit(OpCodes.Callvirt, getModule);
return this;
}
/// <summary>
/// Emits <c>typeof(T).TypeHandle</c>: <c>ldtoken type; call GetTypeFromHandle; callvirt get_TypeHandle</c>.
/// </summary>
public ILBuilder EmitTypeHandle(TypeReference type, MethodReference getTypeFromHandle, MethodReference getTypeHandle)
{
EmitTypeof(type, getTypeFromHandle);
Emit(OpCodes.Callvirt, getTypeHandle);
return this;
}
// ── Variable helpers ─────────────────────────────────────────────
/// <summary>
/// Adds a local variable to the method body and returns it.
/// Sets <see cref="MethodBody.InitLocals"/> to <see langword="true"/>.
/// </summary>
public VariableDefinition AddLocal(TypeReference type)
{
var variable = new VariableDefinition(type);
body.Variables.Add(variable);
body.InitLocals = true;
return variable;
}
// ── Label support ────────────────────────────────────────────────
/// <summary>
/// Creates a <see cref="Instruction.Create(OpCodes.Nop)"/> that can be used as a branch target,
/// without appending it to the body. Call <see cref="MarkLabel"/> to place it.
/// </summary>
public static Instruction DefineLabel() => Instruction.Create(OpCodes.Nop);
/// <summary>
/// Appends a previously defined label instruction to the body, marking the current position.
/// </summary>
public ILBuilder MarkLabel(Instruction label)
{
body.Instructions.Add(label);
return this;
}
}
@@ -2,17 +2,17 @@
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
//
// Copyright (c) 2010-2012 SharpDX - Alexandre Mutel
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -103,7 +103,7 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
}
/// <summary>
/// Creates the write method with the following signature:
/// Creates the write method with the following signature:
/// <code>
/// public static unsafe void* Write&lt;T&gt;(void* pDest, ref T data) where T : struct
/// </code>
@@ -114,7 +114,7 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
var gen = method.Body.GetILProcessor();
var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
@@ -123,34 +123,28 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// Push (0) pDest for memcpy
gen.Emit(OpCodes.Ldarg_0);
// fixed (void* pinnedData = &data[offset])
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Stloc_1);
// Push (1) pinnedData for memcpy
gen.Emit(OpCodes.Ldloc_1);
// totalSize = sizeof(T)
gen.Emit(OpCodes.Sizeof, paramT);
gen.Emit(OpCodes.Conv_I4);
gen.Emit(OpCodes.Stloc_0);
// Push (2) totalSize
gen.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_0)
// fixed (void* pinnedData = &data[offset])
.Emit(OpCodes.Ldarg_1)
.Emit(OpCodes.Stloc_1)
// Push (1) pinnedData for memcpy
.Emit(OpCodes.Ldloc_1)
// totalSize = sizeof(T)
.Emit(OpCodes.Sizeof, paramT)
.Emit(OpCodes.Conv_I4)
.Emit(OpCodes.Stloc_0)
// Push (2) totalSize
.Emit(OpCodes.Ldloc_0);
// Emit cpblk
EmitCpblk(method, gen);
EmitCpblk(il);
// Return pDest + totalSize
gen.Emit(OpCodes.Ldloc_0);
gen.Emit(OpCodes.Conv_I);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Add);
// Ret
gen.Emit(OpCodes.Ret);
il.Emit(OpCodes.Ldloc_0)
.Emit(OpCodes.Conv_I)
.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Add)
.Emit(OpCodes.Ret);
}
private void ReplacePinStatement(MethodDefinition method, ILProcessor ilProcessor, Instruction fixedtoPatch)
@@ -348,12 +342,9 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
var gen = method.Body.GetILProcessor();
gen.Emit(OpCodes.Ldarg_0);
// Ret
gen.Emit(OpCodes.Ret);
var il = new ILBuilder(method.Body, assembly.MainModule);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ret);
}
/// <summary>
@@ -368,12 +359,9 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
var gen = method.Body.GetILProcessor();
gen.Emit(OpCodes.Ldarg_0);
// Ret
gen.Emit(OpCodes.Ret);
var il = new ILBuilder(method.Body, assembly.MainModule);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ret);
}
private void ReplaceFixedArrayStatement(MethodDefinition method, ILProcessor ilProcessor, Instruction fixedtoPatch)
@@ -431,7 +419,7 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
var gen = method.Body.GetILProcessor();
var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
@@ -440,38 +428,32 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// Push (0) pDest for memcpy
gen.Emit(OpCodes.Ldarg_0);
// fixed (void* pinnedData = &data[offset])
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldarg_2);
gen.Emit(OpCodes.Ldelema, paramT);
gen.Emit(OpCodes.Stloc_1);
// Push (1) pinnedData for memcpy
gen.Emit(OpCodes.Ldloc_1);
// totalSize = sizeof(T) * count
gen.Emit(OpCodes.Sizeof, paramT);
gen.Emit(OpCodes.Conv_I4);
gen.Emit(OpCodes.Ldarg_3);
gen.Emit(OpCodes.Mul);
gen.Emit(OpCodes.Stloc_0);
// Push (2) totalSize
gen.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_0)
// fixed (void* pinnedData = &data[offset])
.Emit(OpCodes.Ldarg_1)
.Emit(OpCodes.Ldarg_2)
.Emit(OpCodes.Ldelema, paramT)
.Emit(OpCodes.Stloc_1)
// Push (1) pinnedData for memcpy
.Emit(OpCodes.Ldloc_1)
// totalSize = sizeof(T) * count
.Emit(OpCodes.Sizeof, paramT)
.Emit(OpCodes.Conv_I4)
.Emit(OpCodes.Ldarg_3)
.Emit(OpCodes.Mul)
.Emit(OpCodes.Stloc_0)
// Push (2) totalSize
.Emit(OpCodes.Ldloc_0);
// Emit cpblk
EmitCpblk(method, gen);
EmitCpblk(il);
// Return pDest + totalSize
gen.Emit(OpCodes.Ldloc_0);
gen.Emit(OpCodes.Conv_I);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Add);
// Ret
gen.Emit(OpCodes.Ret);
il.Emit(OpCodes.Ldloc_0)
.Emit(OpCodes.Conv_I)
.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Add)
.Emit(OpCodes.Ret);
}
/// <summary>
@@ -486,45 +468,38 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
var gen = method.Body.GetILProcessor();
var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
method.Body.Variables.Add(new VariableDefinition(intType));
// local(1) T*
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// fixed (void* pinnedData = &data[offset])
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Stloc_1);
// Push (0) pinnedData for memcpy
gen.Emit(OpCodes.Ldloc_1);
// Push (1) pSrc for memcpy
gen.Emit(OpCodes.Ldarg_0);
// totalSize = sizeof(T)
gen.Emit(OpCodes.Sizeof, paramT);
gen.Emit(OpCodes.Conv_I4);
gen.Emit(OpCodes.Stloc_0);
// Push (2) totalSize
gen.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_1)
.Emit(OpCodes.Stloc_1)
// Push (0) pinnedData for memcpy
.Emit(OpCodes.Ldloc_1)
// Push (1) pSrc for memcpy
.Emit(OpCodes.Ldarg_0)
// totalSize = sizeof(T)
.Emit(OpCodes.Sizeof, paramT)
.Emit(OpCodes.Conv_I4)
.Emit(OpCodes.Stloc_0)
// Push (2) totalSize
.Emit(OpCodes.Ldloc_0);
// Emit cpblk
EmitCpblk(method, gen);
EmitCpblk(il);
// Return pDest + totalSize
gen.Emit(OpCodes.Ldloc_0);
gen.Emit(OpCodes.Conv_I);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Add);
// Ret
gen.Emit(OpCodes.Ret);
il.Emit(OpCodes.Ldloc_0)
.Emit(OpCodes.Conv_I)
.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Add)
.Emit(OpCodes.Ret);
}
/// <summary>
@@ -539,12 +514,10 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
var gen = method.Body.GetILProcessor();
var paramT = method.GenericParameters[0];
var il = new ILBuilder(method.Body, assembly.MainModule);
// Push (1) pSrc for memcpy
gen.Emit(OpCodes.Cpobj);
il.Emit(OpCodes.Cpobj);
}
/// <summary>
@@ -559,7 +532,7 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Instructions.Clear();
method.Body.InitLocals = true;
var gen = method.Body.GetILProcessor();
var il = new ILBuilder(method.Body, assembly.MainModule);
var paramT = method.GenericParameters[0];
// Preparing locals
// local(0) int
@@ -568,39 +541,33 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
method.Body.Variables.Add(new VariableDefinition(new PinnedType(new ByReferenceType(paramT))));
// fixed (void* pinnedData = &data[offset])
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldarg_2);
gen.Emit(OpCodes.Ldelema, paramT);
gen.Emit(OpCodes.Stloc_1);
// Push (0) pinnedData for memcpy
gen.Emit(OpCodes.Ldloc_1);
// Push (1) pDest for memcpy
gen.Emit(OpCodes.Ldarg_0);
// totalSize = sizeof(T) * count
gen.Emit(OpCodes.Sizeof, paramT);
gen.Emit(OpCodes.Conv_I4);
gen.Emit(OpCodes.Ldarg_3);
gen.Emit(OpCodes.Conv_I4);
gen.Emit(OpCodes.Mul);
gen.Emit(OpCodes.Stloc_0);
// Push (2) totalSize
gen.Emit(OpCodes.Ldloc_0);
il.Emit(OpCodes.Ldarg_1)
.Emit(OpCodes.Ldarg_2)
.Emit(OpCodes.Ldelema, paramT)
.Emit(OpCodes.Stloc_1)
// Push (0) pinnedData for memcpy
.Emit(OpCodes.Ldloc_1)
// Push (1) pDest for memcpy
.Emit(OpCodes.Ldarg_0)
// totalSize = sizeof(T) * count
.Emit(OpCodes.Sizeof, paramT)
.Emit(OpCodes.Conv_I4)
.Emit(OpCodes.Ldarg_3)
.Emit(OpCodes.Conv_I4)
.Emit(OpCodes.Mul)
.Emit(OpCodes.Stloc_0)
// Push (2) totalSize
.Emit(OpCodes.Ldloc_0);
// Emit cpblk
EmitCpblk(method, gen);
EmitCpblk(il);
// Return pDest + totalSize
gen.Emit(OpCodes.Ldloc_0);
gen.Emit(OpCodes.Conv_I);
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Add);
// Ret
gen.Emit(OpCodes.Ret);
il.Emit(OpCodes.Ldloc_0)
.Emit(OpCodes.Conv_I)
.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Add)
.Emit(OpCodes.Ret);
}
/// <summary>
@@ -614,16 +581,13 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
{
methodCopyStruct.Body.Instructions.Clear();
var gen = methodCopyStruct.Body.GetILProcessor();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldarg_2);
gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1
gen.Emit(OpCodes.Cpblk);
// Ret
gen.Emit(OpCodes.Ret);
var il = new ILBuilder(methodCopyStruct.Body, assembly.MainModule);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldarg_1)
.Emit(OpCodes.Ldarg_2)
.Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1
.Emit(OpCodes.Cpblk)
.Emit(OpCodes.Ret);
}
/// <summary>
@@ -637,32 +601,27 @@ internal class InteropProcessor : IAssemblyDefinitionProcessor
{
methodSetStruct.Body.Instructions.Clear();
var gen = methodSetStruct.Body.GetILProcessor();
gen.Emit(OpCodes.Ldarg_0);
gen.Emit(OpCodes.Ldarg_1);
gen.Emit(OpCodes.Ldarg_2);
gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1
gen.Emit(OpCodes.Initblk);
// Ret
gen.Emit(OpCodes.Ret);
var il = new ILBuilder(methodSetStruct.Body, assembly.MainModule);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldarg_1)
.Emit(OpCodes.Ldarg_2)
.Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1
.Emit(OpCodes.Initblk)
.Emit(OpCodes.Ret);
}
/// <summary>
/// Emits the cpblk method, supporting x86 and x64 platform.
/// </summary>
/// <param name="method">The method.</param>
/// <param name="gen">The gen.</param>
private void EmitCpblk(MethodDefinition method, ILProcessor gen)
/// <param name="il">The IL builder.</param>
private static void EmitCpblk(ILBuilder il)
{
var cpblk = gen.Create(OpCodes.Cpblk);
//gen.Emit(OpCodes.Sizeof, voidPointerType);
//gen.Emit(OpCodes.Ldc_I4_8);
//gen.Emit(OpCodes.Bne_Un_S, cpblk);
gen.Emit(OpCodes.Unaligned, (byte)1); // unaligned to 1
gen.Append(cpblk);
var cpblk = Instruction.Create(OpCodes.Cpblk);
//il.Emit(OpCodes.Sizeof, voidPointerType);
//il.Emit(OpCodes.Ldc_I4_8);
//il.Emit(OpCodes.Bne_Un_S, cpblk);
il.Emit(OpCodes.Unaligned, (byte)1) // unaligned to 1
.Append(cpblk);
}
private List<string> GetSharpDXAttributes(MethodDefinition method)
@@ -22,7 +22,7 @@ internal class ProfileSerializerProcessor : ICecilSerializerProcessor
// For each profile, try to instantiate all types existing in default profile
foreach (var type in defaultProfile.SerializableTypes)
{
context.GenerateSerializer(type.Key, false, profile.Key);
context.ResolveSerializer(type.Key, false, profile.Key);
}
}
}
@@ -65,6 +65,21 @@ class ResolveGenericsVisitor : CecilTypeReferenceVisitor
return result;
}
/// <summary>
/// Creates a visitor that maps generic parameters from <paramref name="source"/> to <paramref name="target"/>.
/// Returns null if the source has no generic parameters.
/// </summary>
public static ResolveGenericsVisitor? FromMapping(IGenericParameterProvider source, IGenericParameterProvider target)
{
if (source.GenericParameters.Count == 0)
return null;
var mapping = new Dictionary<TypeReference, TypeReference>();
for (int i = 0; i < source.GenericParameters.Count; i++)
mapping[source.GenericParameters[i]] = target.GenericParameters[i];
return new ResolveGenericsVisitor(mapping);
}
public override TypeReference Visit(GenericParameter type)
{
TypeReference typeParent = type;
@@ -1,144 +1,38 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using System.Runtime.Versioning;
using Stride.Core.AssemblyProcessor.Serializers;
using System.Runtime.CompilerServices;
using System.Text;
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor;
internal class ComplexSerializerRegistry
/// <summary>
/// Static utility methods for analyzing serializable types and generating names.
/// </summary>
internal static class SerializationHelpers
{
private static readonly HashSet<string> forbiddenKeywords;
private static readonly HashSet<IMemberDefinition> ignoredMembers;
static ComplexSerializerRegistry()
{
ignoredMembers = [];
forbiddenKeywords =
[ "obj", "stream", "mode",
"abstract", "event", "new", "struct",
"as", "explicit", "null", "switch",
"base", "extern", "object", "this",
"bool", "false", "operator", "throw",
"break", "finally", "out", "true",
"byte", "fixed", "override", "try",
"case", "float", "params", "typeof",
"catch", "for", "private", "uint",
"char", "foreach", "protected", "ulong",
"checked", "goto", "public", "unchecked",
"class", "if", "readonly", "unsafe",
"const", "implicit", "ref", "ushort",
"continue", "in", "return", "using",
"decimal", "int", "sbyte", "virtual",
"default", "interface", "sealed", "volatile",
"delegate", "internal", "short", "void",
"do", "is", "sizeof", "while",
"double", "lock", "stackalloc",
"else", "long", "static",
"enum", "namespace", "string" ];
}
public List<TypeReference> ReferencedAssemblySerializerFactoryTypes { get; } = [];
public CecilSerializerContext Context { get; }
//private List<IDataSerializerFactory> serializerFactories = new List<IDataSerializerFactory>();
public string? TargetFramework { get; }
public string ClassName { get; }
public AssemblyDefinition Assembly { get; }
public List<ICecilSerializerDependency> SerializerDependencies { get; } = [];
public List<ICecilSerializerFactory> SerializerFactories { get; } = [];
public ComplexSerializerRegistry(PlatformType platform, AssemblyDefinition assembly, TextWriter log)
{
Assembly = assembly;
ClassName = Utilities.BuildValidClassName(assembly.Name.Name) + "SerializerFactory";
// Register referenced assemblies serializer factory, so that we can call them recursively
foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences)
{
try
{
var referencedAssembly = assembly.MainModule.AssemblyResolver.Resolve(referencedAssemblyName);
var assemblySerializerFactoryType = GetSerializerFactoryType(referencedAssembly);
if (assemblySerializerFactoryType != null)
ReferencedAssemblySerializerFactoryTypes.Add(assemblySerializerFactoryType);
}
catch (AssemblyResolutionException)
{
continue;
}
}
// Find target framework and replicate it for serializer assembly.
var targetFrameworkAttribute = assembly.CustomAttributes
.FirstOrDefault(x => x.AttributeType.FullName == typeof(TargetFrameworkAttribute).FullName);
if (targetFrameworkAttribute != null)
{
TargetFramework = "\"" + (string)targetFrameworkAttribute.ConstructorArguments[0].Value + "\"";
var frameworkDisplayNameField = targetFrameworkAttribute.Properties.FirstOrDefault(x => x.Name == "FrameworkDisplayName");
if (frameworkDisplayNameField.Name != null)
{
TargetFramework += ", FrameworkDisplayName=\"" + (string)frameworkDisplayNameField.Argument.Value + "\"";
}
}
// Prepare serializer processors
Context = new CecilSerializerContext(platform, assembly, log);
var processors = new List<ICecilSerializerProcessor>
{
// Import list of serializer registered by referenced assemblies
new ReferencedAssemblySerializerProcessor(),
// Generate serializers for types tagged as serializable
new CecilComplexClassSerializerProcessor(),
// Generate serializers for PropertyKey and ParameterKey
new PropertyKeySerializerProcessor(),
// Update Engine (with AnimationData<T>)
new UpdateEngineProcessor(),
// Profile serializers
new ProfileSerializerProcessor(),
// Data contract aliases
new DataContractAliasProcessor()
};
// Apply each processor
foreach (var processor in processors)
processor.ProcessSerializers(Context);
}
private static TypeDefinition? GetSerializerFactoryType(AssemblyDefinition referencedAssembly)
{
var assemblySerializerFactoryAttribute =
referencedAssembly.CustomAttributes.FirstOrDefault(
x =>
x.AttributeType.FullName ==
"Stride.Core.Serialization.AssemblySerializerFactoryAttribute");
// No serializer factory?
if (assemblySerializerFactoryAttribute == null)
return null;
var typeReference = (TypeReference)assemblySerializerFactoryAttribute.Fields.Single(x => x.Name == "Type").Argument.Value;
if (typeReference == null)
return null;
return typeReference.Resolve();
}
private static readonly HashSet<string> forbiddenKeywords =
[ "obj", "stream", "mode",
"abstract", "event", "new", "struct",
"as", "explicit", "null", "switch",
"base", "extern", "object", "this",
"bool", "false", "operator", "throw",
"break", "finally", "out", "true",
"byte", "fixed", "override", "try",
"case", "float", "params", "typeof",
"catch", "for", "private", "uint",
"char", "foreach", "protected", "ulong",
"checked", "goto", "public", "unchecked",
"class", "if", "readonly", "unsafe",
"const", "implicit", "ref", "ushort",
"continue", "in", "return", "using",
"decimal", "int", "sbyte", "virtual",
"default", "interface", "sealed", "volatile",
"delegate", "internal", "short", "void",
"do", "is", "sizeof", "while",
"double", "lock", "stackalloc",
"else", "long", "static",
"enum", "namespace", "string" ];
private static string TypeNameWithoutGenericEnding(TypeReference type)
{
@@ -175,88 +69,17 @@ internal class ComplexSerializerRegistry
return typeName;
}
public static string GetSerializerInstantiateMethodName(TypeReference serializerType, bool appendGenerics)
public static string CreateMemberVariableName(IMemberDefinition memberInfo)
{
return "Instantiate_" + SerializerTypeName(serializerType, appendGenerics, false);
var memberVariableName = char.ToLowerInvariant(memberInfo.Name[0]) + memberInfo.Name[1..];
if (forbiddenKeywords.Contains(memberVariableName))
memberVariableName += "_";
return memberVariableName;
}
/// <summary>
/// Generates the generic constraints in a code form.
/// </summary>
/// <param name="type">The type.</param>
/// <returns></returns>
public static string GenerateGenericConstraints(TypeReference type)
public static IEnumerable<SerializableItem> GetSerializableItems(TypeReference type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null, HashSet<IMemberDefinition>? ignoredMembers = null)
{
if (!type.HasGenericParameters)
return string.Empty;
var result = new StringBuilder();
foreach (var genericParameter in type.GenericParameters)
{
// If no constraints, skip it
var hasContraints = genericParameter.HasReferenceTypeConstraint || genericParameter.HasNotNullableValueTypeConstraint || genericParameter.Constraints.Count > 0 || genericParameter.HasDefaultConstructorConstraint;
if (!hasContraints)
{
continue;
}
bool hasFirstContraint = false;
result.AppendFormat(" where {0}: ", genericParameter.Name);
// Where class/struct constraint must be before any other constraint
if (genericParameter.HasReferenceTypeConstraint)
{
result.AppendFormat("class");
hasFirstContraint = true;
}
else if (genericParameter.HasNotNullableValueTypeConstraint)
{
result.AppendFormat("struct");
hasFirstContraint = true;
}
foreach (var genericParameterConstraint in genericParameter.Constraints)
{
// Skip value type constraint
if (genericParameterConstraint.ConstraintType.FullName != typeof(ValueType).FullName)
{
if (hasFirstContraint)
{
result.Append(", ");
}
result.AppendFormat("{0}", genericParameterConstraint.ConstraintType.ConvertCSharp());
result.AppendLine();
hasFirstContraint = true;
}
}
// New constraint must be last
if (!genericParameter.HasNotNullableValueTypeConstraint && genericParameter.HasDefaultConstructorConstraint)
{
if (hasFirstContraint)
{
result.Append(", ");
}
result.AppendFormat("new()");
result.AppendLine();
}
}
return result.ToString();
}
public static void IgnoreMember(IMemberDefinition memberInfo)
{
ignoredMembers.Add(memberInfo);
}
public static IEnumerable<SerializableItem> GetSerializableItems(TypeReference type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null)
{
foreach (var serializableItemOriginal in GetSerializableItems(type.Resolve(), serializeFields, flagsOverride))
foreach (var serializableItemOriginal in GetSerializableItems(type.Resolve(), serializeFields, flagsOverride, ignoredMembers))
{
var serializableItem = serializableItemOriginal;
@@ -270,7 +93,7 @@ internal class ComplexSerializerRegistry
}
}
public static IEnumerable<SerializableItem> GetSerializableItems(TypeDefinition type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null)
public static IEnumerable<SerializableItem> GetSerializableItems(TypeDefinition type, bool serializeFields, ComplexTypeSerializerFlags? flagsOverride = null, HashSet<IMemberDefinition>? ignoredMembers = null)
{
ComplexTypeSerializerFlags flags;
@@ -282,7 +105,7 @@ internal class ComplexSerializerRegistry
if (field.IsStatic)
continue;
if (ignoredMembers.Contains(field))
if (ignoredMembers?.Contains(field) == true)
continue;
if (field.IsPublic || (field.IsAssembly && field.CustomAttributes.Any(a => a.AttributeType.FullName == "Stride.Core.DataMemberAttribute")))
@@ -315,7 +138,7 @@ internal class ComplexSerializerRegistry
}
// Ignore blacklisted properties
if (ignoredMembers.Contains(property))
if (ignoredMembers?.Contains(property) == true)
continue;
properties.Add(property);
@@ -385,7 +208,7 @@ internal class ComplexSerializerRegistry
}
}
static bool IsAccessibleThroughAccessModifiers(PropertyDefinition property)
private static bool IsAccessibleThroughAccessModifiers(PropertyDefinition property)
{
var get = property.GetMethod;
var set = property.SetMethod;
@@ -445,14 +268,6 @@ internal class ComplexSerializerRegistry
&& !((type.MetadataType == MetadataType.ValueType || type.MetadataType == MetadataType.Class) && type.Resolve().IsValueType);
}
public static string CreateMemberVariableName(IMemberDefinition memberInfo)
{
var memberVariableName = char.ToLowerInvariant(memberInfo.Name[0]) + memberInfo.Name[1..];
if (forbiddenKeywords.Contains(memberVariableName))
memberVariableName += "_";
return memberVariableName;
}
public struct SerializableItem
{
public bool HasFixedAttribute;
File diff suppressed because it is too large Load Diff
@@ -6,14 +6,13 @@ using Mono.Cecil.Rocks;
namespace Stride.Core.AssemblyProcessor.Serializers;
class CecilComplexClassSerializerProcessor : ICecilSerializerProcessor
class CecilDataContractSerializerProcessor : ICecilSerializerProcessor
{
public void ProcessSerializers(CecilSerializerContext context)
{
foreach (var type in context.Assembly.MainModule.GetAllTypes().ToArray())
{
// Force generation of serializers (complex types, etc...)
// Check complex type definitions
// Discover [DataContract] types and resolve their serializers
ProcessType(context, type);
}
}
@@ -18,10 +18,39 @@ internal class CecilSerializerContext
SerializableTypesProfiles = [];
SerializableTypes = new ProfileInfo();
SerializableTypesProfiles.Add("Default", SerializableTypes);
ComplexTypes = [];
PendingSerializers = [];
IgnoredMembers = [];
this.log = log;
StrideCoreModule = assembly.GetStrideCoreModule();
// Discover referenced assemblies' serializer factories
foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences)
{
try
{
var referencedAssembly = assembly.MainModule.AssemblyResolver.Resolve(referencedAssemblyName);
var factoryType = GetSerializerFactoryType(referencedAssembly);
if (factoryType != null)
ReferencedAssemblySerializerFactoryTypes.Add(factoryType);
}
catch (AssemblyResolutionException)
{
}
}
// Run the processor pipeline
ICecilSerializerProcessor[] processors =
[
new ReferencedAssemblySerializerProcessor(),
new CecilDataContractSerializerProcessor(),
new PropertyKeySerializerProcessor(),
new UpdateEngineProcessor(),
new ProfileSerializerProcessor(),
new DataContractAliasProcessor(),
];
foreach (var processor in processors)
processor.ProcessSerializers(this);
}
public PlatformType Platform { get; }
@@ -29,21 +58,17 @@ internal class CecilSerializerContext
/// <summary>
/// Gets the assembly being processed.
/// </summary>
/// <value>
/// The assembly being processed.
/// </value>
public AssemblyDefinition Assembly { get; }
public ModuleDefinition StrideCoreModule { get; }
public List<TypeReference> ReferencedAssemblySerializerFactoryTypes { get; } = [];
public List<Tuple<string, TypeDefinition, bool>> DataContractAliases { get; } = [];
/// <summary>
/// Gets the list of serializable type grouped by profile.
/// </summary>
/// <value>
/// The serializable types profiles.
/// </value>
public Dictionary<string, ProfileInfo> SerializableTypesProfiles { get; }
/// <summary>
@@ -52,158 +77,154 @@ internal class CecilSerializerContext
public ProfileInfo SerializableTypes { get; }
/// <summary>
/// Gets the list of complex serializers to generate.
/// Gets the list of serializers pending code generation.
/// Populated during collection, consumed by the code generation phase.
/// </summary>
/// <value>
/// The list of complex serializers to generate.
/// </value>
public Dictionary<TypeDefinition, SerializableTypeInfo> ComplexTypes { get; }
public List<SerializerDescriptor> PendingSerializers { get; }
/// <summary>
/// Members that should be excluded from serialization (e.g. members without valid serializers).
/// </summary>
public HashSet<IMemberDefinition> IgnoredMembers { get; }
/// <summary>
/// Ensure the following type can be serialized. If not, try to register appropriate serializer.
/// This method can be recursive.
/// </summary>
/// <param name="type">The type.</param>
public SerializableTypeInfo GenerateSerializer(TypeReference type, bool force = true, string profile = "Default", bool generic = false)
public SerializableTypeInfo ResolveSerializer(TypeReference type, bool force = true, string profile = "Default", bool generic = false)
{
var serializableTypes = GetSerializableTypes(profile);
// Already handled?
if (serializableTypes.TryGetSerializableTypeInfo(type, generic, out var serializableTypeInfo))
return serializableTypeInfo;
// Try to get one without generic
if (generic && serializableTypes.TryGetSerializableTypeInfo(type, false, out serializableTypeInfo))
return serializableTypeInfo;
// TDOO: Array, List, Generic types, etc... (equivalent of previous serializer factories)
// Arrays
if (type is ArrayType arrayType)
{
// Only proceed if element type is serializable (and in Default profile, otherwise ElementType is enough)
if (GenerateSerializer(arrayType.ElementType, force, profile) != null)
{
if (profile == "Default")
{
var arraySerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1");
var serializerType = new GenericInstanceType(arraySerializerType);
serializerType.GenericArguments.Add(arrayType.ElementType);
AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true), profile);
return serializableTypeInfo;
}
else
{
// Fallback to default
return GenerateSerializer(type, force, "Default");
}
}
return ResolveArraySerializer(arrayType, force, profile);
return null;
}
// Try to match with existing generic serializer (for List, Dictionary, etc...)
// Generic instances (List<T>, Dictionary<K,V>, etc.)
if (type is GenericInstanceType genericInstanceType)
{
var elementType = genericInstanceType.ElementType;
SerializableTypeInfo elementSerializableTypeInfo;
if ((elementSerializableTypeInfo = GenerateSerializer(elementType, false, profile, true)) != null)
{
switch (elementSerializableTypeInfo.Mode)
{
case DataSerializerGenericMode.Type:
{
var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType);
serializerType.GenericArguments.Add(type);
AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile);
break;
}
case DataSerializerGenericMode.TypeAndGenericArguments:
{
var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType);
serializerType.GenericArguments.Add(type);
foreach (var genericArgument in genericInstanceType.GenericArguments)
{
// Generate serializer for each generic argument
//GenerateSerializer(genericArgument);
serializerType.GenericArguments.Add(genericArgument);
}
AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile);
break;
}
case DataSerializerGenericMode.GenericArguments:
{
var serializerType = new GenericInstanceType(elementSerializableTypeInfo.SerializerType);
foreach (var genericArgument in genericInstanceType.GenericArguments)
{
// Generate serializer for each generic argument
//GenerateSerializer(genericArgument);
serializerType.GenericArguments.Add(genericArgument);
}
AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(serializerType, true) { ComplexSerializer = elementSerializableTypeInfo.ComplexSerializer }, profile);
break;
}
default:
throw new NotImplementedException();
}
if (elementSerializableTypeInfo.ComplexSerializer)
{
ProcessComplexSerializerMembers(type, serializableTypeInfo);
}
serializableTypeInfo = ResolveGenericSerializer(genericInstanceType, profile);
if (serializableTypeInfo != null)
return serializableTypeInfo;
}
}
// Check complex type definitions
if (profile == "Default" && (serializableTypeInfo = FindSerializerInfo(type, generic)) != null)
// Check type definitions for serializer info (only in Default profile)
if (profile == "Default")
{
return serializableTypeInfo;
serializableTypeInfo = FindSerializerInfo(type, generic);
if (serializableTypeInfo != null)
return serializableTypeInfo;
}
// Fallback to default
// Non-Default profiles fall back to Default
if (profile != "Default")
return GenerateSerializer(type, force, "Default", generic);
return ResolveSerializer(type, force, "Default", generic);
// Part after that is only if a serializer is absolutely necessary. This is skipped when scanning normal assemblies type that might have nothing to do with serialization.
// Past this point, only proceed if a serializer is absolutely necessary.
// This is skipped when scanning normal assembly types that might have nothing to do with serialization.
if (!force)
return null;
// Non instantiable type? (object, interfaces, abstract classes)
// Non-instantiable types (object, interfaces, abstract classes)
// Serializer can be null since they will be inherited anyway (handled through MemberSerializer)
var resolvedType = type.Resolve();
if (resolvedType.IsAbstract || resolvedType.IsInterface || resolvedType.FullName == typeof(object).FullName)
{
AddSerializableType(type, serializableTypeInfo = new SerializableTypeInfo(null, true), profile);
serializableTypeInfo = new SerializableTypeInfo(null, true);
AddSerializableType(type, serializableTypeInfo, profile);
return serializableTypeInfo;
}
return null;
}
private void ProcessComplexSerializerMembers(TypeReference type, SerializableTypeInfo serializableTypeInfo, string profile = "Default")
private SerializableTypeInfo ResolveArraySerializer(ArrayType arrayType, bool force, string profile)
{
// Process base type (for complex serializers)
// Check if we have any serializable closed base type and collect it to pass it over to GenerateSerializerCode later,
// who'll ensure the generated serializer for this type calls into its base types' serializer
// Only proceed if element type is serializable
if (ResolveSerializer(arrayType.ElementType, force, profile) == null)
return null;
// Non-Default profiles fall back to Default for array serializer registration
if (profile != "Default")
return ResolveSerializer(arrayType, force, "Default");
var arraySerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.ArraySerializer`1");
var serializerType = new GenericInstanceType(arraySerializerType);
serializerType.GenericArguments.Add(arrayType.ElementType);
var info = new SerializableTypeInfo(serializerType, true);
AddSerializableType(arrayType, info, profile);
return info;
}
private SerializableTypeInfo ResolveGenericSerializer(GenericInstanceType type, string profile)
{
// Try to match with existing generic serializer (for List, Dictionary, etc.)
var elementInfo = ResolveSerializer(type.ElementType, false, profile, true);
if (elementInfo == null)
return null;
var serializerType = InstantiateSerializerType(elementInfo.SerializerType, elementInfo.Mode, type, type.GenericArguments);
var info = new SerializableTypeInfo(serializerType, true) { IsGeneratedSerializer = elementInfo.IsGeneratedSerializer };
AddSerializableType(type, info, profile);
if (elementInfo.IsGeneratedSerializer)
CollectSerializerDependencies(type, info);
return info;
}
/// <summary>
/// Constructs a concrete serializer type from an open generic serializer definition
/// by adding type arguments according to the <see cref="DataSerializerGenericMode"/>.
/// </summary>
private static GenericInstanceType InstantiateSerializerType(
TypeReference openSerializerType,
DataSerializerGenericMode mode,
TypeReference dataType,
IEnumerable<TypeReference> genericArguments)
{
var serializerType = new GenericInstanceType(openSerializerType);
// Add the data type itself as first arg for Type/TypeAndGenericArguments modes
if (mode is DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments)
serializerType.GenericArguments.Add(dataType);
// Add the data type's generic arguments for GenericArguments/TypeAndGenericArguments modes
if (mode is DataSerializerGenericMode.GenericArguments or DataSerializerGenericMode.TypeAndGenericArguments)
{
foreach (var arg in genericArguments)
serializerType.GenericArguments.Add(arg);
}
return serializerType;
}
private void CollectSerializerDependencies(TypeReference type, SerializableTypeInfo serializableTypeInfo, string profile = "Default", SerializerDescriptor? descriptor = null)
{
// Find the nearest serializable base type so the generated serializer can chain to it
for (var baseType = type; (baseType = ResolveGenericsVisitor.Process(baseType, baseType.Resolve().BaseType)) != null;)
{
if (baseType.ContainsGenericParameter())
continue; // ResolveGenericsVisitor failed, the type it returned is not closed, we can't serialize it
var parentSerializableTypeInfo = GenerateSerializer(baseType, false, profile);
if (parentSerializableTypeInfo?.SerializerType != null)
var parentSerializableTypeInfo = ResolveSerializer(baseType, false, profile);
if (parentSerializableTypeInfo?.SerializerType is not null)
{
serializableTypeInfo.ComplexSerializerProcessParentType = baseType;
if (descriptor is not null)
descriptor.SerializedParentType = baseType;
break;
}
}
// Process members
foreach (var serializableItem in ComplexSerializerRegistry.GetSerializableItems(type, true))
// Resolve serializers for all members, ignoring those without valid serializers
foreach (var serializableItem in SerializationHelpers.GetSerializableItems(type, true, ignoredMembers: IgnoredMembers))
{
// Check that all closed types have a proper serializer
if (serializableItem.Attributes.Any(x => x.AttributeType.FullName == "Stride.Core.DataMemberCustomSerializerAttribute")
@@ -217,9 +238,9 @@ internal class CecilSerializerContext
try
{
if (GenerateSerializer(serializableItem.Type, profile: profile) == null)
if (ResolveSerializer(serializableItem.Type, profile: profile) == null)
{
ComplexSerializerRegistry.IgnoreMember(serializableItem.MemberInfo);
IgnoredMembers.Add(serializableItem.MemberInfo);
if (!isInterface)
{
log.Write(
@@ -232,15 +253,15 @@ internal class CecilSerializerContext
throw new InvalidOperationException($"Could not process serialization for member {serializableItem.MemberInfo}", e);
}
}
// Cache final serializable items (after ignored members have been updated)
if (descriptor is not null)
descriptor.SerializableItems = SerializationHelpers.GetSerializableItems(type, true, ignoredMembers: IgnoredMembers).ToArray();
}
/// <summary>
/// Finds the serializer information.
/// Finds the serializer information by inspecting the type's attributes and inheritance chain.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="generic">If set to true, when using <see cref="DataSerializerGenericMode.Type"/>, it will returns the generic version instead of actual type one.</param>
/// <returns></returns>
/// <exception cref="System.InvalidOperationException">Not sure how to process this inherited serializer</exception>
internal SerializableTypeInfo FindSerializerInfo(TypeReference type, bool generic)
{
if (type == null || type.FullName == typeof(object).FullName || type.FullName == typeof(ValueType).FullName || type.IsGenericParameter)
@@ -248,199 +269,138 @@ internal class CecilSerializerContext
var resolvedType = type.Resolve();
// Nested type
if (resolvedType.IsNested)
{
// Check public/private flags
if (!resolvedType.IsNestedPublic && !resolvedType.IsNestedAssembly)
return null;
}
// Nested type — must be publicly accessible
if (resolvedType.IsNested && !resolvedType.IsNestedPublic && !resolvedType.IsNestedAssembly)
return null;
// Enums
if (resolvedType.IsEnum)
{
// Enum
// Let's generate a EnumSerializer
var enumSerializerType = StrideCoreModule.GetTypeResolved("Stride.Core.Serialization.Serializers.EnumSerializer`1");
var serializerType = new GenericInstanceType(enumSerializerType);
serializerType.GenericArguments.Add(type);
var serializableTypeInfo = new SerializableTypeInfo(serializerType, true, DataSerializerGenericMode.None);
AddSerializableType(type, serializableTypeInfo);
return serializableTypeInfo;
var info = new SerializableTypeInfo(serializerType, true, DataSerializerGenericMode.None);
AddSerializableType(type, info);
return info;
}
// 1. Check if there is a Serializable attribute
// Note: Not anymore since we don't want all system types to have unknown complex serializers.
//if (((resolvedType.Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable) || resolvedType.CustomAttributes.Any(x => x.AttributeType.FullName == typeof(SerializableAttribute).FullName))
//{
// serializerInfo.Serializable = true;
// serializerInfo.ComplexSerializer = true;
// serializerInfo.ComplexSerializerName = SerializerTypeName(resolvedType);
// return serializerInfo;
//}
// 2.1. Check if there is DataSerializerAttribute on this type (if yes, it is serializable, but not a "complex type")
var dataSerializerAttribute =
resolvedType.CustomAttributes.FirstOrDefault(
x => x.AttributeType.FullName == "Stride.Core.Serialization.DataSerializerAttribute");
// [DataSerializer] attribute — explicit serializer assignment
var dataSerializerAttribute = resolvedType.CustomAttributes
.FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.Serialization.DataSerializerAttribute");
if (dataSerializerAttribute != null)
return ProcessDataSerializerAttribute(type, dataSerializerAttribute, generic);
// [DataContract] attribute — collect generated serializer
var dataContractAttribute = resolvedType.CustomAttributes
.FirstOrDefault(x => x.AttributeType.FullName == "Stride.Core.DataContractAttribute");
if (dataContractAttribute != null)
{
var modeField = dataSerializerAttribute.Fields.FirstOrDefault(x => x.Name == "Mode");
var mode = (modeField.Name != null) ? (DataSerializerGenericMode)modeField.Argument.Value : DataSerializerGenericMode.None;
var dataSerializerType = ((TypeReference)dataSerializerAttribute.ConstructorArguments[0].Value);
// Reading from custom arguments doesn't have its ValueType properly set
dataSerializerType = dataSerializerType.FixupValueType();
if (mode == DataSerializerGenericMode.Type || (mode == DataSerializerGenericMode.TypeAndGenericArguments && type is GenericInstanceType))
{
var genericSerializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = true };
AddSerializableType(type, genericSerializableTypeInfo);
var actualSerializerType = new GenericInstanceType(dataSerializerType);
// Add Type as generic arguments
actualSerializerType.GenericArguments.Add(type);
// If necessary, add generic arguments too
if (mode == DataSerializerGenericMode.TypeAndGenericArguments)
{
foreach (var genericArgument in ((GenericInstanceType)type).GenericArguments)
{
actualSerializerType.GenericArguments.Add(genericArgument);
}
}
// Special case for GenericMode == DataSerializerGenericMode.Type: we store actual serializer instantiation in SerializerType (alongside the generic version in GenericSerializerType).
var serializableTypeInfo = new SerializableTypeInfo(actualSerializerType, true);
AddSerializableType(type, serializableTypeInfo);
if (!generic)
return serializableTypeInfo;
return genericSerializableTypeInfo;
}
else
{
var serializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = false };
AddSerializableType(type, serializableTypeInfo);
return serializableTypeInfo;
}
}
// 2.2. Check if SerializableExtendedAttribute is set on this class, or any of its base class with ApplyHierarchy
var serializableExtendedAttribute =
resolvedType.CustomAttributes.FirstOrDefault(
x => x.AttributeType.FullName == "Stride.Core.DataContractAttribute");
if (dataSerializerAttribute == null && serializableExtendedAttribute != null)
{
// CHeck if ApplyHierarchy is active, otherwise it needs to be the exact type.
var inherited = serializableExtendedAttribute.Properties.Where(x => x.Name == "Inherited")
var inherited = dataContractAttribute.Properties
.Where(x => x.Name == "Inherited")
.Select(x => (bool)x.Argument.Value)
.FirstOrDefault();
var serializableTypeInfo = CreateComplexSerializer(type);
serializableTypeInfo.Inherited = inherited;
// Process members
ProcessComplexSerializerMembers(type, serializableTypeInfo);
return serializableTypeInfo;
var (info, descriptor) = CollectSerializer(type);
info.Inherited = inherited;
CollectSerializerDependencies(type, info, descriptor: descriptor);
return info;
}
// Check if parent type contains Inherited attribute
var parentType = ResolveGenericsVisitor.Process(type, type.Resolve().BaseType);
if (parentType != null)
{
// Generate serializer for parent type
var parentSerializableInfoType = GenerateSerializer(parentType.Resolve(), false, generic: true);
// If Inherited flag is on, we also generate a serializer for this type
if (parentSerializableInfoType?.Inherited == true)
{
if (parentSerializableInfoType.ComplexSerializer)
{
var serializableTypeInfo = CreateComplexSerializer(type);
serializableTypeInfo.Inherited = true;
// Process members
ProcessComplexSerializerMembers(type, serializableTypeInfo);
return serializableTypeInfo;
}
else if (parentSerializableInfoType.Mode == DataSerializerGenericMode.Type || parentSerializableInfoType.Mode == DataSerializerGenericMode.TypeAndGenericArguments)
{
// Register generic version
var genericSerializableTypeInfo = new SerializableTypeInfo(parentSerializableInfoType.SerializerType, true, parentSerializableInfoType.Mode);
AddSerializableType(type, genericSerializableTypeInfo);
if (!type.HasGenericParameters)
{
var actualSerializerType = new GenericInstanceType(parentSerializableInfoType.SerializerType);
// Add Type as generic arguments
actualSerializerType.GenericArguments.Add(type);
// If necessary, add generic arguments too
if (parentSerializableInfoType.Mode == DataSerializerGenericMode.TypeAndGenericArguments)
{
foreach (var genericArgument in ((GenericInstanceType)parentType).GenericArguments)
{
actualSerializerType.GenericArguments.Add(genericArgument);
}
}
// Register actual type
var serializableTypeInfo = new SerializableTypeInfo(actualSerializerType, true);
AddSerializableType(type, serializableTypeInfo);
if (!generic)
return serializableTypeInfo;
}
return genericSerializableTypeInfo;
}
else
{
throw new InvalidOperationException("Not sure how to process this inherited serializer");
}
}
}
return null;
// Check if parent type has Inherited attribute
return FindInheritedSerializerInfo(type, generic);
}
private SerializableTypeInfo CreateComplexSerializer(TypeReference type)
private SerializableTypeInfo ProcessDataSerializerAttribute(TypeReference type, CustomAttribute attribute, bool generic)
{
var modeField = attribute.Fields.FirstOrDefault(x => x.Name == "Mode");
var mode = (modeField.Name != null) ? (DataSerializerGenericMode)modeField.Argument.Value : DataSerializerGenericMode.None;
var dataSerializerType = ((TypeReference)attribute.ConstructorArguments[0].Value).FixupValueType();
if (mode is not (DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments)
|| (mode == DataSerializerGenericMode.TypeAndGenericArguments && type is not GenericInstanceType))
{
// Simple non-generic or GenericArguments mode
var info = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = false };
AddSerializableType(type, info);
return info;
}
// Type or TypeAndGenericArguments mode — register both generic and concrete versions
var genericInfo = new SerializableTypeInfo(dataSerializerType, true, mode) { Inherited = true };
AddSerializableType(type, genericInfo);
var genericArguments = type is GenericInstanceType git ? git.GenericArguments : [];
var actualSerializerType = InstantiateSerializerType(dataSerializerType, mode, type, genericArguments);
var concreteInfo = new SerializableTypeInfo(actualSerializerType, true);
AddSerializableType(type, concreteInfo);
return generic ? genericInfo : concreteInfo;
}
private SerializableTypeInfo FindInheritedSerializerInfo(TypeReference type, bool generic)
{
var parentType = ResolveGenericsVisitor.Process(type, type.Resolve().BaseType);
if (parentType == null)
return null;
// Generate serializer for parent type
var parentInfo = ResolveSerializer(parentType.Resolve(), false, generic: true);
if (parentInfo?.Inherited != true)
return null;
// Parent has a generated serializer — collect one for this type too
if (parentInfo.IsGeneratedSerializer)
{
var (info, descriptor) = CollectSerializer(type);
info.Inherited = true;
CollectSerializerDependencies(type, info, descriptor: descriptor);
return info;
}
// Parent has a Type/TypeAndGenericArguments mode serializer — inherit it
if (parentInfo.Mode is DataSerializerGenericMode.Type or DataSerializerGenericMode.TypeAndGenericArguments)
{
// Register generic version
var genericInfo = new SerializableTypeInfo(parentInfo.SerializerType, true, parentInfo.Mode);
AddSerializableType(type, genericInfo);
if (!type.HasGenericParameters)
{
var genericArguments = parentType is GenericInstanceType git ? git.GenericArguments : [];
var actualSerializerType = InstantiateSerializerType(parentInfo.SerializerType, parentInfo.Mode, type, genericArguments);
var concreteInfo = new SerializableTypeInfo(actualSerializerType, true);
AddSerializableType(type, concreteInfo);
if (!generic)
return concreteInfo;
}
return genericInfo;
}
throw new InvalidOperationException("Not sure how to process this inherited serializer");
}
private (SerializableTypeInfo Info, SerializerDescriptor? Descriptor) CollectSerializer(TypeReference type)
{
var isLocal = type.Resolve().Module.Assembly == Assembly;
// Create a fake TypeReference (even though it doesn't really exist yet, but at least ConvertCSharp to get its name will work).
TypeReference dataSerializerType;
var className = ComplexSerializerRegistry.SerializerTypeName(type, false, true);
// Create a forward TypeReference for the serializer (the actual TypeDefinition is created later during code generation).
var className = SerializationHelpers.SerializerTypeName(type, false, true);
if (type.HasGenericParameters)
className += "`" + type.GenericParameters.Count;
if (isLocal && type is TypeDefinition)
{
dataSerializerType = new TypeDefinition("Stride.Core.DataSerializers", className,
TypeAttributes.AnsiClass | TypeAttributes.AutoClass | TypeAttributes.Sealed |
TypeAttributes.BeforeFieldInit |
(type.HasGenericParameters ? TypeAttributes.Public : TypeAttributes.NotPublic));
// TODO: Only if not using Roslyn
Assembly.MainModule.Types.Add((TypeDefinition)dataSerializerType);
}
else
{
dataSerializerType = new TypeReference("Stride.Core.DataSerializers", className, type.Module, type.Scope);
}
var dataSerializerType = new TypeReference("Stride.Core.DataSerializers", className, type.Module, isLocal ? Assembly.MainModule : type.Scope);
var mode = DataSerializerGenericMode.None;
if (type.HasGenericParameters)
{
mode = DataSerializerGenericMode.GenericArguments;
// Clone generic parameters
// Clone generic parameters onto the forward reference
foreach (var genericParameter in type.GenericParameters)
{
var newGenericParameter = new GenericParameter(genericParameter.Name, dataSerializerType)
@@ -448,7 +408,6 @@ internal class CecilSerializerContext
Attributes = genericParameter.Attributes
};
// Clone type constraints (others will be in Attributes)
foreach (var constraint in genericParameter.Constraints)
newGenericParameter.Constraints.Add(constraint);
@@ -456,31 +415,32 @@ internal class CecilSerializerContext
}
}
if (dataSerializerType is TypeDefinition dataSerializerTypeDefinition)
{
// Setup base class
var resolvedType = type.Resolve();
var useClassDataSerializer = resolvedType.IsClass && !resolvedType.IsValueType && !resolvedType.IsAbstract && !resolvedType.IsInterface && resolvedType.GetEmptyConstructor() != null;
var classDataSerializerType = Assembly.GetStrideCoreModule().GetType(useClassDataSerializer ? "Stride.Core.Serialization.ClassDataSerializer`1" : "Stride.Core.Serialization.DataSerializer`1");
var parentType = Assembly.MainModule.ImportReference(classDataSerializerType).MakeGenericType(type.MakeGenericType(dataSerializerType.GenericParameters.ToArray<TypeReference>()));
//parentType = ResolveGenericsVisitor.Process(serializerType, type.BaseType);
dataSerializerTypeDefinition.BaseType = parentType;
}
var serializableTypeInfo = new SerializableTypeInfo(dataSerializerType, true, mode)
{
Local = type.Resolve().Module.Assembly == Assembly
Local = isLocal
};
AddSerializableType(type, serializableTypeInfo);
SerializerDescriptor? descriptor = null;
if (isLocal && type is TypeDefinition definition)
{
ComplexTypes.Add(definition, serializableTypeInfo);
var resolvedType = type.Resolve();
var useClassDataSerializer = resolvedType.IsClass && !resolvedType.IsValueType && !resolvedType.IsAbstract && !resolvedType.IsInterface && resolvedType.GetEmptyConstructor() != null;
descriptor = new SerializerDescriptor
{
DataType = definition,
SerializerClassName = className,
IsPublic = type.HasGenericParameters,
UseClassDataSerializer = useClassDataSerializer,
SerializableTypeInfo = serializableTypeInfo,
};
PendingSerializers.Add(descriptor);
}
serializableTypeInfo.ComplexSerializer = true;
serializableTypeInfo.IsGeneratedSerializer = true;
return serializableTypeInfo;
return (serializableTypeInfo, descriptor);
}
public void AddSerializableType(TypeReference dataType, SerializableTypeInfo serializableTypeInfo, string profile = "Default")
@@ -498,8 +458,7 @@ internal class CecilSerializerContext
if (profileInfo.TryGetSerializableTypeInfo(dataType, serializableTypeInfo.Mode != DataSerializerGenericMode.None, out var currentValue))
{
// TODO: Doesn't work in some generic case
if (//currentValue.SerializerType.ConvertCSharp() != serializableTypeInfo.SerializerType.ConvertCSharp() ||
currentValue.Mode != serializableTypeInfo.Mode)
if (currentValue.Mode != serializableTypeInfo.Mode)
throw new InvalidOperationException(string.Format("Incompatible serializer found for same type in different assemblies for {0}", dataType.ConvertCSharp()));
return;
}
@@ -516,37 +475,46 @@ internal class CecilSerializerContext
profileInfo.AddSerializableTypeInfo(dataType, serializableTypeInfo);
// Scan and add dependencies (stored in EnumerateGenericInstantiations() functions).
if (serializableTypeInfo.Local && serializableTypeInfo.SerializerType != null)
{
var resolvedSerializerType = serializableTypeInfo.SerializerType.Resolve();
if (resolvedSerializerType != null)
{
var enumerateGenericInstantiationsMethod = resolvedSerializerType.Methods.FirstOrDefault(x => x.Name == "EnumerateGenericInstantiations");
if (enumerateGenericInstantiationsMethod != null)
{
// Detect all ldtoken (attributes would have been better, but unfortunately C# doesn't allow generics in attributes)
foreach (var inst in enumerateGenericInstantiationsMethod.Body.Instructions)
{
if (inst.OpCode.Code == Code.Ldtoken)
{
var type = (TypeReference)inst.Operand;
// Scan and add dependencies (stored in EnumerateGenericInstantiations() methods)
ScanSerializerDependencies(dataType, serializableTypeInfo);
}
// Try to "close" generics type with serializer type as a context
var dependentType = ResolveGenericsVisitor.Process(serializableTypeInfo.SerializerType, type);
if (!dependentType.ContainsGenericParameter())
{
// Import type so that it becomes local to the assembly
// (otherwise SerializableTypeInfo.Local will be false and it won't be instantiated)
var importedType = Assembly.MainModule.ImportReference(dependentType);
if (GenerateSerializer(importedType) == null)
{
throw new InvalidOperationException(string.Format("Could not find serializer for generic dependent type {0} when processing {1}", dependentType, dataType));
}
}
}
}
}
/// <summary>
/// Scans a serializer type's EnumerateGenericInstantiations method for ldtoken instructions
/// to discover dependent types that also need serializers.
/// </summary>
private void ScanSerializerDependencies(TypeReference dataType, SerializableTypeInfo serializableTypeInfo)
{
if (!serializableTypeInfo.Local || serializableTypeInfo.SerializerType == null)
return;
var resolvedSerializerType = serializableTypeInfo.SerializerType.Resolve();
if (resolvedSerializerType == null)
return;
var enumerateMethod = resolvedSerializerType.Methods.FirstOrDefault(x => x.Name == "EnumerateGenericInstantiations");
if (enumerateMethod == null)
return;
// Detect all ldtoken (attributes would have been better, but unfortunately C# doesn't allow generics in attributes)
foreach (var inst in enumerateMethod.Body.Instructions)
{
if (inst.OpCode.Code != Code.Ldtoken)
continue;
var type = (TypeReference)inst.Operand;
// Try to "close" generics type with serializer type as a context
var dependentType = ResolveGenericsVisitor.Process(serializableTypeInfo.SerializerType, type);
if (dependentType.ContainsGenericParameter())
continue;
// Import type so that it becomes local to the assembly
// (otherwise SerializableTypeInfo.Local will be false and it won't be instantiated)
var importedType = Assembly.MainModule.ImportReference(dependentType);
if (ResolveSerializer(importedType) == null)
{
throw new InvalidOperationException(string.Format("Could not find serializer for generic dependent type {0} when processing {1}", dependentType, dataType));
}
}
}
@@ -578,14 +546,9 @@ internal class CecilSerializerContext
public bool Inherited;
/// <summary>
/// True if it's a complex serializer.
/// True if the serializer is auto-generated (for [DataContract] types).
/// </summary>
public bool ComplexSerializer;
/// <summary>
/// Not null if it's a complex serializer and its base class should be serialized too.
/// </summary>
public TypeReference? ComplexSerializerProcessParentType;
public bool IsGeneratedSerializer;
public SerializableTypeInfo(TypeReference serializerType, bool local, DataSerializerGenericMode mode = DataSerializerGenericMode.None)
{
@@ -595,6 +558,22 @@ internal class CecilSerializerContext
}
}
private static TypeDefinition? GetSerializerFactoryType(AssemblyDefinition referencedAssembly)
{
var assemblySerializerFactoryAttribute =
referencedAssembly.CustomAttributes.FirstOrDefault(
x => x.AttributeType.FullName == "Stride.Core.Serialization.AssemblySerializerFactoryAttribute");
if (assemblySerializerFactoryAttribute == null)
return null;
var typeReference = (TypeReference)assemblySerializerFactoryAttribute.Fields.Single(x => x.Name == "Type").Argument.Value;
if (typeReference == null)
return null;
return typeReference.Resolve();
}
public class ProfileInfo
{
/// <summary>
@@ -18,7 +18,7 @@ class PropertyKeySerializerProcessor : ICecilSerializerProcessor
if (!member.IsStatic || member.IsPrivate)
continue;
if (ComplexSerializerRegistry.IsMemberIgnored(member.CustomAttributes, ComplexTypeSerializerFlags.SerializePublicFields, DataMemberMode.Default))
if (SerializationHelpers.IsMemberIgnored(member.CustomAttributes, ComplexTypeSerializerFlags.SerializePublicFields, DataMemberMode.Default))
continue;
if (member.FieldType.Name == "PropertyKey`1"
@@ -27,12 +27,12 @@ class PropertyKeySerializerProcessor : ICecilSerializerProcessor
|| member.FieldType.Name == "ObjectParameterKey`1"
|| member.FieldType.Name == "PermutationParameterKey`1")
{
context.GenerateSerializer(member.FieldType);
context.ResolveSerializer(member.FieldType);
var genericType = (GenericInstanceType)member.FieldType;
// Also generate serializer for embedded type
context.GenerateSerializer(genericType.GenericArguments[0]);
context.ResolveSerializer(genericType.GenericArguments[0]);
}
}
}
@@ -25,7 +25,7 @@ class ReferencedAssemblySerializerProcessor : ICecilSerializerProcessor
if (!processedAssemblies.Add(assembly))
return;
// TODO: Add a flag for ComplexSerializer and transmit it properly (it needs different kind of analysis)
// TODO: Add a flag for generated serializers and transmit it properly (it needs different kind of analysis)
// Let's recurse over referenced assemblies
foreach (var referencedAssemblyName in assembly.MainModule.AssemblyReferences.ToArray())
@@ -80,8 +80,8 @@ class ReferencedAssemblySerializerProcessor : ICecilSerializerProcessor
if (dataSerializerType == null)
{
// TODO: We should avoid calling GenerateSerializer now just to have the dataSerializerType (we should do so only in a second step)
serializableTypeInfo = context.GenerateSerializer(dataType, profile: profile);
// TODO: We should avoid calling ResolveSerializer now just to have the dataSerializerType (we should do so only in a second step)
serializableTypeInfo = context.ResolveSerializer(dataType, profile: profile);
if (serializableTypeInfo == null)
throw new InvalidOperationException(string.Format("Can't find serializer for type {0}", dataType));
serializableTypeInfo.Local = local;
@@ -91,7 +91,7 @@ class ReferencedAssemblySerializerProcessor : ICecilSerializerProcessor
else
{
// Add it to list of serializable types
serializableTypeInfo = new CecilSerializerContext.SerializableTypeInfo(dataSerializerType, local, mode) { ExistingLocal = local, Inherited = inherited, ComplexSerializer = complexSerializer };
serializableTypeInfo = new CecilSerializerContext.SerializableTypeInfo(dataSerializerType, local, mode) { ExistingLocal = local, Inherited = inherited, IsGeneratedSerializer = complexSerializer };
context.AddSerializableType(dataType, serializableTypeInfo, profile);
}
}
@@ -0,0 +1,51 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net) and Silicon Studio Corp. (https://www.siliconstudio.co.jp)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Mono.Cecil;
namespace Stride.Core.AssemblyProcessor.Serializers;
/// <summary>
/// Describes a serializer to be generated. Collected during the discovery phase
/// and consumed by the code generation phase to create the actual <see cref="TypeDefinition"/>.
/// </summary>
internal class SerializerDescriptor
{
/// <summary>
/// The data type being serialized.
/// </summary>
public required TypeDefinition DataType { get; init; }
/// <summary>
/// The class name for the generated serializer (e.g. "MyTypeSerializer").
/// </summary>
public required string SerializerClassName { get; init; }
/// <summary>
/// Whether the serializer type should be public (true for generic types).
/// </summary>
public required bool IsPublic { get; init; }
/// <summary>
/// Whether to use ClassDataSerializer{T} (true) or DataSerializer{T} (false) as the base class.
/// </summary>
public required bool UseClassDataSerializer { get; init; }
/// <summary>
/// The serializable type info created during collection, which holds the serializer type reference
/// and metadata used by the registration/factory phase.
/// </summary>
public required CecilSerializerContext.SerializableTypeInfo SerializableTypeInfo { get; init; }
/// <summary>
/// Base type whose serializer should be called first (for inheritance chains).
/// Set during <see cref="CecilSerializerContext.CollectSerializerDependencies"/>.
/// </summary>
public TypeReference? SerializedParentType { get; set; }
/// <summary>
/// The serializable fields/properties of <see cref="DataType"/>.
/// Computed once during collection and reused during code generation.
/// </summary>
public SerializationHelpers.SerializableItem[] SerializableItems { get; set; }
}
@@ -72,21 +72,6 @@
<Compile Include="..\Stride.Core\Storage\ObjectIdBuilder.cs">
<Link>Core\ObjectIdBuilder.cs</Link>
</Compile>
<Compile Update="AssemblyScanCodeGenerator.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AssemblyScanCodeGenerator.tt</DependentUpon>
</Compile>
<Compile Update="ComplexSerializerCodeGenerator.cs">
<DependentUpon>ComplexSerializerCodeGenerator.tt</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
<Compile Update="ComplexClassSerializerGenerator.cs">
<DependentUpon>ComplexClassSerializerGenerator.tt</DependentUpon>
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
</Compile>
</ItemGroup>
<!-- Items that have no role in the build process -->
@@ -14,73 +14,45 @@ internal partial class UpdateEngineProcessor
var assembly = context.Assembly;
// Check "#if IL" directly in the source to easily see what is generated
GenerateUpdateEngineHelperCode(assembly);
GenerateUpdatableFieldCode(assembly);
new UpdatablePropertyCodeGenerator(assembly).GenerateUpdatablePropertyCode();
new UpdatableListCodeGenerator(assembly).GenerateUpdatablePropertyCode();
}
private static void GenerateUpdateEngineHelperCode(AssemblyDefinition assembly)
{
var updateEngineHelperType = assembly.MainModule.GetType("Stride.Updater.UpdateEngineHelper");
// UpdateEngineHelper.ObjectToPtr
var objectToPtr = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "ObjectToPtr"));
objectToPtr.Emit(OpCodes.Ldarg, objectToPtr.Body.Method.Parameters[0]);
objectToPtr.Emit(OpCodes.Conv_I);
objectToPtr.Emit(OpCodes.Ret);
// UpdateEngineHelper.PtrToObject
// Simpler "ldarg.0 + ret" doesn't work with Xamarin: https://bugzilla.xamarin.com/show_bug.cgi?id=40608
var ptrToObject = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "PtrToObject"));
ptrToObject.Body.Variables.Add(new VariableDefinition(assembly.MainModule.TypeSystem.Object));
ptrToObject.Emit(OpCodes.Ldloca_S, (byte)0);
ptrToObject.Emit(OpCodes.Ldarg, ptrToObject.Body.Method.Parameters[0]);
// Somehow Xamarin forces us to do a roundtrip to an object
ptrToObject.Emit(OpCodes.Stind_I);
ptrToObject.Emit(OpCodes.Ldloc_0);
ptrToObject.Emit(OpCodes.Ret);
// UpdateEngineHelper.Unbox
var unbox = RewriteBody(updateEngineHelperType.Methods.First(x => x.Name == "Unbox"));
unbox.Emit(OpCodes.Ldarg, unbox.Body.Method.Parameters[0]);
unbox.Emit(OpCodes.Unbox, unbox.Body.Method.GenericParameters[0]);
unbox.Emit(OpCodes.Ret);
}
/// <summary>
/// Rewrites UpdatableField methods with raw pointer-based field access.
/// </summary>
private static void GenerateUpdatableFieldCode(AssemblyDefinition assembly)
{
var updatableFieldType = assembly.MainModule.GetType("Stride.Updater.UpdatableField");
var updatableFieldGenericType = assembly.MainModule.GetType("Stride.Updater.UpdatableField`1");
// UpdatableField.GetObject
var getObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "GetObject"));
getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]);
getObject.Emit(OpCodes.Ldind_Ref);
getObject.Emit(OpCodes.Ret);
// Generates: object GetObject(IntPtr ptr) => *(object*)ptr;
var getObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "GetObject"), assembly);
getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0])
.Emit(OpCodes.Ldind_Ref)
.Emit(OpCodes.Ret);
// UpdatableField.SetObject
var setObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "SetObject"));
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]);
setObject.Emit(OpCodes.Stind_Ref);
setObject.Emit(OpCodes.Ret);
// Generates: void SetObject(IntPtr ptr, object value) { *(object*)ptr = value; }
var setObject = RewriteBody(updatableFieldType.Methods.First(x => x.Name == "SetObject"), assembly);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0])
.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1])
.Emit(OpCodes.Stind_Ref)
.Emit(OpCodes.Ret);
// UpdatableField<T>.SetStruct
var setStruct = RewriteBody(updatableFieldGenericType.Methods.First(x => x.Name == "SetStruct"));
setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0]);
setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]);
setStruct.Emit(OpCodes.Unbox, updatableFieldGenericType.GenericParameters[0]);
setStruct.Emit(OpCodes.Cpobj, updatableFieldGenericType.GenericParameters[0]);
setStruct.Emit(OpCodes.Ret);
// Generates: void SetStruct(IntPtr ptr, object value) { *(T*)ptr = (T)value; }
var setStruct = RewriteBody(updatableFieldGenericType.Methods.First(x => x.Name == "SetStruct"), assembly);
setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0])
.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1])
.Emit(OpCodes.Unbox, updatableFieldGenericType.GenericParameters[0])
.Emit(OpCodes.Cpobj, updatableFieldGenericType.GenericParameters[0])
.Emit(OpCodes.Ret);
}
private static ILProcessor RewriteBody(MethodDefinition method)
private static ILBuilder RewriteBody(MethodDefinition method, AssemblyDefinition assembly)
{
method.Body = new MethodBody(method);
return method.Body.GetILProcessor();
return new ILBuilder(method.Body, assembly.MainModule);
}
/// <summary>
@@ -96,50 +68,49 @@ internal partial class UpdateEngineProcessor
this.assembly = assembly;
}
public abstract void EmitGetCode(ILProcessor il, TypeReference type);
public abstract void EmitGetCode(ILBuilder il, TypeReference type);
public virtual void EmitSetCodeBeforeValue(ILProcessor il, TypeReference type)
public virtual void EmitSetCodeBeforeValue(ILBuilder il, TypeReference type)
{
}
public abstract void EmitSetCodeAfterValue(ILProcessor il, TypeReference type);
public abstract void EmitSetCodeAfterValue(ILBuilder il, TypeReference type);
public virtual void GenerateUpdatablePropertyCode()
{
// UpdatableProperty.GetStructAndUnbox
var getStructAndUnbox = RewriteBody(declaringType.Methods.First(x => x.Name == "GetStructAndUnbox"));
getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[1]);
//getStructAndUnbox.Emit(OpCodes.Call, assembly.MainModule.ImportReference(unbox).MakeGenericMethod(declaringType.GenericParameters[0]));
getStructAndUnbox.Emit(OpCodes.Unbox, declaringType.GenericParameters[0]);
getStructAndUnbox.Emit(OpCodes.Dup);
getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[0]);
// Generates: void GetStructAndUnbox(IntPtr ptr, object obj) { *(T*)Unbox(obj) = Get(ptr); }
var getStructAndUnbox = RewriteBody(declaringType.Methods.First(x => x.Name == "GetStructAndUnbox"), assembly);
getStructAndUnbox.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[1])
.Emit(OpCodes.Unbox, declaringType.GenericParameters[0])
.Emit(OpCodes.Dup)
.Emit(OpCodes.Ldarg, getStructAndUnbox.Body.Method.Parameters[0]);
EmitGetCode(getStructAndUnbox, declaringType.GenericParameters[0]);
getStructAndUnbox.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]);
getStructAndUnbox.Emit(OpCodes.Ret);
getStructAndUnbox.Emit(OpCodes.Stobj, declaringType.GenericParameters[0])
.Emit(OpCodes.Ret);
// UpdatableProperty.GetBlittable
var getBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "GetBlittable"));
getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[1]);
getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[0]);
// Generates: void GetBlittable(IntPtr ptr, IntPtr dest) { *(T*)dest = Get(ptr); }
var getBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "GetBlittable"), assembly);
getBlittable.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[1])
.Emit(OpCodes.Ldarg, getBlittable.Body.Method.Parameters[0]);
EmitGetCode(getBlittable, declaringType.GenericParameters[0]);
getBlittable.Emit(OpCodes.Stobj, declaringType.GenericParameters[0]);
getBlittable.Emit(OpCodes.Ret);
getBlittable.Emit(OpCodes.Stobj, declaringType.GenericParameters[0])
.Emit(OpCodes.Ret);
// UpdatableProperty.SetStruct
var setStruct = RewriteBody(declaringType.Methods.First(x => x.Name == "SetStruct"));
// Generates: void SetStruct(IntPtr ptr, object value) { Set(ptr, (T)value); }
var setStruct = RewriteBody(declaringType.Methods.First(x => x.Name == "SetStruct"), assembly);
setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setStruct, declaringType.GenericParameters[0]);
setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1]);
setStruct.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]);
setStruct.Emit(OpCodes.Ldarg, setStruct.Body.Method.Parameters[1])
.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]);
EmitSetCodeAfterValue(setStruct, declaringType.GenericParameters[0]);
setStruct.Emit(OpCodes.Ret);
// UpdatableProperty.SetBlittable
var setBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "SetBlittable"));
// Generates: void SetBlittable(IntPtr ptr, IntPtr src) { Set(ptr, *(T*)src); }
var setBlittable = RewriteBody(declaringType.Methods.First(x => x.Name == "SetBlittable"), assembly);
setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setBlittable, declaringType.GenericParameters[0]);
setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[1]);
setBlittable.Emit(OpCodes.Ldobj, declaringType.GenericParameters[0]);
setBlittable.Emit(OpCodes.Ldarg, setBlittable.Body.Method.Parameters[1])
.Emit(OpCodes.Ldobj, declaringType.GenericParameters[0]);
EmitSetCodeAfterValue(setBlittable, declaringType.GenericParameters[0]);
setBlittable.Emit(OpCodes.Ret);
}
@@ -167,16 +138,14 @@ internal partial class UpdateEngineProcessor
public override void GenerateUpdatablePropertyCode()
{
// For UpdatableProperty, GetObject/SetObject are declared on another type
// UpdatableProperty.GetObject
var getObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "GetObject"));
// Generates: object GetObject(IntPtr ptr) => Getter(ptr); // via calli
var getObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "GetObject"), assembly);
getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]);
EmitGetCode(getObject, assembly.MainModule.TypeSystem.Object);
getObject.Emit(OpCodes.Ret);
// UpdatableProperty.SetObject
var setObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "SetObject"));
// Generates: void SetObject(IntPtr ptr, object value) { Setter(ptr, value); } // via calli
var setObject = RewriteBody(declaringTypeForObjectMethods.Methods.First(x => x.Name == "SetObject"), assembly);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setObject, assembly.MainModule.TypeSystem.Object);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]);
@@ -186,48 +155,60 @@ internal partial class UpdateEngineProcessor
base.GenerateUpdatablePropertyCode();
}
public override void EmitGetCode(ILProcessor il, TypeReference type)
/// <summary>
/// Emits getter call via function pointer (calli). Branches on VirtualDispatchGetter:
/// <code>
/// if (VirtualDispatchGetter)
/// return calli(Getter, (IntPtr)obj); // static dispatcher that does ldvirtftn internally
/// else
/// return calli(Getter, obj); // direct instance call via fn pointer
/// </code>
/// </summary>
public override void EmitGetCode(ILBuilder il, TypeReference type)
{
var calliInstance = Instruction.Create(OpCodes.Calli, new CallSite(type) { HasThis = true });
// Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expect a IntPtr rather than object
// Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expects IntPtr
// (another option would be to use "castclass object" after pushing the IntPtr on the stack)
var calliVirtualDispatch = Instruction.Create(OpCodes.Calli, new CallSite(type) { HasThis = false, Parameters = { new ParameterDefinition(assembly.MainModule.TypeSystem.IntPtr) } });
var postCalli = Instruction.Create(OpCodes.Nop);
var postCalli = ILBuilder.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, updatablePropertyGetter);
il.Emit(OpCodes.Ldarg_0);
// For normal calls, we use ldftn and an instance calls
// For virtual and interface calls, we generate a dispatch function that calls ldvirtftn on the actual object, then call the method on the object
// this dispatcher method is static, so the calli has a different signature
// Note: we could later optimize the bool check by having two variant of Get/SetObject
// and two different implementations of both UpdatableProperty<T> and UpdatablePropertyObject<T>
// (not sure if worth it)
il.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchGetter);
il.Emit(OpCodes.Brfalse, calliInstance);
il.Append(calliVirtualDispatch);
il.Emit(OpCodes.Br, postCalli);
il.Append(calliInstance);
il.Append(postCalli);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldfld, updatablePropertyGetter)
.Emit(OpCodes.Ldarg_0)
// For normal calls, we use ldftn and an instance calli.
// For virtual/interface calls, we generate a static dispatch function (via <see cref="CreateDispatcher"/>)
// that calls ldvirtftn on the actual object — the calli has a different signature since the dispatcher is static.
/// Note: we could later optimize the bool check by having two variants of Get/SetObject
// and two different implementations of both UpdatableProperty&lt;T&gt; and UpdatablePropertyObject&lt;T&gt;
// (not sure if worth it).
.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchGetter)
.Emit(OpCodes.Brfalse, calliInstance)
.Append(calliVirtualDispatch)
.Emit(OpCodes.Br, postCalli)
.Append(calliInstance)
.MarkLabel(postCalli);
}
public override void EmitSetCodeAfterValue(ILProcessor il, TypeReference type)
/// <summary>
/// Emits setter call via function pointer (calli), same branching pattern as getter.
/// </summary>
public override void EmitSetCodeAfterValue(ILBuilder il, TypeReference type)
{
var calliInstance = Instruction.Create(OpCodes.Calli, new CallSite(assembly.MainModule.TypeSystem.Void) { HasThis = true, Parameters = { new ParameterDefinition(type) } });
// Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expect a IntPtr rather than object
// Note: .NET 6 doesn't like IntPtr => object implicit conversion so we pretend the method expects IntPtr
// (another option would be to use "castclass object" after pushing the IntPtr on the stack)
var calliVirtualDispatch = Instruction.Create(OpCodes.Calli, new CallSite(assembly.MainModule.TypeSystem.Void) { HasThis = false, Parameters = { new ParameterDefinition(assembly.MainModule.TypeSystem.IntPtr), new ParameterDefinition(type) } });
var postCalli = Instruction.Create(OpCodes.Nop);
var postCalli = ILBuilder.DefineLabel();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, updatablePropertySetter);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchSetter);
il.Emit(OpCodes.Brfalse, calliInstance);
il.Append(calliVirtualDispatch);
il.Emit(OpCodes.Br, postCalli);
il.Append(calliInstance);
il.Append(postCalli);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldfld, updatablePropertySetter)
.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldfld, updatablePropertyVirtualDispatchSetter)
.Emit(OpCodes.Brfalse, calliInstance)
.Append(calliVirtualDispatch)
.Emit(OpCodes.Br, postCalli)
.Append(calliInstance)
.MarkLabel(postCalli);
}
}
@@ -239,21 +220,19 @@ internal partial class UpdateEngineProcessor
public override void GenerateUpdatablePropertyCode()
{
// For UpdatableCustomAccessor, GetObject/SetObject are declared in the generic type
// UpdatableProperty.GetObject
var getObject = RewriteBody(declaringType.Methods.First(x => x.Name == "GetObject"));
// Generates: object GetObject(IntPtr ptr) => (object)Get(ptr);
var getObject = RewriteBody(declaringType.Methods.First(x => x.Name == "GetObject"), assembly);
getObject.Emit(OpCodes.Ldarg, getObject.Body.Method.Parameters[0]);
EmitGetCode(getObject, declaringType.GenericParameters[0]);
getObject.Emit(OpCodes.Box, declaringType.GenericParameters[0]); // Required for Windows 10 AOT
getObject.Emit(OpCodes.Ret);
getObject.Emit(OpCodes.Box, declaringType.GenericParameters[0]) // Required for Windows 10 AOT
.Emit(OpCodes.Ret);
// UpdatableProperty.SetObject
var setObject = RewriteBody(declaringType.Methods.First(x => x.Name == "SetObject"));
// Generates: void SetObject(IntPtr ptr, object value) { Set(ptr, (T)value); }
var setObject = RewriteBody(declaringType.Methods.First(x => x.Name == "SetObject"), assembly);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[0]);
EmitSetCodeBeforeValue(setObject, declaringType.GenericParameters[0]);
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1]);
setObject.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); // Required for Windows 10 AOT
setObject.Emit(OpCodes.Ldarg, setObject.Body.Method.Parameters[1])
.Emit(OpCodes.Unbox_Any, declaringType.GenericParameters[0]); // Required for Windows 10 AOT
EmitSetCodeAfterValue(setObject, declaringType.GenericParameters[0]);
setObject.Emit(OpCodes.Ret);
@@ -261,6 +240,11 @@ internal partial class UpdateEngineProcessor
}
}
/// <summary>
/// Generates list accessor methods that delegate to <c>IList&lt;T&gt;[Index]</c>.
/// Get: <c>return ((IList&lt;T&gt;)obj)[this.Index];</c>
/// Set: <c>((IList&lt;T&gt;)obj)[this.Index] = value;</c>
/// </summary>
class UpdatableListCodeGenerator : UpdatableCustomPropertyCodeGenerator
{
private readonly FieldDefinition indexField;
@@ -282,20 +266,20 @@ internal partial class UpdateEngineProcessor
ilistSetItem = assembly.MainModule.ImportReference(ilistItem.SetMethod).MakeGeneric(declaringType.GenericParameters[0]);
}
public override void EmitGetCode(ILProcessor il, TypeReference type)
public override void EmitGetCode(ILBuilder il, TypeReference type)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, indexField);
il.Emit(OpCodes.Callvirt, ilistGetItem);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldfld, indexField)
.Emit(OpCodes.Callvirt, ilistGetItem);
}
public override void EmitSetCodeBeforeValue(ILProcessor il, TypeReference type)
public override void EmitSetCodeBeforeValue(ILBuilder il, TypeReference type)
{
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, indexField);
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldfld, indexField);
}
public override void EmitSetCodeAfterValue(ILProcessor il, TypeReference type)
public override void EmitSetCodeAfterValue(ILBuilder il, TypeReference type)
{
il.Emit(OpCodes.Callvirt, ilistSetItem);
}
@@ -41,22 +41,37 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
// Obtain the static constructor of <Module> and the return instruction
var moduleConstructor = assembly.OpenModuleConstructor(out var returnInstruction);
// Get the IL processor of the module constructor
var il = moduleConstructor.Body.GetILProcessor();
// Get the IL processor of the module constructor (used only for InsertBefore)
var moduleCtorIL = moduleConstructor.Body.GetILProcessor();
// Create the call to Initialize method
var initializeMethodReference = assembly.MainModule.ImportReference(mainPrepareMethod);
var callInitializeInstruction = il.Create(OpCodes.Call, initializeMethodReference);
il.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction);
var callInitializeInstruction = moduleCtorIL.Create(OpCodes.Call, initializeMethodReference);
moduleCtorIL.InsertBefore(moduleConstructor.Body.Instructions.Last(), callInitializeInstruction);
return mainPrepareMethod;
}
/// <summary>
/// Creates a static dispatcher method for virtual/interface property access via <c>ldvirtftn</c>/<c>calli</c>.
/// </summary>
/// <remarks>
/// Generates code equivalent to:
/// <code>
/// static TReturn Dispatcher_get_Property(object @this /*, params... */)
/// {
/// return @this./* virtual call via ldvirtftn+calli */get_Property(/* params... */);
/// }
/// </code>
/// This is needed because <c>ldftn</c> of a virtual method gives the base slot, not the override.
/// The dispatcher uses <c>ldvirtftn</c> to resolve the actual vtable entry at runtime.
/// </remarks>
private MethodReference CreateDispatcher(AssemblyDefinition assembly, MethodReference method)
{
var updateEngineType = GetOrCreateUpdateType(assembly, true);
var module = assembly.MainModule;
var dispatcherMethod = new MethodDefinition($"Dispatcher_{method.Name}", MethodAttributes.HideBySig | MethodAttributes.Assembly | MethodAttributes.Static, assembly.MainModule.ImportReference(method.ReturnType));
var dispatcherMethod = new MethodDefinition($"Dispatcher_{method.Name}", MethodAttributes.HideBySig | MethodAttributes.Assembly | MethodAttributes.Static, module.ImportReference(method.ReturnType));
updateEngineType.Methods.Add(dispatcherMethod);
dispatcherMethod.Parameters.Add(new ParameterDefinition("this", ParameterAttributes.None, method.DeclaringType));
@@ -65,19 +80,22 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
dispatcherMethod.Parameters.Add(new ParameterDefinition(param.Name, param.Attributes, param.ParameterType));
}
var il = dispatcherMethod.Body.GetILProcessor();
// Emit: load all args, then resolve virtual method and call via calli
var il = new ILBuilder(dispatcherMethod.Body, module);
il.Emit(OpCodes.Ldarg_0);
foreach (var param in dispatcherMethod.Parameters.Skip(1)) // first parameter is "this"
// note: first parameter is "this"
foreach (var param in dispatcherMethod.Parameters.Skip(1))
{
il.Emit(OpCodes.Ldarg, param);
}
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldvirtftn, method);
var callsite = new Mono.Cecil.CallSite(method.ReturnType) { HasThis = true };
foreach (var param in method.Parameters)
callsite.Parameters.Add(param);
il.Emit(OpCodes.Calli, callsite);
il.Emit(OpCodes.Ret);
// Emit: @this.ldvirtftn(method) then calli — resolves the actual override at runtime
il.Emit(OpCodes.Ldarg_0)
.Emit(OpCodes.Ldvirtftn, method)
.Emit(OpCodes.Calli, callsite)
.Emit(OpCodes.Ret);
return dispatcherMethod;
}
@@ -88,6 +106,7 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
EnumerateReferences(references, context.Assembly);
var coreAssembly = CecilExtensions.FindCorlibAssembly(context.Assembly);
var module = context.Assembly.MainModule;
// Only process assemblies depending on Stride.Engine
if (!references.Any(x => x.Name.Name == "Stride.Engine"))
@@ -97,12 +116,12 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
var serializationAssemblyName = "Stride.Engine.Serializers";
// Add [InteralsVisibleTo] attribute
var internalsVisibleToAttributeCtor = context.Assembly.MainModule.ImportReference(internalsVisibleToAttribute.GetConstructors().Single());
var internalsVisibleToAttributeCtor = module.ImportReference(internalsVisibleToAttribute.GetConstructors().Single());
var internalsVisibleAttribute = new CustomAttribute(internalsVisibleToAttributeCtor)
{
ConstructorArguments =
{
new CustomAttributeArgument(context.Assembly.MainModule.ImportReference(context.Assembly.MainModule.TypeSystem.String), serializationAssemblyName)
new CustomAttributeArgument(module.ImportReference(module.TypeSystem.String), serializationAssemblyName)
}
};
context.Assembly.CustomAttributes.Add(internalsVisibleAttribute);
@@ -112,7 +131,7 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
var strideEngineAssembly = context.Assembly.Name.Name == "Stride.Engine"
? context.Assembly
: context.Assembly.MainModule.AssemblyResolver.Resolve(new AssemblyNameReference("Stride.Engine", null));
: module.AssemblyResolver.Resolve(new AssemblyNameReference("Stride.Engine", null));
var strideEngineModule = strideEngineAssembly.MainModule;
// Generate IL for Stride.Core
@@ -140,14 +159,14 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
parameterCollectionResolverInstantiateValueAccessor = parameterCollectionResolver.Methods.First(x => x.Name == "InstantiateValueAccessor");
var registerMemberMethod = strideEngineModule.GetType("Stride.Updater.UpdateEngine").Methods.First(x => x.Name == "RegisterMember");
updateEngineRegisterMemberMethod = context.Assembly.MainModule.ImportReference(registerMemberMethod);
updateEngineRegisterMemberMethod = module.ImportReference(registerMemberMethod);
var registerMemberResolverMethod = strideEngineModule.GetType("Stride.Updater.UpdateEngine").Methods.First(x => x.Name == "RegisterMemberResolver");
//pclVisitor.VisitMethod(registerMemberResolverMethod);
updateEngineRegisterMemberResolverMethod = context.Assembly.MainModule.ImportReference(registerMemberResolverMethod);
updateEngineRegisterMemberResolverMethod = module.ImportReference(registerMemberResolverMethod);
var typeType = coreAssembly.MainModule.GetTypeResolved(typeof(Type).FullName);
getTypeFromHandleMethod = context.Assembly.MainModule.ImportReference(typeType.Methods.First(x => x.Name == "GetTypeFromHandle"));
getTypeFromHandleMethod = module.ImportReference(typeType.Methods.First(x => x.Name == "GetTypeFromHandle"));
var mainPrepareMethod = CreateUpdateMethod(context.Assembly);
@@ -168,7 +187,7 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
try
{
ProcessType(context, context.Assembly.MainModule.ImportReference(typeDefinition), mainPrepareMethod);
ProcessType(context, module.ImportReference(typeDefinition), mainPrepareMethod);
}
catch (Exception e)
{
@@ -176,15 +195,20 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
}
}
// Force generic instantiations
var il = mainPrepareMethod.Body.GetILProcessor();
// Force generic instantiations — register resolvers for lists, arrays, and trigger generic update methods
// Generates calls like:
// UpdateEngine.RegisterMemberResolver(new ListUpdateResolver<ElementType>());
// UpdateEngine.RegisterMemberResolver(new ArrayUpdateResolver<ElementType>());
// ParameterCollectionResolver.InstantiateValueAccessor<KeyType>(); // iOS AOT only
// UpdateGeneric_TypeName<T1, T2>(); // for closed generic types
var il = new ILBuilder(mainPrepareMethod.Body, module);
foreach (var serializableType in context.SerializableTypesProfiles.SelectMany(x => x.Value.SerializableTypes).ToArray())
{
// Special case: when processing Stride.Engine assembly, we automatically add dependent assemblies types too
if (!serializableType.Value.Local && strideEngineAssembly != context.Assembly)
continue;
// Try to find if original method definition was generated
// Try to find if original method definition was generated
var typeDefinition = serializableType.Key.Resolve();
// If using List<T>, register this type in UpdateEngine
@@ -196,8 +220,8 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
{
//call Updater.UpdateEngine.RegisterMemberResolver(new Updater.ListUpdateResolver<T>());
var elementType = ResolveGenericsVisitor.Process(serializableType.Key, listInterfaceType.GenericArguments[0]);
il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableListUpdateResolverGenericCtor).MakeGeneric(context.Assembly.MainModule.ImportReference(elementType)));
il.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
il.Emit(OpCodes.Newobj, il.Import(updatableListUpdateResolverGenericCtor).MakeGeneric(il.Import(elementType)))
.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
}
parentTypeDefinition = parentTypeDefinition.BaseType?.Resolve();
@@ -208,15 +232,15 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
{
//call Updater.UpdateEngine.RegisterMemberResolver(new Updater.ArrayUpdateResolver<T>());
var elementType = ResolveGenericsVisitor.Process(serializableType.Key, arrayType.ElementType);
il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableArrayUpdateResolverGenericCtor).MakeGeneric(context.Assembly.MainModule.ImportReference(elementType)));
il.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
il.Emit(OpCodes.Newobj, il.Import(updatableArrayUpdateResolverGenericCtor).MakeGeneric(il.Import(elementType)))
.Emit(OpCodes.Call, updateEngineRegisterMemberResolverMethod);
}
// Generic instantiation for AOT platforms
if (context.Platform == Core.PlatformType.iOS && serializableType.Key.Name == "ValueParameterKey`1")
{
var keyType = ((GenericInstanceType)serializableType.Key).GenericArguments[0];
il.Emit(OpCodes.Call, context.Assembly.MainModule.ImportReference(parameterCollectionResolverInstantiateValueAccessor).MakeGenericMethod(context.Assembly.MainModule.ImportReference(keyType)));
il.Emit(OpCodes.Call, il.Import(parameterCollectionResolverInstantiateValueAccessor).MakeGenericMethod(il.Import(keyType)));
}
if (serializableType.Key is GenericInstanceType genericInstanceType)
@@ -233,9 +257,9 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
if (updateMethod != null)
{
// Emit call to update engine setup method with generic arguments of current type
il.Emit(OpCodes.Call, context.Assembly.MainModule.ImportReference(updateMethod)
il.Emit(OpCodes.Call, il.Import(updateMethod)
.MakeGenericMethod(genericInstanceType.GenericArguments
.Select(context.Assembly.MainModule.ImportReference)
.Select(x => module.ImportReference(x))
.ToArray()));
}
}
@@ -244,6 +268,11 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
il.Emit(OpCodes.Ret);
}
/// <summary>
/// Registers all updatable fields and properties of a type with the update engine.
/// For generic types, creates a separate <c>UpdateGeneric_TypeName&lt;T&gt;()</c> method
/// that can be instantiated per closed generic type.
/// </summary>
public void ProcessType(CecilSerializerContext context, TypeReference type, MethodDefinition updateMainMethod)
{
var typeDefinition = type.Resolve();
@@ -252,14 +281,14 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
if (typeDefinition.IsEnum)
return;
var module = context.Assembly.MainModule;
var updateCurrentMethod = updateMainMethod;
ResolveGenericsVisitor replaceGenericsVisitor = null;
if (typeDefinition.HasGenericParameters)
{
// Make a prepare method for just this object since it might need multiple instantiation
updateCurrentMethod = new MethodDefinition(ComputeUpdateMethodName(typeDefinition), MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static, context.Assembly.MainModule.TypeSystem.Void);
var genericsMapping = new Dictionary<TypeReference, TypeReference>();
updateCurrentMethod = new MethodDefinition(ComputeUpdateMethodName(typeDefinition), MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.Static, module.TypeSystem.Void);
foreach (var genericParameter in typeDefinition.GenericParameters)
{
var genericParameterCopy = new GenericParameter(genericParameter.Name, updateCurrentMethod)
@@ -267,125 +296,28 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
Attributes = genericParameter.Attributes,
};
foreach (var constraint in genericParameter.Constraints)
genericParameterCopy.Constraints.Add(new GenericParameterConstraint(context.Assembly.MainModule.ImportReference(constraint.ConstraintType)));
genericParameterCopy.Constraints.Add(new GenericParameterConstraint(module.ImportReference(constraint.ConstraintType)));
updateCurrentMethod.GenericParameters.Add(genericParameterCopy);
genericsMapping[genericParameter] = genericParameterCopy;
}
replaceGenericsVisitor = new ResolveGenericsVisitor(genericsMapping);
replaceGenericsVisitor = ResolveGenericsVisitor.FromMapping(typeDefinition, updateCurrentMethod);
updateMainMethod.DeclaringType.Methods.Add(updateCurrentMethod);
}
var il = updateCurrentMethod.Body.GetILProcessor();
var il = new ILBuilder(updateCurrentMethod.Body, module);
var typeIsValueType = type.IsResolvedValueType();
var emptyObjectField = typeIsValueType ? null : updateMainMethod.DeclaringType.Fields.FirstOrDefault(x => x.Name == "emptyObject");
VariableDefinition emptyStruct = null;
// Note: forcing fields and properties to be processed in all cases
foreach (var serializableItem in ComplexSerializerRegistry.GetSerializableItems(type, true, ComplexTypeSerializerFlags.SerializePublicFields | ComplexTypeSerializerFlags.SerializePublicProperties | ComplexTypeSerializerFlags.Updatable))
foreach (var serializableItem in SerializationHelpers.GetSerializableItems(type, true, ComplexTypeSerializerFlags.SerializePublicFields | ComplexTypeSerializerFlags.SerializePublicProperties | ComplexTypeSerializerFlags.Updatable, context.IgnoredMembers))
{
if (serializableItem.MemberInfo is FieldReference fieldReference)
{
var field = fieldReference.Resolve();
// First time it is needed, let's create empty object in the class (var emptyObject = new object()) or empty local struct in the method
if (typeIsValueType)
{
if (emptyStruct == null)
{
emptyStruct = new VariableDefinition(type);
updateMainMethod.Body.Variables.Add(emptyStruct);
}
}
else
{
if (emptyObjectField == null)
{
emptyObjectField = new FieldDefinition("emptyObject", FieldAttributes.Static | FieldAttributes.Private, context.Assembly.MainModule.TypeSystem.Object);
// Create static ctor that will initialize this object
var staticConstructor = new MethodDefinition(".cctor",
MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
context.Assembly.MainModule.TypeSystem.Void);
var staticConstructorIL = staticConstructor.Body.GetILProcessor();
staticConstructorIL.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(emptyObjectField.FieldType.Resolve().GetConstructors().Single(x => !x.IsStatic && !x.HasParameters)));
staticConstructorIL.Emit(OpCodes.Stsfld, emptyObjectField);
staticConstructorIL.Emit(OpCodes.Ret);
updateMainMethod.DeclaringType.Fields.Add(emptyObjectField);
updateMainMethod.DeclaringType.Methods.Add(staticConstructor);
}
}
il.Emit(OpCodes.Ldtoken, type);
il.Emit(OpCodes.Call, getTypeFromHandleMethod);
il.Emit(OpCodes.Ldstr, field.Name);
if (typeIsValueType)
il.Emit(OpCodes.Ldloca, emptyStruct);
else
il.Emit(OpCodes.Ldsfld, emptyObjectField);
il.Emit(OpCodes.Ldflda, context.Assembly.MainModule.ImportReference(fieldReference));
il.Emit(OpCodes.Conv_I);
if (typeIsValueType)
il.Emit(OpCodes.Ldloca, emptyStruct);
else
il.Emit(OpCodes.Ldsfld, emptyObjectField);
il.Emit(OpCodes.Conv_I);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Conv_I4);
var fieldType = context.Assembly.MainModule.ImportReference(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(field.FieldType) : field.FieldType);
il.Emit(OpCodes.Newobj, context.Assembly.MainModule.ImportReference(updatableFieldGenericCtor).MakeGeneric(fieldType));
il.Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
}
EmitFieldRegistration(il, fieldReference, type, typeIsValueType, replaceGenericsVisitor, module, updateMainMethod, ref emptyObjectField, ref emptyStruct);
if (serializableItem.MemberInfo is PropertyReference propertyReference)
{
var property = propertyReference.Resolve();
var propertyGetMethod = context.Assembly.MainModule.ImportReference(property.GetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
il.Emit(OpCodes.Ldtoken, type);
il.Emit(OpCodes.Call, getTypeFromHandleMethod);
il.Emit(OpCodes.Ldstr, property.Name);
// If it's a virtual or interface call, we need to create a dispatcher using ldvirtftn
if (property.GetMethod.IsVirtual)
propertyGetMethod = CreateDispatcher(context.Assembly, propertyGetMethod);
il.Emit(OpCodes.Ldftn, propertyGetMethod);
// Set whether getter method uses a VirtualDispatch (static call) or instance call
il.Emit(property.GetMethod.IsVirtual ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
// Only uses setter if it exists and it's public
if (property.SetMethod?.IsPublic == true)
{
var propertySetMethod = context.Assembly.MainModule.ImportReference(property.SetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
if (property.SetMethod.IsVirtual)
propertySetMethod = CreateDispatcher(context.Assembly, propertySetMethod);
il.Emit(OpCodes.Ldftn, propertySetMethod);
}
else
{
// 0 (native int)
il.Emit(OpCodes.Ldc_I4_0);
il.Emit(OpCodes.Conv_I);
}
// Set whether setter method uses a VirtualDispatch (static call) or instance call
il.Emit((property.SetMethod?.IsVirtual ?? false) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
var propertyType = context.Assembly.MainModule.ImportReference(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(property.PropertyType) : property.PropertyType);
var updatablePropertyInflatedCtor = GetOrCreateUpdatablePropertyCtor(context.Assembly, propertyType);
il.Emit(OpCodes.Newobj, updatablePropertyInflatedCtor);
il.Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
}
EmitPropertyRegistration(il, propertyReference, type, replaceGenericsVisitor, context.Assembly, updateCurrentMethod);
}
if (updateCurrentMethod != updateMainMethod)
@@ -396,12 +328,150 @@ internal partial class UpdateEngineProcessor : ICecilSerializerProcessor
// Also call it from main method if it was a closed generic instantiation
if (type is GenericInstanceType genericInstanceType)
{
il = updateMainMethod.Body.GetILProcessor();
il.Emit(OpCodes.Call, updateCurrentMethod.MakeGeneric(genericInstanceType.GenericArguments.Select(context.Assembly.MainModule.ImportReference).ToArray()));
var mainIL = new ILBuilder(updateMainMethod.Body, module);
mainIL.Emit(OpCodes.Call, updateCurrentMethod.MakeGeneric(genericInstanceType.GenericArguments.Select(module.ImportReference).ToArray()));
}
}
}
/// <summary>
/// Emits field registration with the update engine using pointer offset computation.
/// </summary>
/// <remarks>
/// Generates code equivalent to:
/// <code>
/// // For reference types:
/// UpdateEngine.RegisterMember(typeof(T), "fieldName",
/// new UpdatableField&lt;FieldType&gt;(&amp;emptyObject.field - &amp;emptyObject));
/// // For value types:
/// UpdateEngine.RegisterMember(typeof(T), "fieldName",
/// new UpdatableField&lt;FieldType&gt;(&amp;emptyStruct.field - &amp;emptyStruct));
/// </code>
/// </remarks>
private void EmitFieldRegistration(
ILBuilder il, FieldReference fieldReference, TypeReference type, bool typeIsValueType,
ResolveGenericsVisitor replaceGenericsVisitor, ModuleDefinition module,
MethodDefinition updateMainMethod, ref FieldDefinition emptyObjectField, ref VariableDefinition emptyStruct)
{
var field = fieldReference.Resolve();
// We need a dummy instance to compute field offsets via pointer math: &empty.field - &empty
// For reference types: a static field initialized in .cctor (var emptyObject = new object())
// For value types: a local variable (T emptyStruct)
if (typeIsValueType)
{
if (emptyStruct == null)
{
emptyStruct = new VariableDefinition(type);
updateMainMethod.Body.Variables.Add(emptyStruct);
}
}
else
{
if (emptyObjectField == null)
{
emptyObjectField = new FieldDefinition("emptyObject", FieldAttributes.Static | FieldAttributes.Private, module.TypeSystem.Object);
// Create static ctor that will initialize this object
var staticConstructor = new MethodDefinition(".cctor",
MethodAttributes.Private | MethodAttributes.HideBySig | MethodAttributes.Static | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName,
module.TypeSystem.Void);
var staticCtorIL = new ILBuilder(staticConstructor.Body, module);
staticCtorIL.Emit(OpCodes.Newobj, module.ImportReference(emptyObjectField.FieldType.Resolve().GetConstructors().Single(x => !x.IsStatic && !x.HasParameters)))
.Emit(OpCodes.Stsfld, emptyObjectField)
.Emit(OpCodes.Ret);
updateMainMethod.DeclaringType.Fields.Add(emptyObjectField);
updateMainMethod.DeclaringType.Methods.Add(staticConstructor);
}
}
// Emit: typeof(T), "fieldName"
il.EmitTypeof(type, getTypeFromHandleMethod)
.Emit(OpCodes.Ldstr, field.Name);
// Emit: (int)(&empty.field - &empty) — computes field byte offset
if (typeIsValueType)
il.Emit(OpCodes.Ldloca, emptyStruct);
else
il.Emit(OpCodes.Ldsfld, emptyObjectField);
il.Emit(OpCodes.Ldflda, il.Import(fieldReference))
.Emit(OpCodes.Conv_I);
if (typeIsValueType)
il.Emit(OpCodes.Ldloca, emptyStruct);
else
il.Emit(OpCodes.Ldsfld, emptyObjectField);
il.Emit(OpCodes.Conv_I)
.Emit(OpCodes.Sub)
.Emit(OpCodes.Conv_I4);
// Emit: new UpdatableField<FieldType>(offset)
var fieldType = il.Import(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(field.FieldType) : field.FieldType);
il.Emit(OpCodes.Newobj, il.Import(updatableFieldGenericCtor).MakeGeneric(fieldType))
// Emit: UpdateEngine.RegisterMember(typeof(T), "fieldName", updatableField);
.Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
}
/// <summary>
/// Emits property registration with the update engine using function pointers.
/// </summary>
/// <remarks>
/// Generates code equivalent to:
/// <code>
/// UpdateEngine.RegisterMember(typeof(T), "PropertyName",
/// new UpdatableProperty&lt;PropType&gt;(
/// ldftn(get_Property), // or ldftn(Dispatcher_get_Property) for virtual
/// isGetterVirtual,
/// ldftn(set_Property), // or IntPtr.Zero if no public setter
/// isSetterVirtual));
/// </code>
/// Virtual/interface properties use a dispatcher trampoline (see <see cref="CreateDispatcher"/>).
/// </remarks>
private void EmitPropertyRegistration(
ILBuilder il, PropertyReference propertyReference, TypeReference type,
ResolveGenericsVisitor replaceGenericsVisitor, AssemblyDefinition assembly,
MethodDefinition updateCurrentMethod)
{
var property = propertyReference.Resolve();
var propertyGetMethod = il.Import(property.GetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
// Emit: typeof(T), "PropertyName"
il.EmitTypeof(type, getTypeFromHandleMethod)
.Emit(OpCodes.Ldstr, property.Name);
// Emit: ldftn(get_Property) — for virtual, wrap in a dispatcher that uses ldvirtftn+calli
if (property.GetMethod.IsVirtual)
propertyGetMethod = CreateDispatcher(assembly, propertyGetMethod);
il.Emit(OpCodes.Ldftn, propertyGetMethod)
// Set whether setter method uses a VirtualDispatch (static call) or instance call
.Emit(property.GetMethod.IsVirtual ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
// Emit: ldftn(set_Property) or IntPtr.Zero if no public setter
if (property.SetMethod?.IsPublic == true)
{
var propertySetMethod = il.Import(property.SetMethod).MakeGeneric(updateCurrentMethod.GenericParameters.ToArray());
if (property.SetMethod.IsVirtual)
propertySetMethod = CreateDispatcher(assembly, propertySetMethod);
il.Emit(OpCodes.Ldftn, propertySetMethod);
}
else
{
il.Emit(OpCodes.Ldc_I4_0)
.Emit(OpCodes.Conv_I);
}
// Set whether setter method uses a VirtualDispatch (static call) or instance call
il.Emit((property.SetMethod?.IsVirtual ?? false) ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0);
// Emit: new UpdatableProperty<PropType>(getter, isGetterVirtual, setter, isSetterVirtual)
var propertyType = il.Import(replaceGenericsVisitor != null ? replaceGenericsVisitor.VisitDynamic(property.PropertyType) : property.PropertyType);
var updatablePropertyInflatedCtor = GetOrCreateUpdatablePropertyCtor(assembly, propertyType);
il.Emit(OpCodes.Newobj, updatablePropertyInflatedCtor)
// Emit: UpdateEngine.RegisterMember(typeof(T), "PropertyName", updatableProperty);
.Emit(OpCodes.Call, updateEngineRegisterMemberMethod);
}
private static string ComputeUpdateMethodName(TypeDefinition typeDefinition)
{
var typeName = ComputeTypeName(typeDefinition);
@@ -58,6 +58,7 @@ public class ModuleInitializerGenerator : IIncrementalGenerator
}
}
var outputClass = @$"
// <auto-generated/>
namespace Stride.Core.CompilerServices.AutoGenerated.ModuleInitializer
{{
file static class ModuleInitializer
@@ -102,7 +102,7 @@ public class TestHybridDictionary
var result = dictionary.Remove("key1");
Assert.True(result);
Assert.Equal(1, dictionary.Count);
Assert.Single(dictionary);
Assert.False(dictionary.ContainsKey("key1"));
}
@@ -115,7 +115,7 @@ public class TestHybridDictionary
var result = dictionary.Remove("key2");
Assert.False(result);
Assert.Equal(1, dictionary.Count);
Assert.Single(dictionary);
}
[Fact]
@@ -202,7 +202,7 @@ public class TestHybridDictionary
dictionary.Clear();
Assert.Equal(0, dictionary.Count);
Assert.Empty(dictionary);
}
[Fact]
@@ -249,7 +249,7 @@ public class TestHybridDictionary
foreach (var kvp in dictionary)
{
count++;
Assert.True(kvp.Key.StartsWith("key"));
Assert.StartsWith("key", kvp.Key);
}
Assert.Equal(3, count);
@@ -86,8 +86,8 @@ public class TestBoundingSphere
BoundingSphere.Merge(ref sphere1, ref sphere2, out var merged);
// Merged sphere should contain both spheres
Assert.True(merged.Contains(ref sphere1) != ContainmentType.Disjoint);
Assert.True(merged.Contains(ref sphere2) != ContainmentType.Disjoint);
Assert.NotEqual(ContainmentType.Disjoint, merged.Contains(ref sphere1));
Assert.NotEqual(ContainmentType.Disjoint, merged.Contains(ref sphere2));
// Test non-ref version
var merged2 = BoundingSphere.Merge(sphere1, sphere2);
@@ -113,9 +113,9 @@ public class TestSphericalHarmonics
var result = sh.Evaluate(direction);
// Verify result is valid (SH can produce negative or positive values)
Assert.True(!float.IsNaN(result.R));
Assert.True(!float.IsNaN(result.G));
Assert.True(!float.IsNaN(result.B));
Assert.False(float.IsNaN(result.R));
Assert.False(float.IsNaN(result.G));
Assert.False(float.IsNaN(result.B));
}
[Fact]
@@ -38,12 +38,7 @@ public struct Rectangle : IEquatable<Rectangle>, ISpanFormattable
/// <summary>
/// An empty rectangle.
/// </summary>
public static readonly Rectangle Empty;
static Rectangle()
{
Empty = new Rectangle();
}
public static readonly Rectangle Empty = new();
/// <summary>
/// Initializes a new instance of the <see cref="Rectangle"/> struct.
@@ -38,12 +38,7 @@ public struct RectangleF : IEquatable<RectangleF>, ISpanFormattable
/// <summary>
/// An empty rectangle
/// </summary>
public static readonly RectangleF Empty;
static RectangleF()
{
Empty = new RectangleF();
}
public static readonly RectangleF Empty = new();
/// <summary>
/// Initializes a new instance of the <see cref="RectangleF"/> struct.
@@ -55,7 +55,7 @@ public sealed class DatabaseFileProvider : VirtualFileProviderBase
if (streamFlags == StreamFlags.Seekable && !result.CanSeek)
{
var buffer = new byte[result.Length - result.Position];
result.Read(buffer, 0, buffer.Length);
result.ReadExactly(buffer, 0, buffer.Length);
return new DatabaseReadFileStream(objectId, new MemoryStream(buffer), 0);
}
@@ -323,7 +323,7 @@ public abstract class Store<T> : IDisposable where T : new()
return;
var bufferToRead = new byte[length];
stream.Read(bufferToRead, 0, length);
stream.ReadExactly(bufferToRead, 0, length);
var memoryStream = new MemoryStream(bufferToRead);
try
@@ -32,7 +32,7 @@ public class Blob : ReferenceBase
{
Size = (int)stream.Length;
Content = Marshal.AllocHGlobal(Size);
stream.Read(new Span<byte>((void*)Content, Size));
stream.ReadExactly(new Span<byte>((void*)Content, Size));
}
/// <summary>
@@ -5,6 +5,8 @@
namespace Stride.Core.Diagnostics;
#nullable enable
/// <summary>
/// Extensions for <see cref="ILogger"/>.
/// </summary>
@@ -19,11 +21,7 @@ public static class LoggerExtensions
/// <param name="callerInfo">Information about the caller. Default is null, otherwise use <see cref="CallerInfo.Get"/>.</param>
public static void Verbose(this ILogger logger, string message, Exception? exception, CallerInfo? callerInfo = null)
{
#if NET7_0_OR_GREATER
ArgumentNullException.ThrowIfNull(logger);
#else
if (logger is null) throw new ArgumentNullException(nameof(logger));
#endif // NET7_0_OR_GREATER
logger.Log(new LogMessage(logger.Module, LogMessageType.Verbose, message, exception, callerInfo));
}
@@ -46,11 +44,7 @@ public static class LoggerExtensions
/// <param name="callerInfo">Information about the caller. Default is null, otherwise use <see cref="CallerInfo.Get"/>.</param>
public static void Debug(this ILogger logger, string message, Exception? exception, CallerInfo? callerInfo = null)
{
#if NET7_0_OR_GREATER
ArgumentNullException.ThrowIfNull(logger);
#else
if (logger is null) throw new ArgumentNullException(nameof(logger));
#endif // NET7_0_OR_GREATER
logger.Log(new LogMessage(logger.Module, LogMessageType.Debug, message, exception, callerInfo));
}
@@ -73,11 +67,7 @@ public static class LoggerExtensions
/// <param name="callerInfo">Information about the caller. Default is null, otherwise use <see cref="CallerInfo.Get"/>.</param>
public static void Info(this ILogger logger, string message, Exception? exception, CallerInfo? callerInfo = null)
{
#if NET7_0_OR_GREATER
ArgumentNullException.ThrowIfNull(logger);
#else
if (logger is null) throw new ArgumentNullException(nameof(logger));
#endif // NET7_0_OR_GREATER
logger.Log(new LogMessage(logger.Module, LogMessageType.Info, message, exception, callerInfo));
}
@@ -100,11 +90,7 @@ public static class LoggerExtensions
/// <param name="callerInfo">Information about the caller. Default is null, otherwise use <see cref="CallerInfo.Get"/>.</param>
public static void Warning(this ILogger logger, string message, Exception? exception, CallerInfo? callerInfo = null)
{
#if NET7_0_OR_GREATER
ArgumentNullException.ThrowIfNull(logger);
#else
if (logger is null) throw new ArgumentNullException(nameof(logger));
#endif // NET7_0_OR_GREATER
logger.Log(new LogMessage(logger.Module, LogMessageType.Warning, message, exception, callerInfo));
}
@@ -127,11 +113,7 @@ public static class LoggerExtensions
/// <param name="callerInfo">Information about the caller. Default is null, otherwise use <see cref="CallerInfo.Get"/>.</param>
public static void Error(this ILogger logger, string message, Exception? exception, CallerInfo? callerInfo = null)
{
#if NET7_0_OR_GREATER
ArgumentNullException.ThrowIfNull(logger);
#else
if (logger is null) throw new ArgumentNullException(nameof(logger));
#endif // NET7_0_OR_GREATER
logger.Log(new LogMessage(logger.Module, LogMessageType.Error, message, exception, callerInfo));
}
@@ -154,11 +136,7 @@ public static class LoggerExtensions
/// <param name="callerInfo">Information about the caller. Default is null, otherwise use <see cref="CallerInfo.Get"/>.</param>
public static void Fatal(this ILogger logger, string message, Exception? exception, CallerInfo? callerInfo = null)
{
#if NET7_0_OR_GREATER
ArgumentNullException.ThrowIfNull(logger);
#else
if (logger is null) throw new ArgumentNullException(nameof(logger));
#endif // NET7_0_OR_GREATER
logger.Log(new LogMessage(logger.Module, LogMessageType.Fatal, message, exception, callerInfo));
}
@@ -11,6 +11,8 @@
namespace Stride.Core.Diagnostics;
#nullable enable
/// <summary>
/// Extensions for <see cref="ILogger"/>.
/// </summary>
@@ -26,11 +28,7 @@ public static class LoggerExtensions
/// <param name="callerInfo">Information about the caller. Default is null, otherwise use <see cref="CallerInfo.Get"/>.</param>
public static void <#= name #>(this ILogger logger, string message, Exception? exception, CallerInfo? callerInfo = null)
{
#if NET7_0_OR_GREATER
ArgumentNullException.ThrowIfNull(logger);
#else
if (logger is null) throw new ArgumentNullException(nameof(logger));
#endif // NET7_0_OR_GREATER
logger.Log(new LogMessage(logger.Module, LogMessageType.<#= name #>, message, exception, callerInfo));
}
@@ -323,7 +323,7 @@ public class BlittableArraySerializer<T> : ArraySerializer<T> where T : unmanage
{
var span = MemoryMarshal.Cast<T, byte>(array.AsSpan());
if (mode == ArchiveMode.Deserialize)
stream.UnderlyingStream.Read(span);
stream.UnderlyingStream.ReadExactly(span);
else if (mode == ArchiveMode.Serialize)
stream.UnderlyingStream.Write(span);
}
@@ -247,7 +247,7 @@ namespace Stride.Assets.Presentation.AssetEditors.EntityHierarchyEditor.Game
if (game.Input.IsKeyPressed(SceneEditorSettings.SwitchGizmo.GetValue()))
{
var current = activeTransformation;
var next = (int)(current + 1) % Enum.GetValues(typeof(Transformation)).Length;
var next = (int)(current + 1) % Enum.GetValues<Transformation>().Length;
await editor.Dispatcher.InvokeAsync(() => editor.Transform.ActiveTransformation = (Transformation)next);
}
}
@@ -290,7 +290,7 @@ namespace Stride.Assets.Presentation.AssetEditors
{
using (var streamReader = new StreamReader(File.Open(changedFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.UTF8, true))
{
source = streamReader.ReadToEnd();
source = await streamReader.ReadToEndAsync();
}
break;
}
@@ -110,7 +110,7 @@ namespace Stride.Assets.Presentation.AssetEditors.UIEditor.Views
}
/// <inheritdoc/>
protected override Thickness? UpateValueFromFloat(float value)
protected override Thickness? UpdateValueFromFloat(float value)
{
return Thickness.UniformCuboid(value);
}
@@ -79,7 +79,7 @@ namespace Stride.Assets.Presentation.AssetEditors.VisualScriptEditor
SemanticModel = compilation.GetSemanticModel(syntaxTree);
// Compute list of overridable methods
var type = SemanticModel.GetDeclaredSymbol(syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault());
var type = SemanticModel.GetDeclaredSymbol((await syntaxTree.GetRootAsync()).DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault());
// Note: Logic taken from Roslyn AbstractOverrideCompletionProvider
var overridableMethodsCurrentType = new List<ISymbol>();
@@ -123,7 +123,7 @@ namespace Stride.Assets.Presentation.AssetEditors.VisualScriptEditor
// Find the syntax node that generated this diagnostic
var sourceSpan = diagnostic.Location.SourceSpan;
var node = compileResult.SyntaxTree.GetRoot().FindNode(sourceSpan);
var node = (await compileResult.SyntaxTree.GetRootAsync()).FindNode(sourceSpan);
// Find which block or link generated it
// Go through parent recursively until we find one
@@ -455,7 +455,7 @@ namespace Stride.Assets.Presentation.AssetEditors.VisualScriptEditor
foreach (var assembly in GetAssemblies(latestCompilation, cancellationToken)
.OrderBy(assembly =>
{
if (assembly == latestCompilation.Assembly)
if (SymbolEqualityComparer.Default.Equals(assembly, latestCompilation.Assembly))
return 0;
if (assembly.Name.Contains("Stride"))
@@ -471,11 +471,11 @@ namespace Stride.Assets.Presentation.AssetEditors.VisualScriptEditor
// List types that are either public or part of current assembly
foreach (var type in GetAllTypes(assembly, cancellationToken)
.Where(type => type.DeclaredAccessibility != RoslynAccessibility.Private || type.ContainingAssembly == latestCompilation.Assembly))
.Where(type => type.DeclaredAccessibility != RoslynAccessibility.Private || SymbolEqualityComparer.Default.Equals(type.ContainingAssembly, latestCompilation.Assembly)))
{
// List methods
foreach (var method in type.GetMembers().OfType<IMethodSymbol>()
.Where(member => member.DeclaredAccessibility != RoslynAccessibility.Private || type.ContainingAssembly == latestCompilation.Assembly))
.Where(member => member.DeclaredAccessibility != RoslynAccessibility.Private || SymbolEqualityComparer.Default.Equals(type.ContainingAssembly, latestCompilation.Assembly)))
{
// Ignore .ctor, property getter/setter, events, etc...
if (method.MethodKind != MethodKind.Ordinary
@@ -522,7 +522,7 @@ namespace Stride.Assets.Presentation.AssetEditors.VisualScriptEditor
CancellationToken cancellationToken)
{
var stack = new Stack<IAssemblySymbol>();
var processedAssemblies = new HashSet<IAssemblySymbol>();
var processedAssemblies = new HashSet<IAssemblySymbol>(SymbolEqualityComparer.Default);
processedAssemblies.Add(compilation.Assembly);
stack.Push(compilation.Assembly);
@@ -144,7 +144,7 @@ namespace Stride.Assets.Presentation.Templates
scriptContent = scriptContent.Replace("##Namespace##", parameters.Namespace);
scriptContent = scriptContent.Replace("##Scriptname##", location.GetFileNameWithoutExtension());
var asset = (ScriptSourceFileAsset)ObjectFactoryRegistry.NewInstance(typeof(ScriptSourceFileAsset));
var asset = ObjectFactoryRegistry.NewInstance<ScriptSourceFileAsset>();
asset.Id = SourceCodeAsset.GenerateIdFromLocation(parameters.Package.Meta.Name, location);
asset.Text = scriptContent;
yield return new AssetItem(location, asset);
@@ -1,13 +1,21 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:s="clr-namespace:System;assembly=mscorlib"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ui="clr-namespace:Stride.UI;assembly=Stride.UI"
xmlns:views="clr-namespace:Stride.Assets.Presentation.AssetEditors.UIEditor.Views"
xmlns:tp="clr-namespace:Stride.Core.Assets.Editor.View.TemplateProviders;assembly=Stride.Core.Assets.Editor"
xmlns:sd="http://schemas.stride3d.net/xaml/presentation">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="/Stride.Core.Assets.Editor;component/View/DefaultPropertyTemplateProviders.xaml"/>
<ResourceDictionary Source="ImageDictionary.xaml"/>
</ResourceDictionary.MergedDictionaries>
<tp:TypeAndPropertyNameMatchTemplateProvider x:Key="RotationTemplateProvider" Type="{x:Type s:Single}" PropertyName="Rotation" sd:PropertyViewHelper.TemplateCategory="PropertyEditor">
<DataTemplate>
<sd:AngleEditor Value="{Binding NodeValue}" />
</DataTemplate>
</tp:TypeAndPropertyNameMatchTemplateProvider>
<sd:TypeMatchTemplateProvider x:Key="StripDefinitionTemplateProvider" Type="{x:Type ui:StripDefinition}" sd:PropertyViewHelper.TemplateCategory="PropertyEditor">
<DataTemplate>
<UniformGrid Rows="1">
@@ -83,8 +91,4 @@
<views:ThicknessEditor Value="{Binding NodeValue}" DecimalPlaces="3" />
</DataTemplate>
</sd:TypeMatchTemplateProvider>
</ResourceDictionary>
@@ -148,7 +148,7 @@ namespace Stride.Assets.Presentation.ViewModel
document = workspace.GetDocument(DocumentId.Result);
// Set new text
TextDocument.Text = document.GetTextAsync().Result.ToString();
TextDocument.Text = (await document.GetTextAsync()).ToString();
// Update dirty state
hasExternalChanges = false;
@@ -881,7 +881,7 @@ namespace Stride.Core.Assets.Editor.Tests
var result = service.DeserializeCopiedData(copiedText, asset, expectedType);
Assert.NotNull(result);
Assert.NotNull(result.Items);
Assert.Equal(1, result.Items.Count);
Assert.Single(result.Items);
var item = result.Items[0];
Assert.NotNull(item);
@@ -72,7 +72,7 @@ namespace Stride.Core.Assets.Editor.Components.Status
if (CurrentJob == job)
{
job = null;
foreach (JobPriority priority in Enum.GetValues(typeof(JobPriority)).Cast<JobPriority>().Reverse())
foreach (JobPriority priority in Enum.GetValues<JobPriority>().Cast<JobPriority>().Reverse())
{
var nextJob = jobList.LastOrDefault(x => x.Value.Priority == priority).Value;
if (nextJob != null)
@@ -0,0 +1,25 @@
// Copyright (c) .NET Foundation and Contributors (https://dotnetfoundation.org/ & https://stride3d.net)
// Distributed under the MIT license. See the LICENSE.md file in the project root for more information.
using Stride.Core.Presentation.Quantum.ViewModels;
namespace Stride.Core.Assets.Editor.View.TemplateProviders
{
/// <summary>
/// A template provider that matches nodes based on both their type and property name.
/// </summary>
public class TypeAndPropertyNameMatchTemplateProvider : TypeMatchTemplateProvider
{
/// <inheritdoc/>
public override string Name => $"{base.Name}_{PropertyName}";
/// <summary>
/// Gets or sets the name of the property to match.
/// </summary>
public string PropertyName { get; set; }
public override bool MatchNode(NodeViewModel node)
{
return base.MatchNode(node) && node.Name == PropertyName;
}
}
}
@@ -166,7 +166,7 @@ namespace Stride.Core.Assets.Editor.ViewModel
// Storing out primitive data for filters to save between instances of editor
private List<AssetFilterViewModelData> StoredListData = InternalSettings.ViewFilters.GetValue();
public static readonly IEnumerable<FilterCategory> AllFilterCategories = Enum.GetValues(typeof(FilterCategory)).Cast<FilterCategory>();
public static readonly IEnumerable<FilterCategory> AllFilterCategories = Enum.GetValues<FilterCategory>();
private readonly ObservableSet<AssetViewModel> assets = new ObservableSet<AssetViewModel>();
@@ -64,7 +64,7 @@ namespace Stride.GameStudio.Tests
var result = service.DeserializeCopiedData(copiedText, asset, expectedType);
Assert.NotNull(result);
Assert.NotNull(result.Items);
Assert.Equal(1, result.Items.Count);
Assert.Single(result.Items);
var item = result.Items[0];
Assert.NotNull(item);
@@ -137,7 +137,7 @@ namespace Stride.GameStudio.Tests
var data = service.DeserializeCopiedData(text, assetGraph.Asset, typeof(Entity));
Assert.NotNull(data);
Assert.NotNull(data.Items);
Assert.Equal(1, data.Items.Count);
Assert.Single(data.Items);
var item = data.Items[0];
Assert.NotNull(item);
@@ -23,7 +23,7 @@ namespace Stride.GameStudio.Debugging
{
public partial class AssemblyRecompiler
{
private SourceGroup[] previousSortedConnectedGroups = new SourceGroup[0];
private SourceGroup[] previousSortedConnectedGroups = [];
private IMutableBidirectionalGraph<SourceGroup, CondensedEdge<SyntaxTree, SEdge<SyntaxTree>, SourceGroup>> previousStronglyConnected = new BidirectionalGraph<SourceGroup, CondensedEdge<SyntaxTree, SEdge<SyntaxTree>, SourceGroup>>();
private ImmutableHashSet<SourceGroup> previousConnectedGroups;
private int assemblyCounter;
@@ -53,7 +53,7 @@ namespace Stride.GameStudio.Debugging
foreach (var syntaxTree in gameProjectCompilation.SyntaxTrees)
{
var syntaxRoot = syntaxTree.GetRoot();
var syntaxRoot = await syntaxTree.GetRootAsync();
var semanticModel = gameProjectCompilation.GetSemanticModel(syntaxTree);
var dependencies = new SourceDependencySyntaxVisitor(new HashSet<SyntaxTree>(gameProjectCompilation.SyntaxTrees), semanticModel).DefaultVisit(syntaxRoot);
@@ -106,7 +106,7 @@ namespace Stride.GameStudio.Debugging
for (int i = 0; i < 1000; i++)
internalsVisibleToBuilder.AppendFormat(@"[assembly: InternalsVisibleTo(""{0}.Part{1}"")]", gameProject.AssemblyName, i);
var internalsVisibleToSource = CSharpSyntaxTree.ParseText(internalsVisibleToBuilder.ToString(), null, "", Encoding.UTF8).GetText();
var internalsVisibleToSource = await CSharpSyntaxTree.ParseText(internalsVisibleToBuilder.ToString(), null, "", Encoding.UTF8).GetTextAsync();
// 2. Compile assemblies
foreach (var sourceGroup in sortedConnectedGroups.Reverse())
@@ -125,7 +125,7 @@ namespace Stride.GameStudio.Debugging
// Add sources
foreach (var syntaxTree in sourceGroup.Vertices)
{
project = project.AddDocument(syntaxTree.FilePath, syntaxTree.GetText()).Project;
project = project.AddDocument(syntaxTree.FilePath, await syntaxTree.GetTextAsync()).Project;
}
// Add references to other sources
@@ -25,5 +25,6 @@ TemplateFolders:
- !file ../../../samples/Templates/ThirdPersonPlatformer/ThirdPersonPlatformer/ThirdPersonPlatformer.sdtpl
- !file ../../../samples/Templates/TopDownRPG/TopDownRPG/TopDownRPG.sdtpl
- !file ../../../samples/Templates/VRSandbox/VRSandbox/VRSandbox.sdtpl
- !file ../../../samples/Templates/Platformer2D/Platformer2D/Platformer2D.sdtpl
- !file ../../../samples/Tutorials/CSharpBeginner/CSharpBeginner/CSharpBeginner.sdtpl
- !file ../../../samples/Tutorials/CSharpIntermediate/CSharpIntermediate/CSharpIntermediate.sdtpl
@@ -33,7 +33,7 @@ namespace Stride.Assets.Models
if (!importParameters.InputParameters.TryGet(DeduplicateMaterialsKey, out var deduplicateMaterials))
deduplicateMaterials = true; // Dedupe is the default value
var entityInfo = meshConverter.ExtractEntity(localPath.FullPath, null, importParameters.IsTypeSelectedForOutput(typeof(TextureAsset)), deduplicateMaterials);
var entityInfo = meshConverter.ExtractEntity(localPath.FullPath, null, importParameters.IsTypeSelectedForOutput<TextureAsset>(), deduplicateMaterials);
return entityInfo;
}
@@ -34,7 +34,7 @@ namespace Stride.Assets
// If we have a command line override, use it first
string graphicsApi;
if (context.OptionProperties.TryGetValue("StrideGraphicsApi", out graphicsApi))
return (GraphicsPlatform)Enum.Parse(typeof(GraphicsPlatform), graphicsApi);
return Enum.Parse<GraphicsPlatform>(graphicsApi);
// Ohterwise, use default as fallback
return context.Platform.GetDefaultGraphicsPlatform();
@@ -331,7 +331,7 @@ namespace Stride.Assets.Navigation
object loadedHeightfieldInitialData;
if (!loadedHeightfieldInitialDatas.TryGetValue(assetReference.Url, out loadedHeightfieldInitialData))
{
loadedHeightfieldInitialData = contentManager.Load(typeof(Heightmap), assetReference.Url);
loadedHeightfieldInitialData = contentManager.Load<Heightmap>(assetReference.Url);
loadedHeightfieldInitialDatas.Add(assetReference.Url, loadedHeightfieldInitialData);
}
heightmapSource.Heightmap = loadedHeightfieldInitialData as Heightmap;
@@ -88,7 +88,7 @@ namespace Stride.Assets.Textures.Packing
results[bestAlgorithm] = new List<AtlasTextureLayout>(atlasTextureLayouts);
foreach (var heuristicMethod in (TexturePackingMethod[])Enum.GetValues(typeof(TexturePackingMethod)))
foreach (var heuristicMethod in Enum.GetValues<TexturePackingMethod>())
{
if (heuristicMethod == TexturePackingMethod.Best || heuristicMethod == TexturePackingMethod.BestShortSideFit)
continue;
@@ -260,7 +260,7 @@ namespace Stride.Audio
//read one packet, size first, then data
var len = reader.ReadInt16();
compressedSoundStream.Read(compressedBuffer, 0, len);
compressedSoundStream.ReadExactly(compressedBuffer, 0, len);
currentPacketIndex++;
var writePtr = bufferPtr + offset;
@@ -36,7 +36,7 @@ public class DebugRenderProcessor : EntityProcessor<DebugRenderComponent>
get => _visible;
set
{
if (_sceneSystem.SceneInstance.GetProcessor<CollidableProcessor>() is { } proc)
if (_sceneSystem.SceneInstance.GetProcessor<CollidableProcessor>() is { } proc && _visibilityGroup is not null)
{
if (_visible == value)
return;
@@ -66,7 +66,7 @@ public class DebugRenderProcessor : EntityProcessor<DebugRenderComponent>
protected override void OnEntityComponentAdding(Entity entity, DebugRenderComponent component, DebugRenderComponent data)
{
base.OnEntityComponentAdding(entity, component, data);
if (_sceneSystem.SceneInstance.GetProcessor<CollidableProcessor>() is not null)
if (_sceneSystem?.SceneInstance?.GetProcessor<CollidableProcessor>() is not null && _visibilityGroup is not null)
Visible = component.Visible;
else if (component.Visible)
_latent = true;
@@ -76,19 +76,9 @@ public class DebugRenderProcessor : EntityProcessor<DebugRenderComponent>
protected override void OnSystemAdd()
{
SinglePassWireframeRenderFeature wireframeRenderFeature;
_shapeCacheSystem = Services.GetOrCreate<ShapeCacheSystem>();
_game = Services.GetSafeServiceAs<IGame>();
_sceneSystem = Services.GetSafeServiceAs<SceneSystem>();
if (_sceneSystem.GraphicsCompositor.RenderFeatures.OfType<SinglePassWireframeRenderFeature>().FirstOrDefault() is null)
{
wireframeRenderFeature = new();
_sceneSystem.GraphicsCompositor.RenderFeatures.Add(wireframeRenderFeature);
}
_visibilityGroup = _sceneSystem.SceneInstance.VisibilityGroups.First();
}
protected override void OnSystemRemove()
@@ -98,6 +88,18 @@ public class DebugRenderProcessor : EntityProcessor<DebugRenderComponent>
public override void Draw(RenderContext context)
{
if (_visibilityGroup is null)
{
if (_sceneSystem.SceneInstance.VisibilityGroups.Count == 0)
return;
_visibilityGroup = _sceneSystem.SceneInstance.VisibilityGroups.First();
if (_sceneSystem.GraphicsCompositor.RenderFeatures.OfType<SinglePassWireframeRenderFeature>().FirstOrDefault() is null)
{
_sceneSystem.GraphicsCompositor.RenderFeatures.Add(new SinglePassWireframeRenderFeature());
}
}
if (_latent)
{
Visible = true;
@@ -15,6 +15,8 @@ using Stride.Core.Mathematics;
using Xunit;
using Stride.Engine;
using Stride.Graphics.Regression;
using System.Threading.Tasks;
using System.Threading;
namespace Stride.BepuPhysics.Tests
{
@@ -125,6 +127,56 @@ namespace Stride.BepuPhysics.Tests
RunGameTest(game);
}
[Fact]
public static void ThreadContextTest()
{
var game = new GameTest();
game.Script.AddTask(async () =>
{
var thread = Thread.CurrentThread;
game.ScreenShotAutomationEnabled = false;
var e1 = new BodyComponent { Collider = new CompoundCollider { Colliders = { new BoxCollider() } } };
var e2 = new BodyComponent { Collider = new CompoundCollider { Colliders = { new BoxCollider() } } };
var e3 = new BodyComponent { Collider = new CompoundCollider { Colliders = { new BoxCollider() } } };
game.SceneSystem.SceneInstance.RootScene.Entities.AddRange(new EntityComponent[] { e1, e2, e3 }.Select(x => new Entity { x }));
await Task.Run(() =>
{
Assert.NotEqual(Thread.CurrentThread, thread);
});
Assert.Equal(Thread.CurrentThread, thread);
await e1.Simulation!.AfterUpdate();
Assert.Equal(Thread.CurrentThread, thread);
await Task.Run(() =>
{
Assert.NotEqual(Thread.CurrentThread, thread);
});
Assert.Equal(Thread.CurrentThread, thread);
await e1.Simulation!.NextUpdate();
Assert.Equal(Thread.CurrentThread, thread);
await Task.Run(() =>
{
Assert.NotEqual(Thread.CurrentThread, thread);
});
Assert.Equal(Thread.CurrentThread, thread);
game.Exit();
});
RunGameTest(game);
}
[Fact]
public static void ConstraintsForceTest()
{
@@ -293,7 +345,7 @@ namespace Stride.BepuPhysics.Tests
while (contactE.Exit == false)
await simulation.AfterUpdate();
Assert.NotEmpty(contactE.ImpactForces.Where(x => x.Length() > 100));
Assert.Contains(contactE.ImpactForces, x => x.Length() > 100);
game.Exit();
});

Some files were not shown because too many files have changed in this diff Show More