diff --git a/.gitignore b/.gitignore index a169ee8..03d417a 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ x64/ *.vspscc *.vssscc .builds - +*.vs # Visual C++ cache files ipch/ *.aps @@ -115,6 +115,5 @@ UpgradeLog*.XML /Assembly-CSharp.pidb /Assembly-CSharp-Editor-vs.csproj /Assembly-CSharp-Editor.csproj -/*.sln /*.userprefs /Temp diff --git a/.vs/UniLuaX/v17/DocumentLayout.json b/.vs/UniLuaX/v17/DocumentLayout.json new file mode 100644 index 0000000..8ca89ca --- /dev/null +++ b/.vs/UniLuaX/v17/DocumentLayout.json @@ -0,0 +1,216 @@ +{ + "Version": 1, + "WorkspaceRootPath": "H:\\project2\\UniLua\\", + "Documents": [ + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|C:\\Users\\kmsmg\\AppData\\Local\\Temp\\.vsdbgsrc\\588fd4cd1320d504abfcbd7bb1f0e198f8b5eec657267f804bb540676e7671d3\\ElementUtil.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{F314A553-F4D4-4E2D-A860-CFE3AFDC1C04}|GrinderApp\\GrinderApp\\GrinderApp.csproj|h:\\project2\\unilua\\grinderapp\\grinderapp\\app.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{F314A553-F4D4-4E2D-A860-CFE3AFDC1C04}|GrinderApp\\GrinderApp\\GrinderApp.csproj|solutionrelative:grinderapp\\grinderapp\\app.xaml.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{F314A553-F4D4-4E2D-A860-CFE3AFDC1C04}|GrinderApp\\GrinderApp\\GrinderApp.csproj|h:\\project2\\unilua\\grinderapp\\grinderapp\\grinderapp.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|", + "RelativeMoniker": "D:0:0:{F314A553-F4D4-4E2D-A860-CFE3AFDC1C04}|GrinderApp\\GrinderApp\\GrinderApp.csproj|solutionrelative:grinderapp\\grinderapp\\grinderapp.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|" + }, + { + "AbsoluteMoniker": "D:0:0:{F314A553-F4D4-4E2D-A860-CFE3AFDC1C04}|GrinderApp\\GrinderApp\\GrinderApp.csproj|h:\\project2\\unilua\\grinderapp\\grinderapp\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}", + "RelativeMoniker": "D:0:0:{F314A553-F4D4-4E2D-A860-CFE3AFDC1C04}|GrinderApp\\GrinderApp\\GrinderApp.csproj|solutionrelative:grinderapp\\grinderapp\\app.xaml||{F11ACC28-31D1-4C80-A34B-F4E09D3D753C}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\project2\\UniLua\\GrinderApp\\Modules\\GrinderApp.Modules.ModuleName\\ModuleNameModule.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:GrinderApp\\Modules\\GrinderApp.Modules.ModuleName\\ModuleNameModule.cs||{A6C744A8-0E4A-4FC6-886A-064283054674}" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\project2\\UniLua\\GrinderApp\\Services\\GrinderApp.Services\\GrinderApp.Services.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:GrinderApp\\Services\\GrinderApp.Services\\GrinderApp.Services.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\project2\\UniLua\\GrinderApp\\Services\\GrinderApp.Services.Interfaces\\GrinderApp.Services.Interfaces.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:GrinderApp\\Services\\GrinderApp.Services.Interfaces\\GrinderApp.Services.Interfaces.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\project2\\UniLua\\GrinderApp\\Tests\\GrinderApp.Modules.ModuleName.Tests\\GrinderApp.Modules.ModuleName.Tests.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:GrinderApp\\Tests\\GrinderApp.Modules.ModuleName.Tests\\GrinderApp.Modules.ModuleName.Tests.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|" + }, + { + "AbsoluteMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|H:\\project2\\UniLua\\GrinderApp\\GrinderApp.Core\\GrinderApp.Core.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|", + "RelativeMoniker": "D:0:0:{A2FE74E1-B743-11D0-AE1A-00A0C90FFFC3}|\u003CMiscFiles\u003E|solutionrelative:GrinderApp\\GrinderApp.Core\\GrinderApp.Core.csproj||{04B8AB82-A572-4FEF-95CE-5222444B6B64}|" + } + ], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 247, + "SelectedChildIndex": 8, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{004be353-6879-467c-9d1e-9ac23cdf6d49}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}" + }, + { + "$type": "Bookmark", + "Name": "ST:9:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + }, + { + "$type": "Bookmark", + "Name": "ST:10:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + }, + { + "$type": "Bookmark", + "Name": "ST:1:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{cf577b8c-4134-11d2-83e5-00c04f9902c1}" + }, + { + "$type": "Document", + "DocumentIndex": 0, + "Title": "ElementUtil.cs", + "DocumentMoniker": "C:\\Users\\kmsmg\\AppData\\Local\\Temp\\.vsdbgsrc\\588fd4cd1320d504abfcbd7bb1f0e198f8b5eec657267f804bb540676e7671d3\\ElementUtil.cs", + "ToolTip": "C:\\Users\\kmsmg\\AppData\\Local\\Temp\\.vsdbgsrc\\588fd4cd1320d504abfcbd7bb1f0e198f8b5eec657267f804bb540676e7671d3\\ElementUtil.cs", + "ViewState": "AgIAAHQAAAAAAAAAAADgv5sAAAADAAAAAQAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2024-12-19T09:05:27.556Z", + "EditorCaption": "" + }, + { + "$type": "Document", + "DocumentIndex": 2, + "Title": "GrinderApp", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp\\GrinderApp.csproj", + "RelativeDocumentMoniker": "GrinderApp\\GrinderApp\\GrinderApp.csproj", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp\\GrinderApp.csproj", + "RelativeToolTip": "GrinderApp\\GrinderApp\\GrinderApp.csproj", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|", + "WhenOpened": "2024-12-19T08:46:24.809Z" + }, + { + "$type": "Document", + "DocumentIndex": 4, + "Title": "ModuleNameModule.cs", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\Modules\\GrinderApp.Modules.ModuleName\\ModuleNameModule.cs", + "RelativeDocumentMoniker": "GrinderApp\\Modules\\GrinderApp.Modules.ModuleName\\ModuleNameModule.cs", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\Modules\\GrinderApp.Modules.ModuleName\\ModuleNameModule.cs", + "RelativeToolTip": "GrinderApp\\Modules\\GrinderApp.Modules.ModuleName\\ModuleNameModule.cs", + "ViewState": "AgIAAAYAAAAAAAAAAAAAAAgAAAAhAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2024-12-19T08:39:04.636Z" + }, + { + "$type": "Document", + "DocumentIndex": 1, + "Title": "App.xaml.cs", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp\\App.xaml.cs", + "RelativeDocumentMoniker": "GrinderApp\\GrinderApp\\App.xaml.cs", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp\\App.xaml.cs", + "RelativeToolTip": "GrinderApp\\GrinderApp\\App.xaml.cs", + "ViewState": "AgIAAA4AAAAAAAAAAAAUwBAAAAAJAAAAAAAAAA==", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000738|", + "WhenOpened": "2024-12-19T08:36:28.664Z" + }, + { + "$type": "Document", + "DocumentIndex": 3, + "Title": "App.xaml", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp\\App.xaml", + "RelativeDocumentMoniker": "GrinderApp\\GrinderApp\\App.xaml", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp\\App.xaml", + "RelativeToolTip": "GrinderApp\\GrinderApp\\App.xaml", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.003549|", + "WhenOpened": "2024-12-19T08:35:59.7Z" + }, + { + "$type": "Document", + "DocumentIndex": 5, + "Title": "GrinderApp.Services.csproj", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\Services\\GrinderApp.Services\\GrinderApp.Services.csproj", + "RelativeDocumentMoniker": "GrinderApp\\Services\\GrinderApp.Services\\GrinderApp.Services.csproj", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\Services\\GrinderApp.Services\\GrinderApp.Services.csproj", + "RelativeToolTip": "GrinderApp\\Services\\GrinderApp.Services\\GrinderApp.Services.csproj", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|", + "WhenOpened": "2024-12-19T08:29:00.689Z" + }, + { + "$type": "Document", + "DocumentIndex": 6, + "Title": "GrinderApp.Services.Interfaces.csproj", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\Services\\GrinderApp.Services.Interfaces\\GrinderApp.Services.Interfaces.csproj", + "RelativeDocumentMoniker": "GrinderApp\\Services\\GrinderApp.Services.Interfaces\\GrinderApp.Services.Interfaces.csproj", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\Services\\GrinderApp.Services.Interfaces\\GrinderApp.Services.Interfaces.csproj", + "RelativeToolTip": "GrinderApp\\Services\\GrinderApp.Services.Interfaces\\GrinderApp.Services.Interfaces.csproj", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|", + "WhenOpened": "2024-12-19T08:28:44.14Z" + }, + { + "$type": "Document", + "DocumentIndex": 7, + "Title": "GrinderApp.Modules.ModuleName.Tests.csproj", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\Tests\\GrinderApp.Modules.ModuleName.Tests\\GrinderApp.Modules.ModuleName.Tests.csproj", + "RelativeDocumentMoniker": "GrinderApp\\Tests\\GrinderApp.Modules.ModuleName.Tests\\GrinderApp.Modules.ModuleName.Tests.csproj", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\Tests\\GrinderApp.Modules.ModuleName.Tests\\GrinderApp.Modules.ModuleName.Tests.csproj", + "RelativeToolTip": "GrinderApp\\Tests\\GrinderApp.Modules.ModuleName.Tests\\GrinderApp.Modules.ModuleName.Tests.csproj", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|", + "WhenOpened": "2024-12-19T08:28:24.338Z" + }, + { + "$type": "Document", + "DocumentIndex": 8, + "Title": "GrinderApp.Core.csproj", + "DocumentMoniker": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp.Core\\GrinderApp.Core.csproj", + "RelativeDocumentMoniker": "GrinderApp\\GrinderApp.Core\\GrinderApp.Core.csproj", + "ToolTip": "H:\\project2\\UniLua\\GrinderApp\\GrinderApp.Core\\GrinderApp.Core.csproj", + "RelativeToolTip": "GrinderApp\\GrinderApp.Core\\GrinderApp.Core.csproj", + "Icon": "ae27a6b0-e345-4288-96df-5eaf394ee369.000758|", + "WhenOpened": "2024-12-19T08:27:40.538Z" + } + ] + }, + { + "DockedWidth": 198, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:128:0:{1fc202d4-d401-403c-9834-5b218574bb67}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{aa2115a1-9712-457b-9047-dbb71ca2cdd2}" + }, + { + "$type": "Bookmark", + "Name": "ST:3:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + } + ] + }, + { + "DockedWidth": 267, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{e1b7d1f8-9b3c-49b1-8f4f-bfc63a88835d}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Assets/Behaviour.meta b/Assets/Behaviour.meta deleted file mode 100644 index a97a3a7..0000000 --- a/Assets/Behaviour.meta +++ /dev/null @@ -1,4 +0,0 @@ -fileFormatVersion: 2 -guid: d7daa4568f9cb534f89a493483113005 -DefaultImporter: - userData: diff --git a/Assets/Editor.meta b/Assets/Editor.meta deleted file mode 100644 index 7065fec..0000000 --- a/Assets/Editor.meta +++ /dev/null @@ -1,5 +0,0 @@ -fileFormatVersion: 2 -guid: 3b0b1dff7db84464b9ca2706fd4fb39f -folderAsset: yes -DefaultImporter: - userData: diff --git a/Assets/Resources.meta b/Assets/Resources.meta deleted file mode 100644 index 6a039a4..0000000 --- a/Assets/Resources.meta +++ /dev/null @@ -1,4 +0,0 @@ -fileFormatVersion: 2 -guid: fda4ce3ccdcf24d4794cea8c8369c73b -DefaultImporter: - userData: diff --git a/Assets/Stages.meta b/Assets/Stages.meta deleted file mode 100644 index a5e91bf..0000000 --- a/Assets/Stages.meta +++ /dev/null @@ -1,4 +0,0 @@ -fileFormatVersion: 2 -guid: 68d0e371377a43c4291d80ec510d1c14 -DefaultImporter: - userData: diff --git a/Assets/StreamingAssets.meta b/Assets/StreamingAssets.meta deleted file mode 100644 index b3ab26c..0000000 --- a/Assets/StreamingAssets.meta +++ /dev/null @@ -1,4 +0,0 @@ -fileFormatVersion: 2 -guid: 4dd5041feafaf57428c9e95e6902147e -DefaultImporter: - userData: diff --git a/Assets/Tools.meta b/Assets/Tools.meta deleted file mode 100644 index 99affc9..0000000 --- a/Assets/Tools.meta +++ /dev/null @@ -1,4 +0,0 @@ -fileFormatVersion: 2 -guid: a22a00b92f35fba40b0bf47ff2c2f360 -DefaultImporter: - userData: diff --git a/Assets/UniLua.meta b/Assets/UniLua.meta deleted file mode 100644 index 3aee89c..0000000 --- a/Assets/UniLua.meta +++ /dev/null @@ -1,4 +0,0 @@ -fileFormatVersion: 2 -guid: 9196ce4e34714564ea3f80dcc1f5789f -DefaultImporter: - userData: diff --git a/Assets/UniLua/.vs/ProjectSettings.json b/Assets/UniLua/.vs/ProjectSettings.json new file mode 100644 index 0000000..f8b4888 --- /dev/null +++ b/Assets/UniLua/.vs/ProjectSettings.json @@ -0,0 +1,3 @@ +{ + "CurrentProjectSetting": null +} \ No newline at end of file diff --git a/Assets/UniLua/.vs/UniLua/FileContentIndex/6b17d053-e231-473c-b85c-c8b40fa403b4.vsidx b/Assets/UniLua/.vs/UniLua/FileContentIndex/6b17d053-e231-473c-b85c-c8b40fa403b4.vsidx new file mode 100644 index 0000000..c851d4d Binary files /dev/null and b/Assets/UniLua/.vs/UniLua/FileContentIndex/6b17d053-e231-473c-b85c-c8b40fa403b4.vsidx differ diff --git a/Assets/UniLua/.vs/UniLua/FileContentIndex/b0b9c650-9b98-4f86-b71d-f8224da3660b.vsidx b/Assets/UniLua/.vs/UniLua/FileContentIndex/b0b9c650-9b98-4f86-b71d-f8224da3660b.vsidx new file mode 100644 index 0000000..4e4eb08 Binary files /dev/null and b/Assets/UniLua/.vs/UniLua/FileContentIndex/b0b9c650-9b98-4f86-b71d-f8224da3660b.vsidx differ diff --git a/Assets/UniLua/.vs/UniLua/FileContentIndex/c6591743-cbad-45d4-b47a-5ee1205bdefb.vsidx b/Assets/UniLua/.vs/UniLua/FileContentIndex/c6591743-cbad-45d4-b47a-5ee1205bdefb.vsidx new file mode 100644 index 0000000..8e489fd Binary files /dev/null and b/Assets/UniLua/.vs/UniLua/FileContentIndex/c6591743-cbad-45d4-b47a-5ee1205bdefb.vsidx differ diff --git a/Assets/UniLua/.vs/UniLua/v17/.futdcache.v2 b/Assets/UniLua/.vs/UniLua/v17/.futdcache.v2 new file mode 100644 index 0000000..580fa99 Binary files /dev/null and b/Assets/UniLua/.vs/UniLua/v17/.futdcache.v2 differ diff --git a/Assets/UniLua/.vs/UniLua/v17/.wsuo b/Assets/UniLua/.vs/UniLua/v17/.wsuo new file mode 100644 index 0000000..b49dcdd Binary files /dev/null and b/Assets/UniLua/.vs/UniLua/v17/.wsuo differ diff --git a/Assets/UniLua/.vs/UniLua/v17/DocumentLayout.json b/Assets/UniLua/.vs/UniLua/v17/DocumentLayout.json new file mode 100644 index 0000000..2a06618 --- /dev/null +++ b/Assets/UniLua/.vs/UniLua/v17/DocumentLayout.json @@ -0,0 +1,61 @@ +{ + "Version": 1, + "WorkspaceRootPath": "H:\\project2\\UniLua\\Assets\\UniLua\\", + "Documents": [], + "DocumentGroupContainers": [ + { + "Orientation": 0, + "VerticalTabListWidth": 256, + "DocumentGroups": [ + { + "DockedWidth": 414, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:0:0:{004be353-6879-467c-9d1e-9ac23cdf6d49}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{d78612c7-9962-4b83-95d9-268046dad23a}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{34e76e81-ee4a-11d0-ae2e-00a0c90fffc3}" + }, + { + "$type": "Bookmark", + "Name": "ST:9:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + }, + { + "$type": "Bookmark", + "Name": "ST:10:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{a80febb4-e7e0-4147-b476-21aaf2453969}" + }, + { + "$type": "Bookmark", + "Name": "ST:1:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + }, + { + "$type": "Bookmark", + "Name": "ST:0:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + } + ] + }, + { + "DockedWidth": 17, + "SelectedChildIndex": -1, + "Children": [ + { + "$type": "Bookmark", + "Name": "ST:3:0:{57d563b6-44a5-47df-85be-f4199ad6b651}" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/Assets/UniLua/.vs/UniLuaX/DesignTimeBuild/.dtbcache.v2 b/Assets/UniLua/.vs/UniLuaX/DesignTimeBuild/.dtbcache.v2 new file mode 100644 index 0000000..ace692a Binary files /dev/null and b/Assets/UniLua/.vs/UniLuaX/DesignTimeBuild/.dtbcache.v2 differ diff --git a/Assets/UniLua/.vs/UniLuaX/FileContentIndex/aeefe867-2127-4859-a58f-e4b2d051e1bb.vsidx b/Assets/UniLua/.vs/UniLuaX/FileContentIndex/aeefe867-2127-4859-a58f-e4b2d051e1bb.vsidx new file mode 100644 index 0000000..631e4c5 Binary files /dev/null and b/Assets/UniLua/.vs/UniLuaX/FileContentIndex/aeefe867-2127-4859-a58f-e4b2d051e1bb.vsidx differ diff --git a/Assets/UniLua/.vs/UniLuaX/v17/.futdcache.v2 b/Assets/UniLua/.vs/UniLuaX/v17/.futdcache.v2 new file mode 100644 index 0000000..758db89 Binary files /dev/null and b/Assets/UniLua/.vs/UniLuaX/v17/.futdcache.v2 differ diff --git a/Assets/UniLua/.vs/VSWorkspaceState.json b/Assets/UniLua/.vs/VSWorkspaceState.json new file mode 100644 index 0000000..186c9a9 --- /dev/null +++ b/Assets/UniLua/.vs/VSWorkspaceState.json @@ -0,0 +1,5 @@ +{ + "ExpandedNodes": [], + "SelectedNode": "\\H:\\project2\\UniLua\\Assets\\UniLua", + "PreviewInSolutionExplorer": false +} \ No newline at end of file diff --git a/Assets/UniLua/.vs/slnx.sqlite b/Assets/UniLua/.vs/slnx.sqlite new file mode 100644 index 0000000..ebbc483 Binary files /dev/null and b/Assets/UniLua/.vs/slnx.sqlite differ diff --git a/Assets/UniLua/ByteString.cs.meta b/Assets/UniLua/ByteString.cs.meta deleted file mode 100644 index 4b2d295..0000000 --- a/Assets/UniLua/ByteString.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 3471e05d045ce3b46a61b611ded6ea72 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/Coder.cs.meta b/Assets/UniLua/Coder.cs.meta deleted file mode 100644 index 499f336..0000000 --- a/Assets/UniLua/Coder.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 620d75cf2c2663c4fa41ae320bb9ab5d -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/Common.cs.meta b/Assets/UniLua/Common.cs.meta deleted file mode 100644 index 666ce91..0000000 --- a/Assets/UniLua/Common.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: db0ce5a0e23232d4eacc46d0cf7f2390 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/Do.cs.meta b/Assets/UniLua/Do.cs.meta deleted file mode 100644 index 1b22b61..0000000 --- a/Assets/UniLua/Do.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 6252605096eed104b922bc2b8cea49c6 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/Dump.cs.meta b/Assets/UniLua/Dump.cs.meta deleted file mode 100644 index 2267d03..0000000 --- a/Assets/UniLua/Dump.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7d328a17237537a4fa68158d34db051f -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LLex.cs.meta b/Assets/UniLua/LLex.cs.meta deleted file mode 100644 index e12b418..0000000 --- a/Assets/UniLua/LLex.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 45c6de50fce82bc4fb1287ae03999b43 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaAPI.cs.meta b/Assets/UniLua/LuaAPI.cs.meta deleted file mode 100644 index 13ab246..0000000 --- a/Assets/UniLua/LuaAPI.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: e8b2a12de52517d4685b26b9ef628356 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaAuxLib.cs b/Assets/UniLua/LuaAuxLib.cs index 7176e8a..fb3802c 100644 --- a/Assets/UniLua/LuaAuxLib.cs +++ b/Assets/UniLua/LuaAuxLib.cs @@ -1,725 +1,727 @@  namespace UniLua { - using System; - using System.IO; - using System.Collections.Generic; - - public struct NameFuncPair - { - public string Name; - public CSharpFunctionDelegate Func; - - public NameFuncPair( string name, CSharpFunctionDelegate func ) - { - Name = name; - Func = func; - } - } - - public interface ILuaAuxLib - { - void L_Where( int level ); - int L_Error( string fmt, params object[] args ); - void L_CheckStack( int size, string msg ); - void L_CheckAny( int narg ); - void L_CheckType( int index, LuaType t ); - double L_CheckNumber( int narg ); - UInt64 L_CheckUInt64( int narg ); - int L_CheckInteger( int narg ); - string L_CheckString( int narg ); - uint L_CheckUnsigned( int narg ); - void L_ArgCheck( bool cond, int narg, string extraMsg ); - int L_ArgError( int narg, string extraMsg ); - string L_TypeName( int index ); - - string L_ToString( int index ); - bool L_GetMetaField( int index, string method ); - int L_GetSubTable( int index, string fname ); - - void L_RequireF( string moduleName, CSharpFunctionDelegate openFunc, bool global ); - void L_OpenLibs(); - void L_NewLibTable( NameFuncPair[] define ); - void L_NewLib( NameFuncPair[] define ); - void L_SetFuncs( NameFuncPair[] define, int nup ); - - T L_Opt( Func f, int n, T def ); - int L_OptInt( int narg, int def ); - string L_OptString( int narg, string def ); - bool L_CallMeta( int obj, string name ); - void L_Traceback( ILuaState otherLua, string msg, int level ); - int L_Len( int index ); - - ThreadStatus L_LoadBuffer( string s, string name ); - ThreadStatus L_LoadBufferX( string s, string name, string mode ); - ThreadStatus L_LoadFile( string filename ); - ThreadStatus L_LoadFileX( string filename, string mode ); - - ThreadStatus L_LoadString( string s ); - ThreadStatus L_LoadBytes( byte[] bytes, string name ); - ThreadStatus L_DoString( string s ); - ThreadStatus L_DoFile( string filename ); - - - string L_Gsub( string src, string pattern, string rep ); - - // reference system - int L_Ref( int t ); - void L_Unref( int t, int reference ); - } - - class StringLoadInfo : ILoadInfo - { - public StringLoadInfo(string s ) - { - Str = s; - Pos = 0; - } - - public int ReadByte() - { - if( Pos >= Str.Length ) - return -1; - else - return Str[Pos++]; - } - - public int PeekByte() - { - if( Pos >= Str.Length ) - return -1; - else - return Str[Pos]; - } - - private string Str; - private int Pos; - } - - class BytesLoadInfo : ILoadInfo - { - public BytesLoadInfo( byte[] bytes ) - { - Bytes = bytes; - Pos = 0; - } - - public int ReadByte() - { - if( Pos >= Bytes.Length ) - return -1; - else - return Bytes[Pos++]; - } - - public int PeekByte() - { - if( Pos >= Bytes.Length ) - return -1; - else - return Bytes[Pos]; - } - - private byte[] Bytes; - private int Pos; - } - - public partial class LuaState - { - private const int LEVELS1 = 12; // size of the first part of the stack - private const int LEVELS2 = 10; // size of the second part of the stack - - public void L_Where( int level ) - { - LuaDebug ar = new LuaDebug(); - if( API.GetStack( level, ar ) ) // check function at level - { - GetInfo( "Sl", ar ); // get info about it - if( ar.CurrentLine > 0 ) // is there info? - { - API.PushString( string.Format( "{0}:{1}: ", ar.ShortSrc, ar.CurrentLine ) ); - return; - } - } - API.PushString( "" ); // else, no information available... - } - - public int L_Error( string fmt, params object[] args ) - { - L_Where( 1 ); - API.PushString( string.Format( fmt, args ) ); - API.Concat( 2 ); - return API.Error(); - } - - public void L_CheckStack( int size, string msg ) - { - // keep some extra space to run error routines, if needed - if(!API.CheckStack(size + LuaDef.LUA_MINSTACK)) { - if(msg != null) - { L_Error(string.Format("stack overflow ({0})", msg)); } - else - { L_Error("stack overflow"); } - } - } - - public void L_CheckAny( int narg ) - { - if( API.Type( narg ) == LuaType.LUA_TNONE ) - L_ArgError( narg, "value expected" ); - } - - public double L_CheckNumber( int narg ) - { - bool isnum; - double d = API.ToNumberX( narg, out isnum ); - if( !isnum ) - TagError( narg, LuaType.LUA_TNUMBER ); - return d; - } - - public UInt64 L_CheckUInt64( int narg ) - { - bool isnum; - UInt64 v = API.ToUInt64X( narg, out isnum ); - if( !isnum ) - TagError( narg, LuaType.LUA_TUINT64 ); - return v; - } - - public int L_CheckInteger( int narg ) - { - bool isnum; - int d = API.ToIntegerX( narg, out isnum ); - if( !isnum ) - TagError( narg, LuaType.LUA_TNUMBER ); - return d; - } - - public string L_CheckString( int narg ) - { - string s = API.ToString( narg ); - if( s == null ) TagError( narg, LuaType.LUA_TSTRING ); - return s; - } - - public uint L_CheckUnsigned( int narg ) - { - bool isnum; - uint d = API.ToUnsignedX( narg, out isnum ); - if( !isnum ) - TagError( narg, LuaType.LUA_TNUMBER ); - return d; - } - - public T L_Opt( Func f, int n, T def ) - { - LuaType t = API.Type( n ); - if( t == LuaType.LUA_TNONE || - t == LuaType.LUA_TNIL ) - { - return def; - } - else - { - return f( n ); - } - } - - public int L_OptInt( int narg, int def ) - { - LuaType t = API.Type( narg ); - if( t == LuaType.LUA_TNONE || - t == LuaType.LUA_TNIL ) - { - return def; - } - else - { - return L_CheckInteger( narg ); - } - } - - public string L_OptString( int narg, string def ) - { - LuaType t = API.Type( narg ); - if( t == LuaType.LUA_TNONE || - t == LuaType.LUA_TNIL ) - { - return def; - } - else - { - return L_CheckString( narg ); - } - } - - private int TypeError( int index, string typeName ) - { - string msg = string.Format( "{0} expected, got {1}", - typeName, L_TypeName( index ) ); - API.PushString( msg ); - return L_ArgError( index, msg ); - } - - private void TagError( int index, LuaType t ) - { - TypeError( index, API.TypeName( t ) ); - } - - public void L_CheckType( int index, LuaType t ) - { - if( API.Type( index ) != t ) - TagError( index, t ); - } - - public void L_ArgCheck( bool cond, int narg, string extraMsg ) - { - if( !cond ) - L_ArgError( narg, extraMsg ); - } - - public int L_ArgError( int narg, string extraMsg ) - { - - LuaDebug ar = new LuaDebug(); - if( !API.GetStack( 0, ar ) ) // no stack frame ? - return L_Error( "bad argument {0} ({1})", narg, extraMsg ); - - GetInfo( "n", ar ); - if( ar.NameWhat == "method" ) - { - narg--; // do not count 'self' - if( narg == 0 ) // error is in the self argument itself? - return L_Error( "calling '{0}' on bad self", ar.Name ); - } - if( ar.Name == null ) - ar.Name = PushGlobalFuncName( ar ) ? API.ToString(-1) : "?"; - return L_Error( "bad argument {0} to '{1}' ({2})", - narg, ar.Name, extraMsg ); - } - - public string L_TypeName( int index ) - { - return API.TypeName( API.Type( index ) ); - } - - public bool L_GetMetaField( int obj, string name ) - { - if( !API.GetMetaTable(obj) ) // no metatable? - return false; - API.PushString( name ); - API.RawGet( -2 ); - if( API.IsNil( -1 ) ) - { - API.Pop( 2 ); - return false; - } - else - { - API.Remove( -2 ); - return true; - } - } - - public bool L_CallMeta( int obj, string name ) - { - obj = API.AbsIndex( obj ); - if( !L_GetMetaField( obj, name ) ) // no metafield? - return false; - - API.PushValue( obj ); - API.Call( 1, 1 ); - return true; - } - - private void PushFuncName( LuaDebug ar ) - { - if( ar.NameWhat.Length > 0 && ar.NameWhat[0] != '\0' ) // is there a name? - API.PushString( string.Format( "function '{0}'", ar.Name ) ); - else if( ar.What.Length > 0 && ar.What[0] == 'm' ) // main? - API.PushString( "main chunk" ); - else if( ar.What.Length > 0 && ar.What[0] == 'C' ) - { - if( PushGlobalFuncName( ar ) ) - { - API.PushString( string.Format( "function '{0}'", API.ToString(-1) ) ); - API.Remove( -2 ); //remove name - } - else - API.PushString( "?" ); - } - else - API.PushString( string.Format( "function <{0}:{1}>", ar.ShortSrc, ar.LineDefined ) ); - } - - private int CountLevels() - { - LuaDebug ar = new LuaDebug(); - int li = 1; - int le = 1; - // find an upper bound - while( API.GetStack(le, ar) ) - { - li = le; - le *= 2; - } - // do a binary search - while(li < le) - { - int m = (li + le)/2; - if( API.GetStack( m, ar ) ) - li = m + 1; - else - le = m; - } - return le - 1; - } - - public void L_Traceback( ILuaState otherLua, string msg, int level ) - { - LuaState oLua = otherLua as LuaState; - LuaDebug ar = new LuaDebug(); - int top = API.GetTop(); - int numLevels = oLua.CountLevels(); - int mark = (numLevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; - if( msg != null ) - API.PushString( string.Format( "{0}\n", msg ) ); - API.PushString( "stack traceback:" ); - while( otherLua.GetStack( level++, ar ) ) - { - if( level == mark ) // too many levels? - { - API.PushString( "\n\t..." ); - level = numLevels - LEVELS2; // and skip to last ones - } - else - { - oLua.GetInfo( "Slnt", ar ); - API.PushString( string.Format( "\n\t{0}:", ar.ShortSrc ) ); - if( ar.CurrentLine > 0 ) - API.PushString( string.Format( "{0}:", ar.CurrentLine ) ); - API.PushString(" in "); - PushFuncName( ar ); - if( ar.IsTailCall ) - API.PushString( "\n\t(...tail calls...)" ); - API.Concat( API.GetTop() - top ); - } - } - API.Concat( API.GetTop() - top ); - } - - public int L_Len( int index ) - { - API.Len( index ); - - bool isnum; - int l = (int)API.ToIntegerX( -1, out isnum ); - if( !isnum ) - L_Error( "object length is not a number" ); - API.Pop( 1 ); - return l; - } - - public ThreadStatus L_LoadBuffer( string s, string name ) - { - return L_LoadBufferX( s, name, null ); - } - - public ThreadStatus L_LoadBufferX( string s, string name, string mode ) - { - var loadinfo = new StringLoadInfo( s ); - return API.Load( loadinfo, name, mode ); - } - - public ThreadStatus L_LoadBytes( byte[] bytes, string name ) - { - var loadinfo = new BytesLoadInfo( bytes ); - return API.Load( loadinfo, name, null ); - } - - private ThreadStatus ErrFile( string what, int fnameindex ) - { - return ThreadStatus.LUA_ERRFILE; - } - - public ThreadStatus L_LoadFile( string filename ) - { - return L_LoadFileX( filename, null ); - } - - public ThreadStatus L_LoadFileX( string filename, string mode ) - { - var status = ThreadStatus.LUA_OK; - if( filename == null ) - { - // 暂不实现从 stdin 输入 - throw new System.NotImplementedException(); - } - - int fnameindex = API.GetTop() + 1; - API.PushString( "@" + filename ); - try - { - using( var loadinfo = LuaFile.OpenFile( filename ) ) - { - loadinfo.SkipComment(); - status = API.Load( loadinfo, API.ToString(-1), mode ); - } - } - catch( LuaRuntimeException e ) - { - API.PushString( string.Format( "cannot open {0}: {1}", - filename, e.Message ) ); - return ThreadStatus.LUA_ERRFILE; - } - - API.Remove( fnameindex ); - return status; - } - - public ThreadStatus L_LoadString( string s ) - { - return L_LoadBuffer( s, s ); - } - - public ThreadStatus L_DoString( string s ) - { - var status = L_LoadString( s ); - if( status != ThreadStatus.LUA_OK ) - return status; - return API.PCall( 0, LuaDef.LUA_MULTRET, 0 ); - } - - public ThreadStatus L_DoFile( string filename ) - { - var status = L_LoadFile( filename ); - if( status != ThreadStatus.LUA_OK ) - return status; - return API.PCall( 0, LuaDef.LUA_MULTRET, 0 ); - } - - public string L_Gsub( string src, string pattern, string rep ) - { - string res = src.Replace(pattern, rep); - API.PushString( res ); - return res; - } - - public string L_ToString( int index ) - { - if( !L_CallMeta( index, "__tostring" ) ) // no metafield? // TODO L_CallMeta - { - switch( API.Type(index) ) - { - case LuaType.LUA_TNUMBER: - case LuaType.LUA_TSTRING: - API.PushValue( index ); - break; - - case LuaType.LUA_TBOOLEAN: - API.PushString( API.ToBoolean( index ) ? "true" : "false" ); - break; - - case LuaType.LUA_TNIL: - API.PushString( "nil" ); - break; - - default: - API.PushString( string.Format("{0}: {1:X}" - , L_TypeName( index ) - , API.ToObject( index ).GetHashCode() - ) ); - break; - } - } - return API.ToString( -1 ); - } - - // private static class LibLoadInfo - // { - // public static List Items; - - // static LibLoadInfo() - // { - // Items = new List(); - // Add( "_G", LuaState.LuaOpen_Base ); - // } - - // private static void Add( string name, CSharpFunctionDelegate loadFunc ) - // { - // Items.Add( new NameFuncPair { Name=name, LoadFunc=loadFunc } ); - // } - // } - - public void L_OpenLibs() - { - NameFuncPair[] define = new NameFuncPair[] - { - new NameFuncPair( "_G", LuaBaseLib.OpenLib ), - new NameFuncPair( LuaPkgLib.LIB_NAME, LuaPkgLib.OpenLib ), - new NameFuncPair( LuaCoroLib.LIB_NAME, LuaCoroLib.OpenLib ), - new NameFuncPair( LuaTableLib.LIB_NAME, LuaTableLib.OpenLib ), - new NameFuncPair( LuaIOLib.LIB_NAME, LuaIOLib.OpenLib ), - new NameFuncPair( LuaOSLib.LIB_NAME, LuaOSLib.OpenLib ), + using System; + using System.IO; + using System.Collections.Generic; + + public struct NameFuncPair + { + public string Name; + public CSharpFunctionDelegate Func; + + public NameFuncPair(string name, CSharpFunctionDelegate func) + { + Name = name; + Func = func; + } + } + + public interface ILuaAuxLib + { + void L_Where(int level); + int L_Error(string fmt, params object[] args); + void L_CheckStack(int size, string msg); + void L_CheckAny(int narg); + void L_CheckType(int index, LuaType t); + double L_CheckNumber(int narg); + UInt64 L_CheckUInt64(int narg); + int L_CheckInteger(int narg); + string L_CheckString(int narg); + uint L_CheckUnsigned(int narg); + void L_ArgCheck(bool cond, int narg, string extraMsg); + int L_ArgError(int narg, string extraMsg); + string L_TypeName(int index); + + string L_ToString(int index); + bool L_GetMetaField(int index, string method); + int L_GetSubTable(int index, string fname); + + void L_RequireF(string moduleName, CSharpFunctionDelegate openFunc, bool global); + void L_OpenLibs(); + void L_NewLibTable(NameFuncPair[] define); + void L_NewLib(NameFuncPair[] define); + void L_SetFuncs(NameFuncPair[] define, int nup); + + T L_Opt(Func f, int n, T def); + int L_OptInt(int narg, int def); + string L_OptString(int narg, string def); + bool L_CallMeta(int obj, string name); + void L_Traceback(ILuaState otherLua, string msg, int level); + int L_Len(int index); + + ThreadStatus L_LoadBuffer(string s, string name); + ThreadStatus L_LoadBufferX(string s, string name, string mode); + ThreadStatus L_LoadFile(string filename); + ThreadStatus L_LoadFileX(string filename, string mode); + + ThreadStatus L_LoadString(string s); + ThreadStatus L_LoadBytes(byte[] bytes, string name); + ThreadStatus L_DoString(string s); + ThreadStatus L_DoFile(string filename); + + + string L_Gsub(string src, string pattern, string rep); + + // reference system + int L_Ref(int t); + void L_Unref(int t, int reference); + } + + class StringLoadInfo : ILoadInfo + { + public StringLoadInfo(string s) + { + Str = s; + Pos = 0; + } + + public int ReadByte() + { + if (Pos >= Str.Length) + return -1; + else + return Str[Pos++]; + } + + public int PeekByte() + { + if (Pos >= Str.Length) + return -1; + else + return Str[Pos]; + } + + private string Str; + private int Pos; + } + + class BytesLoadInfo : ILoadInfo + { + public BytesLoadInfo(byte[] bytes) + { + Bytes = bytes; + Pos = 0; + } + + public int ReadByte() + { + if (Pos >= Bytes.Length) + return -1; + else + return Bytes[Pos++]; + } + + public int PeekByte() + { + if (Pos >= Bytes.Length) + return -1; + else + return Bytes[Pos]; + } + + private byte[] Bytes; + private int Pos; + } + + public partial class LuaState + { + private const int LEVELS1 = 12; // size of the first part of the stack + private const int LEVELS2 = 10; // size of the second part of the stack + + public void L_Where(int level) + { + LuaDebug ar = new LuaDebug(); + if (API.GetStack(level, ar)) // check function at level + { + GetInfo("Sl", ar); // get info about it + if (ar.CurrentLine > 0) // is there info? + { + API.PushString(string.Format("{0}:{1}: ", ar.ShortSrc, ar.CurrentLine)); + return; + } + } + API.PushString(""); // else, no information available... + } + + public int L_Error(string fmt, params object[] args) + { + L_Where(1); + API.PushString(string.Format(fmt, args)); + API.Concat(2); + return API.Error(); + } + + public void L_CheckStack(int size, string msg) + { + // keep some extra space to run error routines, if needed + if (!API.CheckStack(size + LuaDef.LUA_MINSTACK)) + { + if (msg != null) + { L_Error(string.Format("stack overflow ({0})", msg)); } + else + { L_Error("stack overflow"); } + } + } + + public void L_CheckAny(int narg) + { + if (API.Type(narg) == LuaType.LUA_TNONE) + L_ArgError(narg, "value expected"); + } + + public double L_CheckNumber(int narg) + { + bool isnum; + double d = API.ToNumberX(narg, out isnum); + if (!isnum) + TagError(narg, LuaType.LUA_TNUMBER); + return d; + } + + public UInt64 L_CheckUInt64(int narg) + { + bool isnum; + UInt64 v = API.ToUInt64X(narg, out isnum); + if (!isnum) + TagError(narg, LuaType.LUA_TUINT64); + return v; + } + + public int L_CheckInteger(int narg) + { + bool isnum; + int d = API.ToIntegerX(narg, out isnum); + if (!isnum) + TagError(narg, LuaType.LUA_TNUMBER); + return d; + } + + public string L_CheckString(int narg) + { + string s = API.ToString(narg); + if (s == null) TagError(narg, LuaType.LUA_TSTRING); + return s; + } + + public uint L_CheckUnsigned(int narg) + { + bool isnum; + uint d = API.ToUnsignedX(narg, out isnum); + if (!isnum) + TagError(narg, LuaType.LUA_TNUMBER); + return d; + } + + public T L_Opt(Func f, int n, T def) + { + LuaType t = API.Type(n); + if (t == LuaType.LUA_TNONE || + t == LuaType.LUA_TNIL) + { + return def; + } + else + { + return f(n); + } + } + + public int L_OptInt(int narg, int def) + { + LuaType t = API.Type(narg); + if (t == LuaType.LUA_TNONE || + t == LuaType.LUA_TNIL) + { + return def; + } + else + { + return L_CheckInteger(narg); + } + } + + public string L_OptString(int narg, string def) + { + LuaType t = API.Type(narg); + if (t == LuaType.LUA_TNONE || + t == LuaType.LUA_TNIL) + { + return def; + } + else + { + return L_CheckString(narg); + } + } + + private int TypeError(int index, string typeName) + { + string msg = string.Format("{0} expected, got {1}", + typeName, L_TypeName(index)); + API.PushString(msg); + return L_ArgError(index, msg); + } + + private void TagError(int index, LuaType t) + { + TypeError(index, API.TypeName(t)); + } + + public void L_CheckType(int index, LuaType t) + { + if (API.Type(index) != t) + TagError(index, t); + } + + public void L_ArgCheck(bool cond, int narg, string extraMsg) + { + if (!cond) + L_ArgError(narg, extraMsg); + } + + public int L_ArgError(int narg, string extraMsg) + { + + LuaDebug ar = new LuaDebug(); + if (!API.GetStack(0, ar)) // no stack frame ? + return L_Error("bad argument {0} ({1})", narg, extraMsg); + + GetInfo("n", ar); + if (ar.NameWhat == "method") + { + narg--; // do not count 'self' + if (narg == 0) // error is in the self argument itself? + return L_Error("calling '{0}' on bad self", ar.Name); + } + if (ar.Name == null) + ar.Name = PushGlobalFuncName(ar) ? API.ToString(-1) : "?"; + return L_Error("bad argument {0} to '{1}' ({2})", + narg, ar.Name, extraMsg); + } + + public string L_TypeName(int index) + { + return API.TypeName(API.Type(index)); + } + + public bool L_GetMetaField(int obj, string name) + { + if (!API.GetMetaTable(obj)) // no metatable? + return false; + API.PushString(name); + API.RawGet(-2); + if (API.IsNil(-1)) + { + API.Pop(2); + return false; + } + else + { + API.Remove(-2); + return true; + } + } + + public bool L_CallMeta(int obj, string name) + { + obj = API.AbsIndex(obj); + if (!L_GetMetaField(obj, name)) // no metafield? + return false; + + API.PushValue(obj); + API.Call(1, 1); + return true; + } + + private void PushFuncName(LuaDebug ar) + { + if (ar.NameWhat.Length > 0 && ar.NameWhat[0] != '\0') // is there a name? + API.PushString(string.Format("function '{0}'", ar.Name)); + else if (ar.What.Length > 0 && ar.What[0] == 'm') // main? + API.PushString("main chunk"); + else if (ar.What.Length > 0 && ar.What[0] == 'C') + { + if (PushGlobalFuncName(ar)) + { + API.PushString(string.Format("function '{0}'", API.ToString(-1))); + API.Remove(-2); //remove name + } + else + API.PushString("?"); + } + else + API.PushString(string.Format("function <{0}:{1}>", ar.ShortSrc, ar.LineDefined)); + } + + private int CountLevels() + { + LuaDebug ar = new LuaDebug(); + int li = 1; + int le = 1; + // find an upper bound + while (API.GetStack(le, ar)) + { + li = le; + le *= 2; + } + // do a binary search + while (li < le) + { + int m = (li + le) / 2; + if (API.GetStack(m, ar)) + li = m + 1; + else + le = m; + } + return le - 1; + } + + public void L_Traceback(ILuaState otherLua, string msg, int level) + { + LuaState oLua = otherLua as LuaState; + LuaDebug ar = new LuaDebug(); + int top = API.GetTop(); + int numLevels = oLua.CountLevels(); + int mark = (numLevels > LEVELS1 + LEVELS2) ? LEVELS1 : 0; + if (msg != null) + API.PushString(string.Format("{0}\n", msg)); + API.PushString("stack traceback:"); + while (otherLua.GetStack(level++, ar)) + { + if (level == mark) // too many levels? + { + API.PushString("\n\t..."); + level = numLevels - LEVELS2; // and skip to last ones + } + else + { + oLua.GetInfo("Slnt", ar); + API.PushString(string.Format("\n\t{0}:", ar.ShortSrc)); + if (ar.CurrentLine > 0) + API.PushString(string.Format("{0}:", ar.CurrentLine)); + API.PushString(" in "); + PushFuncName(ar); + if (ar.IsTailCall) + API.PushString("\n\t(...tail calls...)"); + API.Concat(API.GetTop() - top); + } + } + API.Concat(API.GetTop() - top); + } + + public int L_Len(int index) + { + API.Len(index); + + bool isnum; + int l = (int)API.ToIntegerX(-1, out isnum); + if (!isnum) + L_Error("object length is not a number"); + API.Pop(1); + return l; + } + + public ThreadStatus L_LoadBuffer(string s, string name) + { + return L_LoadBufferX(s, name, null); + } + + public ThreadStatus L_LoadBufferX(string s, string name, string mode) + { + var loadinfo = new StringLoadInfo(s); + return API.Load(loadinfo, name, mode); + } + + public ThreadStatus L_LoadBytes(byte[] bytes, string name) + { + var loadinfo = new BytesLoadInfo(bytes); + return API.Load(loadinfo, name, null); + } + + private ThreadStatus ErrFile(string what, int fnameindex) + { + return ThreadStatus.LUA_ERRFILE; + } + + public ThreadStatus L_LoadFile(string filename) + { + return L_LoadFileX(filename, null); + } + + public ThreadStatus L_LoadFileX(string filename, string mode) + { + var status = ThreadStatus.LUA_OK; + if (filename == null) + { + // 暂不实现从 stdin 输入 + throw new System.NotImplementedException(); + } + + int fnameindex = API.GetTop() + 1; + API.PushString("@" + filename); + try + { + using (var loadinfo = LuaFile.OpenFile(filename)) + { + loadinfo.SkipComment(); + status = API.Load(loadinfo, API.ToString(-1), mode); + } + } + catch (LuaRuntimeException e) + { + API.PushString(string.Format("cannot open {0}: {1}", + filename, e.Message)); + return ThreadStatus.LUA_ERRFILE; + } + + API.Remove(fnameindex); + return status; + } + + public ThreadStatus L_LoadString(string s) + { + return L_LoadBuffer(s, s); + } + + public ThreadStatus L_DoString(string s) + { + var status = L_LoadString(s); + if (status != ThreadStatus.LUA_OK) + return status; + return API.PCall(0, LuaDef.LUA_MULTRET, 0); + } + + public ThreadStatus L_DoFile(string filename) + { + var status = L_LoadFile(filename); + if (status != ThreadStatus.LUA_OK) + return status; + return API.PCall(0, LuaDef.LUA_MULTRET, 0); + } + + public string L_Gsub(string src, string pattern, string rep) + { + string res = src.Replace(pattern, rep); + API.PushString(res); + return res; + } + + public string L_ToString(int index) + { + if (!L_CallMeta(index, "__tostring")) // no metafield? // TODO L_CallMeta + { + switch (API.Type(index)) + { + case LuaType.LUA_TNUMBER: + case LuaType.LUA_TSTRING: + API.PushValue(index); + break; + + case LuaType.LUA_TBOOLEAN: + API.PushString(API.ToBoolean(index) ? "true" : "false"); + break; + + case LuaType.LUA_TNIL: + API.PushString("nil"); + break; + + default: + API.PushString(string.Format("{0}: {1:X}" + , L_TypeName(index) + , API.ToObject(index).GetHashCode() + )); + break; + } + } + return API.ToString(-1); + } + + // private static class LibLoadInfo + // { + // public static List Items; + + // static LibLoadInfo() + // { + // Items = new List(); + // Add( "_G", LuaState.LuaOpen_Base ); + // } + + // private static void Add( string name, CSharpFunctionDelegate loadFunc ) + // { + // Items.Add( new NameFuncPair { Name=name, LoadFunc=loadFunc } ); + // } + // } + + public void L_OpenLibs() + { + NameFuncPair[] define = new NameFuncPair[] + { + new NameFuncPair( "_G", LuaBaseLib.OpenLib ), + new NameFuncPair( LuaPkgLib.LIB_NAME, LuaPkgLib.OpenLib ), + //取消协程 ,项目不需要 + //new NameFuncPair( LuaCoroLib.LIB_NAME, LuaCoroLib.OpenLib ), + new NameFuncPair( LuaTableLib.LIB_NAME, LuaTableLib.OpenLib ), + new NameFuncPair( LuaIOLib.LIB_NAME, LuaIOLib.OpenLib ), + new NameFuncPair( LuaOSLib.LIB_NAME, LuaOSLib.OpenLib ), // {LUA_OSLIBNAME, luaopen_os}, - new NameFuncPair( LuaStrLib.LIB_NAME, LuaStrLib.OpenLib ), - new NameFuncPair( LuaBitLib.LIB_NAME, LuaBitLib.OpenLib ), - new NameFuncPair( LuaMathLib.LIB_NAME, LuaMathLib.OpenLib ), - new NameFuncPair( LuaDebugLib.LIB_NAME, LuaDebugLib.OpenLib ), - new NameFuncPair( LuaFFILib.LIB_NAME, LuaFFILib.OpenLib ), - new NameFuncPair( LuaEncLib.LIB_NAME, LuaEncLib.OpenLib ), - }; - - for( var i=0; i= 0 ) - { - t = API.AbsIndex(t); - API.RawGetI(t, FreeList); - API.RawSetI(t, reference); // t[ref] = t[freelist] - API.PushInteger(reference); - API.RawSetI(t, FreeList); // t[freelist] = ref - } - } + new NameFuncPair( LuaStrLib.LIB_NAME, LuaStrLib.OpenLib ), + new NameFuncPair( LuaBitLib.LIB_NAME, LuaBitLib.OpenLib ), + new NameFuncPair( LuaMathLib.LIB_NAME, LuaMathLib.OpenLib ), + new NameFuncPair( LuaDebugLib.LIB_NAME, LuaDebugLib.OpenLib ), + new NameFuncPair( LuaFFILib.LIB_NAME, LuaFFILib.OpenLib ), + new NameFuncPair( LuaEncLib.LIB_NAME, LuaEncLib.OpenLib ), + }; + + for (var i = 0; i < define.Length; ++i) + { + L_RequireF(define[i].Name, define[i].Func, true); + API.Pop(1); + } + + // LuaBaseLib.LuaOpen_Base( this ); + } + + public void L_RequireF(string moduleName, CSharpFunctionDelegate openFunc, bool global) + { + API.PushCSharpFunction(openFunc); + API.PushString(moduleName); + API.Call(1, 1); + L_GetSubTable(LuaDef.LUA_REGISTRYINDEX, "_LOADED"); + API.PushValue(-2); + API.SetField(-2, moduleName); + API.Pop(1); + if (global) + { + API.PushValue(-1); + API.SetGlobal(moduleName); + } + } + + public int L_GetSubTable(int index, string fname) + { + API.GetField(index, fname); + if (API.IsTable(-1)) + return 1; + else + { + API.Pop(1); + index = API.AbsIndex(index); + API.NewTable(); + API.PushValue(-1); + API.SetField(index, fname); + return 0; + } + } + + public void L_NewLibTable(NameFuncPair[] define) + { + API.CreateTable(0, define.Length); + } + + public void L_NewLib(NameFuncPair[] define) + { + L_NewLibTable(define); + L_SetFuncs(define, 0); + } + + public void L_SetFuncs(NameFuncPair[] define, int nup) + { + // TODO: Check Version + L_CheckStack(nup, "too many upvalues"); + for (var j = 0; j < define.Length; ++j) + { + for (int i = 0; i < nup; ++i) + API.PushValue(-nup); + API.PushCSharpClosure(define[j].Func, nup); + API.SetField(-(nup + 2), define[j].Name); + } + API.Pop(nup); + } + + private bool FindField(int objIndex, int level) + { + if (level == 0 || !API.IsTable(-1)) + return false; // not found + + API.PushNil(); // start 'next' loop + while (API.Next(-2)) // for each pair in table + { + if (API.Type(-2) == LuaType.LUA_TSTRING) // ignore non-string keys + { + if (API.RawEqual(objIndex, -1)) // found object? + { + API.Pop(1); // remove value (but keep name) + return true; + } + else if (FindField(objIndex, level - 1)) // try recursively + { + API.Remove(-2); // remove table (but keep name) + API.PushString("."); + API.Insert(-2); // place '.' between the two names + API.Concat(3); + return true; + } + } + API.Pop(1); // remove value + } + return false; // not found + } + + private bool PushGlobalFuncName(LuaDebug ar) + { + int top = API.GetTop(); + GetInfo("f", ar); + API.PushGlobalTable(); + if (FindField(top + 1, 2)) + { + API.Copy(-1, top + 1); + API.Pop(2); + return true; + } + else + { + API.SetTop(top); // remove function and global table + return false; + } + } + + private const int FreeList = 0; + + public int L_Ref(int t) + { + if (API.IsNil(-1)) + { + API.Pop(1); // remove from stack + return LuaConstants.LUA_REFNIL; // `nil' has a unique fixed reference + } + + t = API.AbsIndex(t); + API.RawGetI(t, FreeList); // get first free element + int reference = API.ToInteger(-1); // ref = t[freelist] + API.Pop(1); // remove it from stack + if (reference != 0) // any free element? + { + API.RawGetI(t, reference); // remove it from list + API.RawSetI(t, FreeList); // t[freelist] = t[ref] + } + else // no free elements + reference = API.RawLen(t) + 1; // get a new reference + API.RawSetI(t, reference); + return reference; + } + + public void L_Unref(int t, int reference) + { + if (reference >= 0) + { + t = API.AbsIndex(t); + API.RawGetI(t, FreeList); + API.RawSetI(t, reference); // t[ref] = t[freelist] + API.PushInteger(reference); + API.RawSetI(t, FreeList); // t[freelist] = ref + } + } #if UNITY_IPHONE public void FEED_AOT_FOR_IOS(LuaState lua) @@ -728,7 +730,7 @@ public void FEED_AOT_FOR_IOS(LuaState lua) } #endif - } + } } diff --git a/Assets/UniLua/LuaAuxLib.cs.meta b/Assets/UniLua/LuaAuxLib.cs.meta deleted file mode 100644 index ff43ba6..0000000 --- a/Assets/UniLua/LuaAuxLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 86e3d38c3ec5564498927f6501ca8f4c -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaBaseLib.cs.meta b/Assets/UniLua/LuaBaseLib.cs.meta deleted file mode 100644 index f545a7b..0000000 --- a/Assets/UniLua/LuaBaseLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7cc8418b4b85dc14487471530afa6bbe -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaBitLib.cs.meta b/Assets/UniLua/LuaBitLib.cs.meta deleted file mode 100644 index de4e9f0..0000000 --- a/Assets/UniLua/LuaBitLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 215359d6c496b9847950e267f6ed7c5b -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaCoroLib.cs.meta b/Assets/UniLua/LuaCoroLib.cs.meta deleted file mode 100644 index 161b30f..0000000 --- a/Assets/UniLua/LuaCoroLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7f5c3b7ec847e854193d6255b700d550 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaDebug.cs.meta b/Assets/UniLua/LuaDebug.cs.meta deleted file mode 100644 index 6aed2cc..0000000 --- a/Assets/UniLua/LuaDebug.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 7f7fe687cc1d69b4f9408695db6faa4d -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaDebugLib.cs.meta b/Assets/UniLua/LuaDebugLib.cs.meta deleted file mode 100644 index fd68cbe..0000000 --- a/Assets/UniLua/LuaDebugLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 2d8bd4859485e144a9691e1b2e938c73 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaEncLib.cs.meta b/Assets/UniLua/LuaEncLib.cs.meta deleted file mode 100644 index ef86e57..0000000 --- a/Assets/UniLua/LuaEncLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9443b021a85a2a149b8606356d3e21f5 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaFFILib.cs.meta b/Assets/UniLua/LuaFFILib.cs.meta deleted file mode 100644 index 338c092..0000000 --- a/Assets/UniLua/LuaFFILib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 62efbce053b703f41af3d6cea2a6d4de -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaFile.cs b/Assets/UniLua/LuaFile.cs index ddb29b4..c2f999c 100644 --- a/Assets/UniLua/LuaFile.cs +++ b/Assets/UniLua/LuaFile.cs @@ -2,92 +2,98 @@ using System.IO; using System.Collections.Generic; -using UnityEngine; + namespace UniLua { - public delegate string PathHook(string filename); - public class LuaFile - { - //private static readonly string LUA_ROOT = System.IO.Path.Combine(Application.streamingAssetsPath, "LuaRoot"); - private static PathHook pathhook = (s) => Path.Combine(Path.Combine(Application.streamingAssetsPath, "LuaRoot"), s); - public static void SetPathHook(PathHook hook) { - pathhook = hook; - } - - public static FileLoadInfo OpenFile( string filename ) - { - //var path = System.IO.Path.Combine(LUA_ROOT, filename); - var path = pathhook(filename); - return new FileLoadInfo( File.Open( path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ); - } - - public static bool Readable( string filename ) - { - //var path = System.IO.Path.Combine(LUA_ROOT, filename); - var path = pathhook(filename); - try { - using( var stream = File.Open( path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite ) ) { - return true; - } - } - catch( Exception ) { - return false; - } - } - } - - public class FileLoadInfo : ILoadInfo, IDisposable - { - public FileLoadInfo( FileStream stream ) - { - Stream = stream; - Reader = new StreamReader(Stream, System.Text.Encoding.UTF8); - Buf = new Queue(); - } - - public int ReadByte() - { - if( Buf.Count > 0 ) - return (int)Buf.Dequeue(); - else - return Reader.Read(); - } - - public int PeekByte() - { - if( Buf.Count > 0 ) - return (int)Buf.Peek(); - else - { - var c = Reader.Read(); - if( c == -1 ) - return c; - Save( (char)c ); - return c; - } - } - - public void Dispose() - { - Reader.Dispose(); - Stream.Dispose(); - } - - private const string UTF8_BOM = "\u00EF\u00BB\u00BF"; - private FileStream Stream; - private StreamReader Reader; - private Queue Buf; - - private void Save( char b ) - { - Buf.Enqueue( b ); - } - - private void Clear() - { - Buf.Clear(); - } + public delegate string PathHook(string filename); + public class LuaFile + { + // private static readonly string LUA_ROOT = System.IO.Path.Combine(Application.streamingAssetsPath, "LuaRoot"); + // private static PathHook pathhook = (s) => Path.Combine(Path.Combine(Application.streamingAssetsPath, "LuaRoot"), s); + private static readonly string LUA_ROOT = ""; + private static PathHook pathhook; + public static void SetPathHook(PathHook hook) + { + pathhook = hook; + } + + public static FileLoadInfo OpenFile(string filename) + { + var path = System.IO.Path.Combine(LUA_ROOT, filename); + + return new FileLoadInfo(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); + } + + public static bool Readable(string filename) + { + var path = System.IO.Path.Combine(LUA_ROOT, filename); + // var path = pathhook(filename); + try + { + using (var stream = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + return true; + } + } + catch (Exception) + { + return false; + } + } + } + + public class FileLoadInfo : ILoadInfo, IDisposable + { + public FileLoadInfo(FileStream stream) + { + Stream = stream; + Reader = new StreamReader(Stream, System.Text.Encoding.UTF8); + Buf = new Queue(); + } + + public int ReadByte() + { + if (Buf.Count > 0) + return (int)Buf.Dequeue(); + else + return Reader.Read(); + } + + public int PeekByte() + { + if (Buf.Count > 0) + return (int)Buf.Peek(); + else + { + var c = Reader.Read(); + if (c == -1) + return c; + Save((char)c); + return c; + } + } + + public void Dispose() + { + Reader.Dispose(); + Stream.Dispose(); + } + + private const string UTF8_BOM = "\u00EF\u00BB\u00BF"; + private FileStream Stream; + private StreamReader Reader; + private Queue Buf; + + private void Save(char b) + { + Buf.Enqueue(b); + } + + private void Clear() + { + Buf.Clear(); + } #if false private int SkipBOM() @@ -105,24 +111,25 @@ private int SkipBOM() } #endif - public void SkipComment() - { - var c = Reader.Read();//SkipBOM(); - - // first line is a comment (Unix exec. file)? - if( c == '#' ) - { - do { - c = Reader.Read(); - } while( c != -1 && c != '\n' ); - Save( (char)'\n' ); // fix line number - } - else if( c != -1 ) - { - Save( (char)c ); - } - } - } + public void SkipComment() + { + var c = Reader.Read();//SkipBOM(); + + // first line is a comment (Unix exec. file)? + if (c == '#') + { + do + { + c = Reader.Read(); + } while (c != -1 && c != '\n'); + Save((char)'\n'); // fix line number + } + else if (c != -1) + { + Save((char)c); + } + } + } } diff --git a/Assets/UniLua/LuaFile.cs.meta b/Assets/UniLua/LuaFile.cs.meta deleted file mode 100644 index 165e391..0000000 --- a/Assets/UniLua/LuaFile.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 94f4a97c688e9b64ca02f12e4355bd91 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaFunc.cs.meta b/Assets/UniLua/LuaFunc.cs.meta deleted file mode 100644 index ac8cee6..0000000 --- a/Assets/UniLua/LuaFunc.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: f7f16d8b096a1514bbd721daf62df68c -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaIOLib.cs.meta b/Assets/UniLua/LuaIOLib.cs.meta deleted file mode 100644 index a6cd842..0000000 --- a/Assets/UniLua/LuaIOLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 16458140beb4e1e41810fcaf658f6fe6 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaMathLib.cs.meta b/Assets/UniLua/LuaMathLib.cs.meta deleted file mode 100644 index 4bd6978..0000000 --- a/Assets/UniLua/LuaMathLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: a950cf5ed15c8b943b03296567b51781 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaObject.cs.meta b/Assets/UniLua/LuaObject.cs.meta deleted file mode 100644 index 4d8a640..0000000 --- a/Assets/UniLua/LuaObject.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bf55010417da6c24ebaf30190f254d0f -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaOsLib.cs.meta b/Assets/UniLua/LuaOsLib.cs.meta deleted file mode 100644 index a3492e6..0000000 --- a/Assets/UniLua/LuaOsLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: bc4220d4d4551884aa891d61dac262f8 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaPkgLib.cs b/Assets/UniLua/LuaPkgLib.cs index 0809f92..933cbc7 100644 --- a/Assets/UniLua/LuaPkgLib.cs +++ b/Assets/UniLua/LuaPkgLib.cs @@ -5,140 +5,140 @@ namespace UniLua { - using Environment = System.Environment; - using StringBuilder = System.Text.StringBuilder; - using String = System.String; - using File = System.IO.File; - using FileMode = System.IO.FileMode; - using Exception = System.Exception; - - internal class LuaPkgLib - { - public const string LIB_NAME = "package"; - - private const string CLIBS = "_CLIBS"; - - private const string LUA_PATH = "LUA_PATH"; - private const string LUA_CPATH = "LUA_CPATH"; - private const string LUA_PATHSUFFIX = "_" + LuaDef.LUA_VERSION_MAJOR + - "_" + LuaDef.LUA_VERSION_MINOR; - private const string LUA_PATHVERSION = LUA_PATH + LUA_PATHSUFFIX; - private const string LUA_CPATHVERSION = LUA_CPATH + LUA_PATHSUFFIX; - - // private const string LUA_LDIR = "!\\lua\\"; - // private const string LUA_CDIR = "!\\"; - // private const string LUA_PATH_DEFAULT = LUA_LDIR + "?.lua;" + - // LUA_LDIR + "?\\init.lua;" + - // LUA_CDIR + "?.lua;" + - // LUA_CDIR + "?\\init.lua;" + - // ".\\?.lua"; - // private const string LUA_CPATH_DEFAULT = LUA_CDIR + "?.dll;" + - // LUA_CDIR + "loadall.dll;" + - // ".\\?.dll"; - - private const string LUA_PATH_DEFAULT = "?.lua;"; - private const string LUA_CPATH_DEFAULT = "?.dll;loadall.dll;"; - - private const string LUA_PATH_SEP = ";"; - private const string LUA_PATH_MARK = "?"; - private const string LUA_EXEC_DIR = "!"; - private const string LUA_IGMARK = "-"; - - private static readonly string LUA_LSUBSEP = LuaConf.LUA_DIRSEP; - - // auxiliary mark (for internal use) - private const string AUXMARK = "\u0001"; - - public static int OpenLib( ILuaState lua ) - { - // NameFuncPair[] define = new NameFuncPair[] - // { - // new NameFuncPair( "module", PKG_Module ), - // }; - - // lua.L_NewLib( define ); - - // // create table CLIBS to keep track of loaded C libraries - // lua.L_GetSubTable( LuaDef.LUA_REGISTRYINDEX, CLIBS ); - // lua.CreateTable( 0, 1 ); // metatable for CLIBS - // lua.PushCSharpFunction - - // create `package' table - NameFuncPair[] pkg_define = new NameFuncPair[] - { - new NameFuncPair( "loadlib", PKG_LoadLib ), - new NameFuncPair( "searchpath", PKG_SearchPath ), - new NameFuncPair( "seeall", PKG_SeeAll ), - }; - lua.L_NewLib( pkg_define ); - - CreateSearchersTable( lua ); + using Environment = System.Environment; + using StringBuilder = System.Text.StringBuilder; + using String = System.String; + using File = System.IO.File; + using FileMode = System.IO.FileMode; + using Exception = System.Exception; + + internal class LuaPkgLib + { + public const string LIB_NAME = "package"; + + private const string CLIBS = "_CLIBS"; + + private const string LUA_PATH = "LUA_PATH"; + private const string LUA_CPATH = "LUA_CPATH"; + private const string LUA_PATHSUFFIX = "_" + LuaDef.LUA_VERSION_MAJOR + + "_" + LuaDef.LUA_VERSION_MINOR; + private const string LUA_PATHVERSION = LUA_PATH + LUA_PATHSUFFIX; + private const string LUA_CPATHVERSION = LUA_CPATH + LUA_PATHSUFFIX; + + // private const string LUA_LDIR = "!\\lua\\"; + // private const string LUA_CDIR = "!\\"; + // private const string LUA_PATH_DEFAULT = LUA_LDIR + "?.lua;" + + // LUA_LDIR + "?\\init.lua;" + + // LUA_CDIR + "?.lua;" + + // LUA_CDIR + "?\\init.lua;" + + // ".\\?.lua"; + // private const string LUA_CPATH_DEFAULT = LUA_CDIR + "?.dll;" + + // LUA_CDIR + "loadall.dll;" + + // ".\\?.dll"; + + private const string LUA_PATH_DEFAULT = "?.lua;"; + private const string LUA_CPATH_DEFAULT = "?.dll;loadall.dll;"; + + private const string LUA_PATH_SEP = ";"; + private const string LUA_PATH_MARK = "?"; + private const string LUA_EXEC_DIR = "!"; + private const string LUA_IGMARK = "-"; + + private static readonly string LUA_LSUBSEP = LuaConf.LUA_DIRSEP; + + // auxiliary mark (for internal use) + private const string AUXMARK = "\u0001"; + + public static int OpenLib(ILuaState lua) + { + // NameFuncPair[] define = new NameFuncPair[] + // { + // new NameFuncPair( "module", PKG_Module ), + // }; + + // lua.L_NewLib( define ); + + // // create table CLIBS to keep track of loaded C libraries + // lua.L_GetSubTable( LuaDef.LUA_REGISTRYINDEX, CLIBS ); + // lua.CreateTable( 0, 1 ); // metatable for CLIBS + // lua.PushCSharpFunction + + // create `package' table + NameFuncPair[] pkg_define = new NameFuncPair[] + { + new NameFuncPair( "loadlib", PKG_LoadLib ), + new NameFuncPair( "searchpath", PKG_SearchPath ), + new NameFuncPair( "seeall", PKG_SeeAll ), + }; + lua.L_NewLib(pkg_define); + + CreateSearchersTable(lua); #if LUA_COMPAT_LOADERS - lua.PushValue( -1 ); // make a copy of `searchers' table - lua.SetField( -3, "loaders" ); // put it in field `loaders' + lua.PushValue(-1); // make a copy of `searchers' table + lua.SetField(-3, "loaders"); // put it in field `loaders' #endif - lua.SetField( -2, "searchers" ); // put it in field `searchers' - - // set field `path' - SetPath( lua, "path", LUA_PATHVERSION, LUA_PATH, LUA_PATH_DEFAULT ); - // set field `cpath' - SetPath( lua, "cpath", LUA_CPATHVERSION, LUA_CPATH, LUA_CPATH_DEFAULT ); - - // store config information - lua.PushString( string.Format("{0}\n{1}\n{2}\n{3}\n{4}\n", - LuaConf.LUA_DIRSEP, - LUA_PATH_SEP, - LUA_PATH_MARK, - LUA_EXEC_DIR, - LUA_IGMARK ) ); - lua.SetField( -2, "config" ); - - // set field `loaded' - lua.L_GetSubTable( LuaDef.LUA_REGISTRYINDEX, "_LOADED" ); - lua.SetField( -2, "loaded" ); - - // set field `preload' - lua.L_GetSubTable( LuaDef.LUA_REGISTRYINDEX, "_PRELOAD" ); - lua.SetField( -2, "preload" ); - - lua.PushGlobalTable(); - lua.PushValue( -2 ); // set `package' as upvalue for next lib - - NameFuncPair[] loadLibFuncs = new NameFuncPair[] - { - new NameFuncPair( "module", LL_Module ), - new NameFuncPair( "require", LL_Require ), - }; - lua.L_SetFuncs( loadLibFuncs, 1 ); // open lib into global table - lua.Pop( 1 ); // pop global table - - return 1; // return `package' table - } - - private static void CreateSearchersTable( ILuaState lua ) - { - CSharpFunctionDelegate[] searchers = new CSharpFunctionDelegate[] - { - SearcherPreload, - SearcherLua, - }; - lua.CreateTable( searchers.Length, 0 ); - for( int i=0; i= path.Length ) - return false; - int end = pos+1; - while( end < path.Length && path[end] != LUA_PATH_SEP[0]) - end++; - - var template = path.Substring( pos, end-pos); - lua.PushString( template ); - - pos = end; - return true; - } - - private static string SearchPath( ILuaState lua, - string name, string path, string sep, string dirsep ) - { - var sb = new StringBuilder(); // to build error message - if( !String.IsNullOrEmpty(sep) ) // non-empty separator? - name = name.Replace( sep, dirsep ); // replace it by `dirsep' - int pos = 0; - while(PushNextTemplate(lua, path, ref pos)) - { - var template = lua.ToString(-1); - string filename = template.Replace( LUA_PATH_MARK, name ); - lua.Remove( -1 ); // remove path template - if( Readable( filename ) ) // does file exist and is readable? - return filename; // return that file name - lua.PushString( string.Format( "\n\tno file '{0}'", filename) ); - lua.Remove( -2 ); // remove file name - sb.Append( lua.ToString(-1) ); // concatenate error msg. entry - } - lua.PushString( sb.ToString() ); // create error message - return null; // not found - } - - private static string FindFile( ILuaState lua, - string name, string pname, string dirsep ) - { - lua.GetField( lua.UpvalueIndex(1), pname ); - string path = lua.ToString( -1 ); - if( path == null ) - lua.L_Error( "'package.{0}' must be a string", pname ); - return SearchPath( lua, name, path, ".", dirsep ); - } - - private static int CheckLoad( ILuaState lua, - bool stat, string filename ) - { - if( stat ) // module loaded successfully? - { - lua.PushString( filename ); // will be 2nd arg to module - return 2; // return open function and file name - } - else return lua.L_Error( - "error loading module '{0}' from file '{1}':\n\t{2}", - lua.ToString(1), filename, lua.ToString(-1) ); - } - - private static int SearcherLua( ILuaState lua ) - { - string name = lua.L_CheckString( 1 ); - string filename = FindFile( lua, name, "path", LUA_LSUBSEP ); - if( filename == null ) - return 1; - return CheckLoad( lua, - (lua.L_LoadFile(filename) == ThreadStatus.LUA_OK), - filename ); - } - - private static int LL_Module( ILuaState lua ) - { - // TODO - return 0; - } - - private static void FindLoader( ILuaState lua, string name ) - { - // will be at index 3 - lua.GetField( lua.UpvalueIndex(1), "searchers" ); - if( ! lua.IsTable(3) ) - lua.L_Error("'package.searchers' must be a table"); - - var sb = new StringBuilder(); - // iterator over available searchers to find a loader - for( int i=1; ; ++i ) - { - lua.RawGetI( 3, i ); // get a searcher - if( lua.IsNil( -1 ) ) // no more searchers? - { - lua.Pop( 1 ); // remove nil - lua.PushString( sb.ToString() ); - lua.L_Error( "module '{0}' not found:{1}", - name, lua.ToString(-1)); - return; - } - - lua.PushString( name ); - lua.Call( 1, 2 ); // call it - if( lua.IsFunction(-2) ) // did it find a loader - return; // module loader found - else if( lua.IsString(-2) ) // searcher returned error message? - { - lua.Pop( 1 ); // return extra return - sb.Append( lua.ToString(-1) ); - } - else - lua.Pop( 2 ); // remove both returns - } - } - - private static int LL_Require( ILuaState lua ) - { - string name = lua.L_CheckString( 1 ); - lua.SetTop( 1 ); - // _LOADED table will be at index 2 - lua.GetField( LuaDef.LUA_REGISTRYINDEX, "_LOADED" ); - // _LOADED[name] - lua.GetField( 2, name ); - // is it there? - if( lua.ToBoolean( -1 ) ) - return 1; // package is already loaded - // else must load package - // remove `GetField' result - lua.Pop( 1 ); - FindLoader( lua, name ); - lua.PushString( name ); // pass name as arg to module loader - lua.Insert( -2 ); // name is 1st arg (before search data) - lua.Call( 2, 1 ); // run loader to load module - if( !lua.IsNil( -1 ) ) // non-nil return? - lua.SetField( 2, name ); // _LOADED[name] = returned value - lua.GetField( 2, name ); - if( lua.IsNil( -1 ) ) // module did not set a value? - { - lua.PushBoolean( true ); // use true as result - lua.PushValue( -1 ); // extra copy to be returned - lua.SetField( 2, name ); // _LOADED[name] = true - } - return 1; - } - - private static int PKG_LoadLib( ILuaState lua ) - { - // TODO - return 0; - } - - private static int PKG_SearchPath( ILuaState lua ) - { - // TODO - return 0; - } - - private static int PKG_SeeAll( ILuaState lua ) - { - // TODO - return 0; - } - } + SetProgDir(lua); + lua.SetField(-2, fieldName); + } + + private static bool noEnv(ILuaState lua) + { + lua.GetField(LuaDef.LUA_REGISTRYINDEX, "LUA_NOENV"); + bool res = lua.ToBoolean(-1); + lua.Pop(1); + return res; + } + + private static void SetProgDir(ILuaState lua) + { + // TODO: unity 读本地文件? + // + // 下面的代码在编辑器中可以运行, 但是 build 后运行时会 crash + // var pgs = System.Diagnostics.Process.GetCurrentProcess(); + // ULDebug.Log( pgs.MainModule.FileName ); + } + + private static int SearcherPreload(ILuaState lua) + { + string name = lua.L_CheckString(1); + lua.GetField(LuaDef.LUA_REGISTRYINDEX, "_PRELOAD"); + lua.GetField(-1, name); + if (lua.IsNil(-1)) // not found? + lua.PushString(string.Format( + "\n\tno field package.preload['{0}']", name)); + return 1; + } + + private static bool Readable(string filename) + { + return LuaFile.Readable(filename); + } + + private static bool PushNextTemplate(ILuaState lua, + string path, ref int pos) + { + while (pos < path.Length && path[pos] == LUA_PATH_SEP[0]) + pos++; // skip separators + if (pos >= path.Length) + return false; + int end = pos + 1; + while (end < path.Length && path[end] != LUA_PATH_SEP[0]) + end++; + + var template = path.Substring(pos, end - pos); + lua.PushString(template); + + pos = end; + return true; + } + + private static string SearchPath(ILuaState lua, + string name, string path, string sep, string dirsep) + { + var sb = new StringBuilder(); // to build error message + if (!String.IsNullOrEmpty(sep)) // non-empty separator? + name = name.Replace(sep, dirsep); // replace it by `dirsep' + int pos = 0; + while (PushNextTemplate(lua, path, ref pos)) + { + var template = lua.ToString(-1); + string filename = template.Replace(LUA_PATH_MARK, name); + lua.Remove(-1); // remove path template + if (Readable(filename)) // does file exist and is readable? + return filename; // return that file name + lua.PushString(string.Format("\n\tno file '{0}'", filename)); + lua.Remove(-2); // remove file name + sb.Append(lua.ToString(-1)); // concatenate error msg. entry + } + lua.PushString(sb.ToString()); // create error message + return null; // not found + } + + private static string FindFile(ILuaState lua, + string name, string pname, string dirsep) + { + lua.GetField(lua.UpvalueIndex(1), pname); + string path = lua.ToString(-1); + if (path == null) + lua.L_Error("'package.{0}' must be a string", pname); + return SearchPath(lua, name, path, ".", dirsep); + } + + private static int CheckLoad(ILuaState lua, + bool stat, string filename) + { + if (stat) // module loaded successfully? + { + lua.PushString(filename); // will be 2nd arg to module + return 2; // return open function and file name + } + else return lua.L_Error( + "error loading module '{0}' from file '{1}':\n\t{2}", + lua.ToString(1), filename, lua.ToString(-1)); + } + + private static int SearcherLua(ILuaState lua) + { + string name = lua.L_CheckString(1); + string filename = FindFile(lua, name, "path", LUA_LSUBSEP); + if (filename == null) + return 1; + return CheckLoad(lua, + (lua.L_LoadFile(filename) == ThreadStatus.LUA_OK), + filename); + } + + private static int LL_Module(ILuaState lua) + { + // TODO + return 0; + } + + private static void FindLoader(ILuaState lua, string name) + { + // will be at index 3 + lua.GetField(lua.UpvalueIndex(1), "searchers"); + if (!lua.IsTable(3)) + lua.L_Error("'package.searchers' must be a table"); + + var sb = new StringBuilder(); + // iterator over available searchers to find a loader + for (int i = 1; ; ++i) + { + lua.RawGetI(3, i); // get a searcher + if (lua.IsNil(-1)) // no more searchers? + { + lua.Pop(1); // remove nil + lua.PushString(sb.ToString()); + lua.L_Error("module '{0}' not found:{1}", + name, lua.ToString(-1)); + return; + } + + lua.PushString(name); + lua.Call(1, 2); // call it + if (lua.IsFunction(-2)) // did it find a loader + return; // module loader found + else if (lua.IsString(-2)) // searcher returned error message? + { + lua.Pop(1); // return extra return + sb.Append(lua.ToString(-1)); + } + else + lua.Pop(2); // remove both returns + } + } + + private static int LL_Require(ILuaState lua) + { + string name = lua.L_CheckString(1); + lua.SetTop(1); + // _LOADED table will be at index 2 + lua.GetField(LuaDef.LUA_REGISTRYINDEX, "_LOADED"); + // _LOADED[name] + lua.GetField(2, name); + // is it there? + if (lua.ToBoolean(-1)) + return 1; // package is already loaded + // else must load package + // remove `GetField' result + lua.Pop(1); + FindLoader(lua, name); + lua.PushString(name); // pass name as arg to module loader + lua.Insert(-2); // name is 1st arg (before search data) + lua.Call(2, 1); // run loader to load module + if (!lua.IsNil(-1)) // non-nil return? + lua.SetField(2, name); // _LOADED[name] = returned value + lua.GetField(2, name); + if (lua.IsNil(-1)) // module did not set a value? + { + lua.PushBoolean(true); // use true as result + lua.PushValue(-1); // extra copy to be returned + lua.SetField(2, name); // _LOADED[name] = true + } + return 1; + } + + private static int PKG_LoadLib(ILuaState lua) + { + // TODO + return 0; + } + + private static int PKG_SearchPath(ILuaState lua) + { + // TODO + return 0; + } + + private static int PKG_SeeAll(ILuaState lua) + { + // TODO + return 0; + } + } } diff --git a/Assets/UniLua/LuaPkgLib.cs.meta b/Assets/UniLua/LuaPkgLib.cs.meta deleted file mode 100644 index 7fee2ba..0000000 --- a/Assets/UniLua/LuaPkgLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d9f1755cddbf50e48ad22b296c264120 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaState.cs.meta b/Assets/UniLua/LuaState.cs.meta deleted file mode 100644 index ca866aa..0000000 --- a/Assets/UniLua/LuaState.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 1c63eb0eb050e4948888f3809ce53282 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaStrLib.cs.meta b/Assets/UniLua/LuaStrLib.cs.meta deleted file mode 100644 index 5468daf..0000000 --- a/Assets/UniLua/LuaStrLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 9a860e99012fc6248a195d4bb3d21633 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaTable.cs.meta b/Assets/UniLua/LuaTable.cs.meta deleted file mode 100644 index 5e72431..0000000 --- a/Assets/UniLua/LuaTable.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: b7b1a57e7e952fb46949f8d4f30a5f0a -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/LuaTableLib.cs.meta b/Assets/UniLua/LuaTableLib.cs.meta deleted file mode 100644 index 6bf2677..0000000 --- a/Assets/UniLua/LuaTableLib.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 0b12a1a7066645d46ad25f806f46193f -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/OpCodes.cs.meta b/Assets/UniLua/OpCodes.cs.meta deleted file mode 100644 index 18923b0..0000000 --- a/Assets/UniLua/OpCodes.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 3517c557a5fdcaf428f57095b160831f -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/Parser.cs.meta b/Assets/UniLua/Parser.cs.meta deleted file mode 100644 index 3e2bf66..0000000 --- a/Assets/UniLua/Parser.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 4032eb37fa4604040bee1551ed64503e -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/TagMethod.cs.meta b/Assets/UniLua/TagMethod.cs.meta deleted file mode 100644 index c5c45c9..0000000 --- a/Assets/UniLua/TagMethod.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 863cac417f8859740be8c9e0bfc8b65d -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/ULDebug.cs b/Assets/UniLua/ULDebug.cs index 5bf0f9c..86e3bcd 100644 --- a/Assets/UniLua/ULDebug.cs +++ b/Assets/UniLua/ULDebug.cs @@ -1,27 +1,36 @@  -#define UNITY +using Serilog; namespace UniLua.Tools { - // using Logger = DebugAssist; - using Logger = UnityEngine.Debug; + // using Logger = DebugAssist; + // using Logger = UnityEngine.Debug; - // thanks to dharco - // refer to https://github.com/dharco/UniLua/commit/2854ddf2500ab2f943f01a6d3c9af767c092ce75 - public class ULDebug - { - public static System.Action Log = NoAction; - public static System.Action LogError = NoAction; + // thanks to dharco + // refer to https://github.com/dharco/UniLua/commit/2854ddf2500ab2f943f01a6d3c9af767c092ce75 + public class ULDebug + { + public static System.Action Log = NoAction; + public static System.Action LogError = NoAction; - private static void NoAction(object msg) { } + private static void NoAction(object msg) { } - static ULDebug() - { -#if UNITY - Log = Logger.Log; - LogError = Logger.LogError; -#endif - } - } + static ULDebug() + { + Serilog.Log.Logger = new LoggerConfiguration() + .MinimumLevel.Debug() + .WriteTo.Debug() + .CreateLogger(); + Log = (ob) => + { + Serilog.Log.Information("LUA:" + (String)ob); + }; + LogError = (ob) => + { + Serilog.Log.Error("LUA:" + (String)ob); + }; + + } + } } diff --git a/Assets/UniLua/ULDebug.cs.meta b/Assets/UniLua/ULDebug.cs.meta deleted file mode 100644 index 13e1878..0000000 --- a/Assets/UniLua/ULDebug.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 1821463dbab7c5b458d1c7ccbe39259a -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/Undump.cs.meta b/Assets/UniLua/Undump.cs.meta deleted file mode 100644 index 166f130..0000000 --- a/Assets/UniLua/Undump.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 889bfc952bfafdf4e9b02ca797df96e3 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/UniLuaX.csproj b/Assets/UniLua/UniLuaX.csproj new file mode 100644 index 0000000..dde8c03 --- /dev/null +++ b/Assets/UniLua/UniLuaX.csproj @@ -0,0 +1,17 @@ + + + + net7.0-windows + enable + enable + + + + + + + + + + + diff --git a/Assets/UniLua/Util.cs.meta b/Assets/UniLua/Util.cs.meta deleted file mode 100644 index 77be9fe..0000000 --- a/Assets/UniLua/Util.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: e8e03e8ba75219a4b9ed5f669f6bc86b -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Assets/UniLua/VM.cs.meta b/Assets/UniLua/VM.cs.meta deleted file mode 100644 index 287ddc4..0000000 --- a/Assets/UniLua/VM.cs.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: e93ace87c0c51dd448bc307c176c8c33 -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: diff --git a/Grinder.Infrastructure/Config/ConfigStream.Json/ConfigStream.Json.csproj.DotSettings b/Grinder.Infrastructure/Config/ConfigStream.Json/ConfigStream.Json.csproj.DotSettings new file mode 100644 index 0000000..73e9656 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.Json/ConfigStream.Json.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.Json/Configuration.Persistence.Json.csproj.DotSettings b/Grinder.Infrastructure/Config/ConfigStream.Json/Configuration.Persistence.Json.csproj.DotSettings new file mode 100644 index 0000000..73e9656 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.Json/Configuration.Persistence.Json.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.Json/Configuration.Store.Json.csproj b/Grinder.Infrastructure/Config/ConfigStream.Json/Configuration.Store.Json.csproj new file mode 100644 index 0000000..81438d9 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.Json/Configuration.Store.Json.csproj @@ -0,0 +1,40 @@ + + + net6.0-windows + Library + FirstLineTamping.Configuration.Store.Json + ConfigStore.Json + false + latest + ConfigStream.Json + Oulida electric co., ltd. + Configuration json store module + Copyright %28c%29 Oulida 2020 + 1.0.0.0 + 1.0.0.0 + + + ..\bin\x86\Debug\ + latest + MinimumRecommendedRules.ruleset + + + ..\bin\x86\Release\ + MinimumRecommendedRules.ruleset + latest + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.Json/Documents/Readme.md b/Grinder.Infrastructure/Config/ConfigStream.Json/Documents/Readme.md new file mode 100644 index 0000000..b6cc1fc --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.Json/Documents/Readme.md @@ -0,0 +1,9 @@ +| Property | Value +|:-------------|:----------------------- +| Project Name | ConfigStream.Json +| Author | Liu Wan Li +| Create Time | 2019-04-09 + +# Summary +这个项目实现一个Json 文件格式的配置文件持久化版本。采用 Newtonsoft.Json 作为转换提供 +接口的定义在 TampingFoundation 项目的 Configs 中 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.Json/JsonConfigStore.cs b/Grinder.Infrastructure/Config/ConfigStream.Json/JsonConfigStore.cs new file mode 100644 index 0000000..d3cbd64 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.Json/JsonConfigStore.cs @@ -0,0 +1,248 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using tracedrive_one.Configuration.Helper; +using tracedrive_one.Configuration.StreamProvider; + +#pragma warning disable 162 + +namespace tracedrive_one.Configuration.Store.Json +{ + /// + /// JSON 配置文件转换流 + /// + public class JsonConfigStore : IConfigStore + { + /// + /// 配置文件读写流(这个设计到技术资源,例如磁盘, 单独抽出来为了更好的单元测试) + /// + private readonly IStreamProvider _configStream; + + public JsonConfigStore(IStreamProvider configStream) + { + if (configStream == null) + throw new ArgumentNullException(nameof(configStream)); + + _configStream = configStream; + } + + /// + /// 数据源由外部更新事件 + /// + public event AsyncEventHandler SourceChanged + { + add + { + var streamProviderWithNotification = _configStream as IStreamProviderWithNotification; + if (streamProviderWithNotification != null) + streamProviderWithNotification.StreamSourceChanged += value; + } + remove + { + var streamProviderWithNotification = _configStream as IStreamProviderWithNotification; + if (streamProviderWithNotification != null) + streamProviderWithNotification.StreamSourceChanged -= value; + } + } + + #region Implementation of IConfigStream + + /// + /// 从数据流中加载 配置文件 对象 + /// + /// + public async Task LoadAsync() + { + using (var stream = _configStream.OpenRead()) + using (var reader = new StreamReader(stream)) + { + // 读取配置信息 + var settingString = await reader.ReadToEndAsync(); + if (string.IsNullOrEmpty(settingString)) + return new ConfigData(); + + return ExtraSettingFromString(settingString); + } + } + + /// + /// 加载配置文件对象 + /// + /// + public ConfigData Load() + { + using (var stream = _configStream.OpenRead()) + using (var reader = new StreamReader(stream)) + { + // 读取配置信息 + var settingString = reader.ReadToEnd(); + if (string.IsNullOrEmpty(settingString)) + return new ConfigData(); + + return ExtraSettingFromString(settingString); + } + } + + /// + /// 配置文件保存至数据流 + /// + /// 配置信息数据 + /// + public async Task SaveAsync(ConfigData section) + { + string settingString = GenerateSettingString(section); + using (var stream = _configStream.OpenWrite()) + using (StreamWriter writer = new StreamWriter(stream)) + { + await writer.WriteAsync(settingString); + await writer.FlushAsync(); + } + } + + + /// + /// 配置文件保存至数据流 + /// + /// 配置信息数据 + /// + public void Save(ConfigData section) + { + string settingString = GenerateSettingString(section); + using (var stream = _configStream.OpenWrite()) + using (StreamWriter writer = new StreamWriter(stream)) + { + writer.Write(settingString); + writer.Flush(); + } + } + + /// + /// 生成设置参数字符串 + /// + /// + /// + private static string GenerateSettingString(ConfigData data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + // 转换为一个Dictionary嵌套,然后写入 + + var stack = new Stack>>(); + var referenced = new HashSet(); // 保存已经读取的节点 + + var rootDict = new Dictionary(); + stack.Push(new Tuple>("", rootDict)); + + // 递归转换 + while (stack.Any()) + { + var cur = stack.Pop(); + var curPath = cur.Item1; + var curDict = cur.Item2; + + // 检查循环引用 + if (referenced.Contains(curPath)) + throw new ArgumentException("Circular reference in config section"); + referenced.Add(curPath); + + // 处理值 + var valuePaths = data.GetChildrenNodes(curPath, false); + foreach (var valuePath in valuePaths) + { + var path = ConfigPath.CombinePath(curPath, valuePath); + var cp = new ConfigPath(valuePath); + var configValue = data.OpenConfigValue(path); + + var value = configValue.Value; + + // 处理枚举 + if (value is Enum e) + value = e.ToString(); + + curDict.Add(cp.Current, value); + } + + // 处理节点 + var sectionPaths = data.GetChildrenNodes(curPath, true); + foreach (var sectionPath in sectionPaths) + { + var path = ConfigPath.CombinePath(curPath, sectionPath); + + var dictionary = new Dictionary(); + var cp = new ConfigPath(sectionPath); + curDict.Add(cp.Current, dictionary); + + stack.Push(new Tuple>(path, dictionary)); + } + } + + // 执行序列化,并写到流中 + var settingString = JsonConvert.SerializeObject(rootDict, Formatting.Indented); + return settingString; + } + + #endregion + + /// + /// 解析处理设置参数 + /// + /// + /// + private static ConfigData ExtraSettingFromString(string settingString) + { + // 执行转换 + var root = JsonConvert.DeserializeObject(settingString) as JObject; + var data = new ConfigData(); + var stack = new Stack>(new[] + { + new Tuple(root, "") + }); + var referenced = new HashSet(); + + // 递归执行JObject至ConfigSection的转换 + while (stack.Any()) + { + var cur = stack.Pop(); + var curItem = cur.Item1; + var curPath = cur.Item2; + + // 引用检查 + if (referenced.Contains(curItem)) + continue; + referenced.Add(curItem); + + // 执行本次转换 + foreach (var item in curItem) + { + var combinePath = ConfigPath.CombinePath(curPath, item.Key); + + // 如果是一个 JObject,递归转换下去 + var jObject = item.Value as JObject; + if (jObject != null) + { + stack.Push(new Tuple(jObject, combinePath)); + continue; + } + + // 处理值 + var jValue = item.Value as JValue; + if (jValue != null) + { + var cv = data.OpenOrCreateConfigValue(combinePath); + cv.Value = jValue.Value; + continue; + } + + throw new NotSupportedException($"Object type {item.Value.GetType().Name} is not supported"); + } + } + + return data; + } + } +} diff --git a/Grinder.Infrastructure/Config/ConfigStream.Json/Properties/AssemblyInfo.cs b/Grinder.Infrastructure/Config/ConfigStream.Json/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..a308afc --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.Json/Properties/AssemblyInfo.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using System.Runtime.InteropServices; +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("5d22f933-5bf8-4db1-be3c-cac8765dabce")] diff --git a/Grinder.Infrastructure/Config/ConfigStream.JsonTests/ConfigStream.JsonTests.csproj.DotSettings b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/ConfigStream.JsonTests.csproj.DotSettings new file mode 100644 index 0000000..73e9656 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/ConfigStream.JsonTests.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Persistence.JsonTests.csproj.DotSettings b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Persistence.JsonTests.csproj.DotSettings new file mode 100644 index 0000000..73e9656 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Persistence.JsonTests.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Store.JsonTests.csproj b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Store.JsonTests.csproj new file mode 100644 index 0000000..c3a1e9d --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Store.JsonTests.csproj @@ -0,0 +1,138 @@ + + + + + + Debug + AnyCPU + {F5808BED-35F8-41AA-A126-5E3D65A8D853} + Library + Properties + ConfigStream.JsonTests + ConfigStream.JsonTests + v4.6.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + 7.0 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + 7.0 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + 6 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + + ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll + True + + + + + + + + + + + + + + + + + + {5D22F933-5BF8-4DB1-BE3C-CAC8765DABCE} + Configuration.Store.Json + + + {E70AADB2-1940-454B-8BFF-1A674F7D39F0} + Configuration + + + {B3F4ED07-9A02-4926-B827-3AFA00330E58} + TampingInfrastructure + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Store.JsonTests.csproj.DotSettings b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Store.JsonTests.csproj.DotSettings new file mode 100644 index 0000000..73e9656 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Configuration.Store.JsonTests.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigStream.JsonTests/JsonConfigStreamTests.cs b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/JsonConfigStreamTests.cs new file mode 100644 index 0000000..fbe24fd --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/JsonConfigStreamTests.cs @@ -0,0 +1,50 @@ +using System.IO; +using System.Threading.Tasks; +using FirstLineTamping.Configuration; +using FirstLineTamping.Configuration.Store.Json; +using FirstLineTamping.Configuration.StreamProvider; +using NUnit.Framework; + +namespace ConfigStream.JsonTests +{ + [TestFixture()] + public class JsonConfigStreamTests + { + /// + /// 先写入,再读出,验证一致 + /// + /// + [Test()] + public async Task SaveThenLoadTest() + { + var config = new Config(); + + var person = config.GetSection(); + person.SetValue("Name", "Nepton"); + person.SetValue("Age", 38); + person.SetValue("Sex", "M"); + person.SetValue("City", "Kunming"); + + var child = person.GetSection("Child"); + child.SetValue("Name", "doudou"); + child.SetValue("Age", "6"); + + var stream = new MemoryStreamProvider(); + config.SetConfigStore(new JsonConfigStore(stream)); + + // 写入 + await config.SaveAsync(); + + // 读出 + var newConfig = new Config(new JsonConfigStore(stream)); + await newConfig.LoadAsync(); + + // 验证 + Assert.AreEqual(person.GetValue("Name"), newConfig.GetValue("Name")); + Assert.AreEqual(person.GetValue("Age"), newConfig.GetValue("Age")); + Assert.AreEqual(person.GetValue("Sex"), newConfig.GetValue("Sex")); + Assert.AreEqual(person.GetValue("Child.Age"), newConfig.GetValue("Child.Age")); + Assert.AreEqual(person.GetValue("City"), newConfig.GetValue("City")); + } + } +} diff --git a/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Properties/AssemblyInfo.cs b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c7e07ee --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ConfigStream.JsonTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ConfigStream.JsonTests")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f5808bed-35f8-41aa-a126-5e3d65a8d853")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Grinder.Infrastructure/Config/ConfigStream.JsonTests/packages.config b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/packages.config new file mode 100644 index 0000000..e897f2a --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigStream.JsonTests/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/Configuration/Config.cs b/Grinder.Infrastructure/Config/Configuration/Config.cs new file mode 100644 index 0000000..3292c69 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/Config.cs @@ -0,0 +1,653 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using GrinderApp.Configuration.Helper; + +namespace GrinderApp.Configuration +{ + /// + /// 配置信息的数据全部保存在该对象 + /// + public class Config + { + /// + /// 配置文件持久化接口 + /// + private IConfigStore _configStore; + + /// + /// 存储数据 + /// + private ConfigData _data = new(); + + /// + /// 允许一个执行者和一个等待着的队列,这个对象在属性更改自动保存的时候防止大批量执行。 + /// + private readonly OneWaiterTaskQueue _oneWaiterTaskQueue = new(); + + /// + /// 在读取节点时, 如果节点不存在,是否把默认值写入节点 + /// + public bool WriteDefaultValueEnable + { + get; + set; + } = true; + + /// + /// 构造函数,含有持久化模块 + /// + /// + public Config(IConfigStore configStore) + { + SetConfigStore(configStore); + Load(); + } + + public Config() + { + } + + #region 数据变更事件 + + /// + /// 内存内容变化,写值 + /// + private void OnConfigSourceChanged() + { + if (SaveMethod != SaveMethods.PropertyChanged || _configStore == null) + return; + + // 如果大量的保存请求到这儿, 只让一个保存正在执行和一个保存模块等待,提高性能 + _oneWaiterTaskQueue.TryEnqueue(async () => + { + // 滞后一秒执行保存,这样可以让保存执行最低间隔一秒,而且间隔一秒自动保存不会有什么影响 + await SaveAsync(); + await Task.Delay(1000); + }); + } + + /// + /// 数据源变更时,重新加载内容 + /// + /// + /// + /// + private async Task ConfigStore_SourceChanged(object sender, EventArgs e) + { + await LoadAsync(); + } + + #endregion + + #region Load & Save + + /// + /// 设置配置文件持久化接口 + /// + /// + public void SetConfigStore(IConfigStore newStore) + { + if (_configStore == newStore) + return; + + if (_configStore != null) + _configStore.SourceChanged -= ConfigStore_SourceChanged; + + _configStore = newStore; + _configStore.SourceChanged += ConfigStore_SourceChanged; + } + + /// + /// 持久化保存 + /// + /// + public async Task SaveAsync() + { + await SaveCoreAsync(true); + } + + /// + /// 加载配置信息 + /// + /// + public async Task LoadAsync() + { + await LoadCoreAsync(true); + } + + /// + /// 加载配置信息 + /// + public void Load() + { + LoadCore(true); + } + + /// + /// 持久化保存-内部执行 + /// + /// + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private async Task SaveCoreAsync(bool throwWhenStoreNotReady) + { + var configStore = _configStore; + + if (configStore == null) + { + if (throwWhenStoreNotReady) + throw new InvalidOperationException($"{nameof(IConfigStore)} is required"); + + return; + } + + await configStore.SaveAsync(_data); + } + + /// + /// 持久化保存参数另存为, 这个操作不应影响内部默认的 Store + /// + /// + public async Task SaveAsAsync(IConfigStore store) + { + if (store == null) + throw new ArgumentNullException(nameof(store)); + + await store.SaveAsync(_data); + } + + /// + /// 从指定的 Store 加载配置, 这个操作不应影响内部默认的 Store + /// + /// + public async Task LoadFromAsync(IConfigStore store) + { + if (store == null) + throw new ArgumentNullException(nameof(store)); + + _data = await store.LoadAsync() ?? new ConfigData(); + } + + /// + /// 加载配置-内部调用 + /// + /// + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private async Task LoadCoreAsync(bool throwWhenStoreNotReady) + { + var configStore = _configStore; + if (configStore == null) + { + if (throwWhenStoreNotReady) + throw new InvalidOperationException($"{nameof(IConfigStore)} is required"); + + return; + } + + _data = await configStore.LoadAsync() ?? new ConfigData(); + } + + /// + /// 加载配置-内部调用 + /// + /// + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + private void LoadCore(bool throwWhenStoreNotReady) + { + var configStore = _configStore; + if (configStore == null) + { + if (throwWhenStoreNotReady) + throw new InvalidOperationException($"{nameof(IConfigStore)} is required"); + + return; + } + + _data = configStore.Load() ?? new ConfigData(); + } + + /// + /// 写入文件的方式,通过手动调用 Flush 函数 or 属性更改后自动刷入 + /// + public SaveMethods SaveMethod + { + get; + set; + } = SaveMethods.PropertyChanged; + + #endregion + + #region Sections + + /// + /// 获取当前根节点 + /// + /// + public ConfigSection GetSection() + { + return new(this, ""); + } + + /// + /// 获取一个指定的子节点 + /// + /// + /// + public ConfigSection GetSection(string path) + { + return new(this, path); + } + + #endregion + + #region GetValue / SetValue + + /// + /// 读取值 + /// + /// 值的路径 + /// 期望的类型 + /// 默认值 + /// + public object GetValue(string path, Type expectedType, object defaultValue = default) + { + var item = _data.OpenConfigValue(path); + if (item != null) + return item.GetValue(expectedType, defaultValue); + + if (WriteDefaultValueEnable) + { + SetValue(path, defaultValue); + } + + return defaultValue; + } + + /// + /// 读取值 + /// + /// 值类型 + /// 值的路径 + /// 默认值 + /// + public TValue GetValue(string path, TValue defaultValue = default) + { + var item = _data.OpenConfigValue(path); + if (item != null) + return item.GetValue(defaultValue); + + if (WriteDefaultValueEnable) + { + SetValue(path, defaultValue); + } + + return defaultValue; + } + + /// + /// 读取值, 如果无法读取(例如路径不存在,无法转换类型),抛出异常 + /// + /// 值类型 + /// 值的路径 + /// + public TValue GetValueOrThrow(string path) + { + var item = _data.OpenConfigValue(path); + if (item != null) + return item.GetValue(); + + throw new ArgumentException("No value item in path"); + } + + + /// + /// 写入值 + /// + /// + /// + /// + public bool SetValue(string path, TValue value) + { + var cv = _data.OpenOrCreateConfigValue(path); + if (cv == null) + throw new ArgumentNullException(nameof(cv)); + + if (cv.SetValue(value)) + { + OnConfigSourceChanged(); + return true; + } + + return false; + } + + #endregion + + /// + /// 删除指定路径的所有项 + /// + /// + public void Remove(string path) + { + if (_data.Remove(path)) + OnConfigSourceChanged(); + } + + /// + /// 清空所有配置 + /// + public void Clear() + { + if (_data.Clear()) + OnConfigSourceChanged(); + } + + /// + /// 更名,把以 开头的路径更名为 + /// + /// 原路径 + /// 新路径 + public void Rename(string originPath, string newPath) + { + if (_data.Rename(originPath, newPath)) + OnConfigSourceChanged(); + } + + /// + /// 从另一个配置文件合并 + /// + /// + /// 是否覆盖已有数据 + public void MergeWith(Config config, bool overrideExists) + { + if (_data.MergeWith(config._data, overrideExists)) + OnConfigSourceChanged(); + } + + /// + /// 获取给定路径下的所有的第一层子路径的节点 + /// 例如对于如下路径:Order.Create.Name, GetSubPaths("Order", true) 返回 "Create" + /// + /// 指定的路径 + /// 要获取的路径是否拥有后续节点(是否为一个ConfigSection) + /// + public string[] GetChildrenNodes(string path, bool? hasMoreSubsequentPath = null) + { + return _data.GetChildrenNodes(path, hasMoreSubsequentPath); + } + + #region Convert From / To JObject + + /// + /// 转换为JObject + /// + /// + /// + public JObject ToJObject(string path) + { + // 转换为一个Dictionary嵌套,然后写入 + var stack = new Stack<(string Path, JObject JObject)>(); + var referenced = new HashSet(); // 保存已经读取的节点 + + var rootDict = new JObject(); + stack.Push((path, rootDict)); + + // 递归转换 + while (stack.Any()) + { + var cur = stack.Pop(); + var curPath = cur.Path; + + // 检查循环引用 + if (referenced.Contains(curPath)) + throw new ArgumentException("Circular reference in config section"); + referenced.Add(curPath); + + // 处理值 + var valuePaths = _data.GetChildrenNodes(curPath, false); + foreach (var valuePath in valuePaths) + { + var fullValuePath = ConfigPath.CombinePath(curPath, valuePath); + var cp = new ConfigPath(valuePath); + var configValue = _data.OpenConfigValue(fullValuePath); + + var value = configValue.Value; + + // 处理枚举 + if (value is Enum e) + value = e.ToString(); + + cur.JObject.Add(cp.Current, new JValue(value)); + } + + // 处理节点 + var sectionPaths = _data.GetChildrenNodes(curPath, true); + foreach (var sectionPath in sectionPaths) + { + var jObject = new JObject(); + var cp = new ConfigPath(sectionPath); + cur.JObject.Add(cp.Current, jObject); + + var fullSectionPath = ConfigPath.CombinePath(curPath, sectionPath); + stack.Push((fullSectionPath, jObject)); + } + } + + return rootDict; + } + + /// + /// 从 JObject 读取值, 然后组合到当前配置中 + /// + /// + /// + /// + public void FromJObject(string path, JObject jObject) + { + // 执行转换 + var stack = new Stack<(JObject JObject, string Path)>(); + stack.Push((jObject, path)); + var referenced = new HashSet(); + + // 递归执行JObject至ConfigSection的转换 + while (stack.Any()) + { + var cur = stack.Pop(); + + // 引用检查 + if (referenced.Contains(cur.JObject)) + continue; + referenced.Add(cur.JObject); + + // 执行本次转换 + foreach (var prop in cur.JObject) + { + var combinePath = ConfigPath.CombinePath(cur.Path, prop.Key); + + if (prop.Value == null) + continue; + + // 如果是一个 JObject,递归转换下去 + if (prop.Value is JObject jObjectSub) + { + stack.Push((jObjectSub, combinePath)); + continue; + } + + // 处理值 + if (prop.Value is JValue jValue) + { + var cv = _data.OpenOrCreateConfigValue(combinePath); + cv.Value = jValue.Value; + continue; + } + + throw new NotSupportedException($"Object type {prop.Value.GetType().Name} is not supported"); + } + } + } + + #endregion + + #region Convert From / To Dictionary + + /// + /// 生成设置参数字符串 + /// + /// + /// + public Dictionary ToDictionary(string path) + { + // 转换为一个Dictionary嵌套,然后写入 + var stack = new Stack<(string path, Dictionary dict)>(); + var referenced = new HashSet(); // 保存已经读取的节点 + + var rootDict = new Dictionary(); + stack.Push((path, rootDict)); + + // 递归转换 + while (stack.Any()) + { + var cur = stack.Pop(); + var curPath = cur.Item1; + var curDict = cur.Item2; + + // 检查循环引用 + if (referenced.Contains(curPath)) + throw new ArgumentException("Circular reference in config section"); + referenced.Add(curPath); + + // 处理值 + var valuePaths = _data.GetChildrenNodes(curPath, false); + foreach (var valuePath in valuePaths) + { + var fullValuePath = ConfigPath.CombinePath(curPath, valuePath); + var cp = new ConfigPath(valuePath); + var configValue = _data.OpenConfigValue(fullValuePath); + + var value = configValue.Value; + + // 处理枚举 + if (value is Enum e) + value = e.ToString(); + + curDict.Add(cp.Current, value); + } + + // 处理节点 + var sectionPaths = _data.GetChildrenNodes(curPath, true); + foreach (var sectionPath in sectionPaths) + { + var dictionary = new Dictionary(); + var cp = new ConfigPath(sectionPath); + curDict.Add(cp.Current, dictionary); + + var fullSectionPath = ConfigPath.CombinePath(curPath, sectionPath); + stack.Push((fullSectionPath, dictionary)); + } + } + + return rootDict; + } + + /// + /// 从 JObject 读取值, 然后组合到当前配置中 + /// + /// + /// + /// + public void FromDictionary(string path, Dictionary dict) + { + // 执行转换 + var stack = new Stack<(string Path, Dictionary Dict)>(); + stack.Push((path, dict)); + var referenced = new HashSet>(); + + // 递归执行JObject至ConfigSection的转换 + while (stack.Any()) + { + var cur = stack.Pop(); + + // 引用检查 + if (referenced.Contains(cur.Dict)) + continue; + referenced.Add(cur.Dict); + + // 执行本次转换 + foreach (var keyValue in cur.Dict) + { + var combinePath = ConfigPath.CombinePath(cur.Path, keyValue.Key); + + if (keyValue.Value == null) + continue; + + // 如果是一个 JObject,递归转换下去 + if (keyValue.Value is Dictionary subDict) + { + stack.Push((combinePath, subDict)); + continue; + } + + // 处理值 + if (keyValue.Value is JValue jValue) + { + var cv = _data.OpenOrCreateConfigValue(combinePath); + cv.Value = jValue.Value; + continue; + } + + throw new NotSupportedException($"Object type {keyValue.Value.GetType().Name} is not supported"); + } + } + } + + #endregion + + #region Convert From / To Object + + /// + /// 从指定的节点装换为一个设置对象 + /// + /// + /// + /// 是否声明子对象类型, (例如含有接口, 抽象类, 则需要传入 true) + /// + public T ToObject(string path, bool declareType = false) + { + var jObject = ToJObject(path); + + var setting = new JsonSerializer + { + TypeNameHandling = declareType ? TypeNameHandling.Auto : TypeNameHandling.None, + }; + + var obj = jObject.ToObject(setting); + return obj; + } + + /// + /// 从指定的节点装换为一个设置对象 + /// + /// + /// 要转换的 obj + /// 是否声明子对象类型, (例如含有接口, 抽象类, 则需要传入 true) + /// + public void FromObject(string path, object obj, bool declareType = false) + { + var setting = new JsonSerializer() + { + TypeNameHandling = declareType ? TypeNameHandling.Auto : TypeNameHandling.None + }; + var jObject = JObject.FromObject(obj, setting); + + FromJObject(path, jObject); + } + + #endregion + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigBase.cs b/Grinder.Infrastructure/Config/Configuration/ConfigBase.cs new file mode 100644 index 0000000..58a1688 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigBase.cs @@ -0,0 +1,284 @@ +using Serilog; +using System.Collections.Generic; +using System.ComponentModel; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace GrinderApp.Configuration +{ + /// + /// 强类型命名配置对象基类 + /// 该对象是操作Config的基类 + /// + /// + /// 2019-10-16 11:34:51 增加接口 INotifyPropertyChanged TODO 已知问题:1 没有检测变化 2 不是所有的 SetValue 入口都进行了监控 + /// + public abstract class ConfigBase : INotifyPropertyChanged, IDefaultConfigWriter + { + private readonly Config _config; + + public Config Config => _config; + + /// + /// 仅返回默认值的模式,这个模式给 RestoreDefaultValue 使用,用户调用了 RestoreDefaultValue 后,程序打开这个开关,然后 GetValue....仅返回默认值,而不查询存储的内容。同时 RestoreDefaultValue 函数把读取到的默认值写入磁盘。 + /// + private bool _returnDefaultValueMode; + + /// + /// 定义作用域(包) + /// 在多个配置文件合并的时候,用来区分不同的作用域 + /// + public abstract string PathName + { + get; + } + + /// + /// 构造函数, + /// + /// 强命名该分组 + protected ConfigBase(Config config) + { + _config = config; + } + + + + + /// + /// 获取配置参数属性的描述信息 + /// + /// + public Dictionary GetPropertyDescriptions() + { + var dict = new Dictionary(); + + var props = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + foreach (var prop in props) + { + var attr = prop.GetCustomAttribute(true); + if (attr == null) + continue; + + var path = ConfigPath.CombinePath(PathName, prop.Name); + dict.Add(path, attr.Description); + } + + return dict; + } + + + + #region Get / Set Value just for Property :) + + /// + /// 获取值, 该函数使用在对属性执行读写操作时 + /// + /// 值类型 + /// 如果没有存储该值,返回默认值 + /// 属性名 + /// + public TValue GetPropertyValue(TValue defaultValue, [CallerMemberName] string propertyName = default) + { + if (_returnDefaultValueMode) + return defaultValue; + + // 如果没有指定路径, 返回默认值, 而且不操作存储库 + if (PathName is not { } pathName) + return defaultValue; + + return _config.GetValue(ConfigPath.CombinePath(pathName, propertyName), defaultValue); + } + + + /// + /// 设置属性默认值 + /// + /// 值类型 + /// 目标值 + /// 属性名 + public void SetPropertyValue(TValue value, [CallerMemberName] string propertyName = default) + { + // 如果没有指定路径, 不操作存储库 + if (PathName is not { } pathName) + return; + + _config.SetValue(ConfigPath.CombinePath(pathName, propertyName), value); + OnPropertyChanged(propertyName); + } + + /// + /// 重命名属性节点 + /// + /// 原属性名 + /// 属性名 + public void RenameProperty(string originPropertyName, string newPropertyName) + { + // 如果没有指定路径, 不操作存储库 + if (PathName is not { } pathName) + return; + + var originPath = ConfigPath.CombinePath(pathName, originPropertyName); + var newPath = ConfigPath.CombinePath(pathName, newPropertyName); + _config.Rename(originPath, newPath); + } + + #endregion + + /// + /// 获取完整路径的值, 如果找不到,返回默认值。 + /// 如果你不需要指定完整路径, 请调用 GetPropertyValue + /// + /// 值的类型 + /// 保存配置参数的全路径 + /// 默认值,如果无法读取到内容,返回该值 + /// + public TValue GetValue(string path, TValue defaultValue) + { + if (_returnDefaultValueMode) + return defaultValue; + + return _config.GetValue(path, defaultValue); + } + + /// + /// 获取指定路径的值,如果无法获取到值,将抛出异常(用于防止路径指定错误) + /// 如果你不需要指定完整路径, 请调用 GetPropertyValue + /// + /// 值的类型 + /// 保存配置参数的全路径 + /// + public TValue GetValue(string path) + { + if (_returnDefaultValueMode) + return default; + + // todo 考虑抛出异常的合理性,暂时关闭异常抛出的方式 + //return _config.GetValueOrThrow(path); + return _config.GetValue(path, default(TValue)); + } + + /// + /// 获取完整路径的值 + /// 如果你不需要指定完整路径, 请调用 SetPropertyValue + /// + /// 值的类型 + /// 全路径 + /// 值 + /// + public bool SetValue(string path, TValue value) + { + return _config.SetValue(path, value); + } + + /// + /// 恢复默认值 + /// + public void RestoreDefaultValue() + { + var props = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (var prop in props) + { + RestoreDefaultValue(prop); + } + } + public void xr() + { + var props = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (var prop in props) + { + try + { + + // 仅返回默认值的模式,这个模式给 RestoreDefaultValue 使用,用户调用了 RestoreDefaultValue 后,程序打开这个开关, + // 然后 GetValue....仅返回默认值,而不查询存储的内容。同时 RestoreDefaultValue 函数把读取到的默认值写入磁盘。 + if (prop.CanRead && prop.CanWrite) + { + var value = prop.GetValue(this); + // prop.SetValue(this, value); + Log.Information($""); + // Raise property changed + OnPropertyChanged(nameof(prop.Name)); + } + } + finally + { + _returnDefaultValueMode = false; + } + } + } + + /// + /// 恢复默认值 + /// + /// + public void RestoreDefaultValue(string propertyName) + { + var prop = GetType().GetProperty(propertyName); + if (prop != null) + RestoreDefaultValue(prop); + } + + /// + /// 恢复默认值 + /// + /// + public void RestoreDefaultValue(PropertyInfo prop) + { + try + { + _returnDefaultValueMode = true; + + // 仅返回默认值的模式,这个模式给 RestoreDefaultValue 使用,用户调用了 RestoreDefaultValue 后,程序打开这个开关, + // 然后 GetValue....仅返回默认值,而不查询存储的内容。同时 RestoreDefaultValue 函数把读取到的默认值写入磁盘。 + if (prop.CanRead && prop.CanWrite) + { + var value = prop.GetValue(this); + prop.SetValue(this, value); + + // Raise property changed + OnPropertyChanged(nameof(prop.Name)); + } + } + finally + { + _returnDefaultValueMode = false; + } + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + /// + /// 把默认值持久化写入 + /// + /// + public virtual void WriteDefaultValue() + { + // TODO 目前设计不合理,重写该方法,并做相应重构,配置参数读取时不要默认写入. 其中这里应改为 prop.SetValue(prop.GetValue()); + + var props = GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + + foreach (var prop in props) + { + // TODO 判断可读取时去尝试读取加载,并捕获异常,验证这样一来的可靠性 + try + { + // 在读取值的时候,如果发现值不存在,GetValue函数将自动追加一个节点到 ConfigSection 中 + if (prop.CanRead) + prop.GetValue(this); + } + catch + { + // no code here + } + } + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigData.cs b/Grinder.Infrastructure/Config/Configuration/ConfigData.cs new file mode 100644 index 0000000..92175b1 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigData.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Text.RegularExpressions; + +namespace GrinderApp.Configuration +{ + /// + /// 这个类负责存储所有的数据,新版本改为扁平化存储,取消树状存储形式 + /// + public class ConfigData + { + /// + /// 存储所有的设置参数 + /// + private readonly ConcurrentDictionary _dict = new(); + + /// + /// 打开子节点,如果不存在则创建一个 + /// + /// + /// + public ConfigValue OpenOrCreateConfigValue(string path) + { + ConfigPath.EnsurePathNotEmpty(path); + return _dict.GetOrAdd(path, _ => new ConfigValue()); + } + + /// + /// 打开子节点 + /// + /// + /// + public ConfigValue OpenConfigValue(string path) + { + ConfigPath.EnsurePathNotEmpty(path); + + _dict.TryGetValue(path, out var value); + return value; + } + + /// + /// 移除指定路径的一个参数项,或者一个Section下的所有参数项 + /// + /// 需要删除的参数的全路径 + /// 删除成功 + public bool Remove(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + ConfigPath.EnsurePathNotEmpty(path); + + // 找到所有的路径 + var r = new Regex($@"^{path}(\.|$)", RegexOptions.Compiled); + var q = from key in _dict.Keys + where r.IsMatch(key) + select key; + + // 删除这些Key对应的项目 + return q.Aggregate(false, (current, key) => current | _dict.TryRemove(key, out _)); + } + + /// + /// 更名,把以 开头的路径更名为 + /// + /// 原路径 + /// 新路径 + /// 如果数据发生了更改,返回True,否则返回False + public bool Rename(string originPath, string newPath) + { + if (originPath == null) + throw new ArgumentNullException(nameof(originPath)); + if (newPath == null) + throw new ArgumentNullException(nameof(newPath)); + + ConfigPath.EnsurePathNotEmpty(originPath); + ConfigPath.EnsurePathNotEmpty(newPath); + + // 找到所有的路径 + var r = new Regex($@"^{originPath}(\.|$)", RegexOptions.Compiled); + var q = from key in _dict.Keys + where r.IsMatch(key) + select key; + + var hasChanged = false; + // 执行更名 + foreach (var path in q) + { + var newFullPath = Regex.Replace(path, $@"^{originPath}", newPath); + + if (_dict.TryRemove(path, out var value)) + { + _dict[newFullPath] = value; + + hasChanged = true; + } + } + + return hasChanged; + } + + /// + /// 获取给定路径下的所有的第一层子路径的节点 + /// 例如对于如下路径:Order.Create.Name, GetSubPaths("Order", true) 返回 "Create" + /// + /// 指定的路径 + /// 要获取的路径是否拥有后续节点(是否为一个ConfigSection) + /// + public string[] GetChildrenNodes(string path, bool? hasMoreSubsequentPath = null) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + ConfigPath.EnsurePathValidated(path); + + if (string.IsNullOrEmpty(path) == false) + path += ConfigPath.PathSeparator; + + // 找到所有的路径 + var q = from key in _dict.Keys + where key.StartsWith(path) + let cp = new ConfigPath(key.Substring(path.Length)) + where hasMoreSubsequentPath.HasValue == false || cp.HasMoreSubsequentPath == hasMoreSubsequentPath + orderby cp.Current + select cp.Current; + + return q.Distinct().ToArray(); + } + + /// + /// 清空所有保存的配置信息 + /// + public bool Clear() + { + if (_dict.Any()) + { + _dict.Clear(); + return true; + } + + return false; + } + + /// + /// 从另一个配置存储合并配置参数 + /// + /// 另一个配置存储 + /// 如果出现路径重复,是否覆盖当前对象的数据 + public bool MergeWith(ConfigData second, bool overrideExists) + { + var changed = false; + + foreach (var keyValue in second._dict) + { + if (!overrideExists && _dict.ContainsKey(keyValue.Key)) + continue; + + var node = OpenOrCreateConfigValue(keyValue.Key); + if (!Equals(node.Value, keyValue.Value.Value)) + { + node.Value = keyValue.Value.Value; + changed = true; + } + } + + return changed; + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigDataVisitor.cs b/Grinder.Infrastructure/Config/Configuration/ConfigDataVisitor.cs new file mode 100644 index 0000000..5ea41b8 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigDataVisitor.cs @@ -0,0 +1,20 @@ +namespace GrinderApp.Configuration +{ + /// + /// 用来遍历 ConfigTree 的算法 + /// // TODO 有时间重构 Config 里的三个遍历算法, 剥离遍历和 + /// + internal class ConfigDataVisitor + { + private readonly ConfigData _data; + + public ConfigDataVisitor(ConfigData data) + { + _data = data; + } + + public void Visit(string path) + { + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigFactory.cs b/Grinder.Infrastructure/Config/Configuration/ConfigFactory.cs new file mode 100644 index 0000000..995bd2f --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigFactory.cs @@ -0,0 +1,97 @@ +using GrinderApp.Configuration.Store.Json; +using GrinderApp.Configuration.StreamProvider; +using GrinderApp.Configuration; +using Serilog; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrinderApp.Configuration +{ + /// + /// 配置参数管理器 + /// + public static class ConfigFactory + { + /// + /// 应用程序的全局配置参数对象 + /// + private static Config _config; + + /// + /// MachineSetting 创建锁 + /// + private static readonly object _configLock = new object(); + + /// + /// 大机参数 + /// + public static Config Config + { + get + { + if (_config == null) + { + lock (_configLock) + { + if (_config == null) + { + //var baseFolder = new MachineFolderService(); + // var path = baseFolder.GetPreparedFolder(AppFolder.Setting); + var path= Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + Log.Information($"Loading settings from {path}"); + var filePath = Path.Combine(path, "setting.json"); + + // 如果没有配置文件,使用工厂设置参数 + var fileExist = File.Exists(filePath); + var config = new Config(new JsonConfigStore(new FileStreamProvider(filePath))); + + // 使用工厂设置覆盖(如果有) + if (!fileExist) + MergeWithFactory(config); + + _config = config; + } + } + } + + return _config; + } + } + + /// + /// 复位所有的设置 + /// + public static void Reset() + { + try + { + // 清空系统的设置 + Config.Clear(); + + // 使用工厂设置覆盖(如果有) + MergeWithFactory(Config); + } + catch (Exception ex) + { + Log.Error(ex, "Cannot reset config settings"); + } + } + + /// + /// 从工厂默认参数恢复 + /// + /// 设置参数 + private static void MergeWithFactory(Config config) + { + var factoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory ?? string.Empty, "factory-setting.json"); + if (File.Exists(factoryPath)) + { + var factorySetting = new Config(new JsonConfigStore(new FileStreamProvider(factoryPath))); + config.MergeWith(factorySetting, true); + } + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigPath.cs b/Grinder.Infrastructure/Config/Configuration/ConfigPath.cs new file mode 100644 index 0000000..7cca95f --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigPath.cs @@ -0,0 +1,165 @@ +using System; +using System.Linq; + +namespace GrinderApp.Configuration +{ + /// + /// 该对象负责处理配置路径字符串 + /// + public class ConfigPath + { + #region Static elements + /// + /// 检查Key 是否合法 + /// + /// + /// + public static bool IsNodeValidated(string newKey) + { + return newKey?.Length > 0 && newKey.Contains(PathSeparator) == false; + } + + /// + /// 确保路径不空 + /// + /// + public static void EnsurePathNotEmpty(string path) + { + var nodes = SplitPath(path); + if (nodes.Any() == false) + throw new ArgumentException("Path is empty"); + } + + /// + /// 确保Path合法,如果有问题,报错 + /// + /// + public static void EnsurePathValidated(string path) + { + SplitPath(path); + } + + /// + /// 通过传入的Path,生成路径数组 + /// + /// 路径字符串 + /// + public static string[] SplitPath(string fullPath) + { + if (fullPath == null) + throw new ArgumentNullException(nameof(fullPath)); + + if (fullPath == string.Empty) + return new string[0]; + + // 格式化当前的路径 + var paths = fullPath.Split(PathSeparator).Select(c => c.Trim()).ToArray(); + + // 组合路径前验证合法性 + if (paths.Any(string.IsNullOrEmpty)) + throw new ArgumentException("Path illegal, some path node is empty or whitespace"); + + return paths; + } + + /// + /// 组合路径 + /// + /// + /// + public static string CombinePath(params string[] paths) + { + if (paths == null) + throw new ArgumentNullException(nameof(paths)); + + // 过滤无效内容 + paths = paths.Where(c => string.IsNullOrEmpty(c) == false).ToArray(); + + // 格式化当前的路径 + paths = paths.SelectMany(c => c.Split(PathSeparator)).Select(c => c.Trim()).ToArray(); + + // 组合路径前验证合法性 + if (paths.Any(string.IsNullOrEmpty)) + throw new ArgumentException("Path illegal, some path node is empty or whitespace"); + + return string.Join(PathSeparator.ToString(), paths); + } + + /// + /// 路径分隔符 + /// + public const char PathSeparator = '.'; + + #endregion + + /// + /// 路径节点数组 + /// + private readonly string[] _paths; + + /// + /// 通过路径数组创建对象 + /// + /// 路径数组,已拆分的路径 + public ConfigPath(string path) + { + if (path == null) + throw new ArgumentNullException(nameof(path)); + + _paths = SplitPath(path); + } + + /// + /// 判断路径是否为空 + /// + /// + public bool IsEmpty => _paths.Length == 0; + + /// + /// 获取路径头。 + /// 例如 a.b.c 的路径头为 a + /// + /// + public string Current + { + get + { + if (_paths.Length == 0) + throw new ArgumentException("Path illegal, this is the empty path"); + + return _paths.First(); + } + } + + /// + /// 是否有更多的子节点 + /// + public bool HasMoreSubsequentPath => _paths.Length > 1; + + /// + /// 获取后续的路径。 + /// 例如 a.b.c 的后续路径为 b.c + /// + /// + public string SubsequentPath + { + get + { + if (_paths.Length == 0) + throw new ArgumentException("Path illegal, this is the last node of path"); + + var list = _paths.ToList(); + list.RemoveAt(0); + + return CombinePath(list.ToArray()); + } + } + + /// + /// 返回路径 + /// + public string[] Paths => _paths; + + + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigSection.cs b/Grinder.Infrastructure/Config/Configuration/ConfigSection.cs new file mode 100644 index 0000000..6eb2e25 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigSection.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json.Linq; + +namespace GrinderApp.Configuration +{ + /// + /// 这个类负责保存配置信息数据,意味着一个配置信息分组 + /// + public class ConfigSection + { + private readonly Config _config; + + private readonly string _pathPrefix; + + /// Initializes a new instance of the class. + public ConfigSection(Config config, string pathPrefix) + { + _config = config; + _pathPrefix = pathPrefix; + } + public string GetFullPath(string path) + { + return ConfigPath.CombinePath(_pathPrefix, path); + } + + /// + /// 获取值 + /// + /// 路径 + /// 配置参数值的数据类型 + /// 默认值,如果值不存在返回默认值 + /// + public object GetValue(string path, Type expectedType, object defaultValue = default) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Value cannot be null or empty.", nameof(path)); + + path = ConfigPath.CombinePath(_pathPrefix, path); + + return _config.GetValue(path, expectedType, defaultValue); + } + + /// + /// 获取值 + /// + /// 要获取的值的类型 + /// 路径 + /// 默认值,如果值不存在返回默认值 + /// + public TValue GetValue(string path, TValue defaultValue = default) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Value cannot be null or empty.", nameof(path)); + + path = ConfigPath.CombinePath(_pathPrefix, path); + + return _config.GetValue(path, defaultValue); + } + + /// + /// 获取指定Key对应的子Section对象 + /// + /// + /// + public ConfigSection GetSection(string path) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Value cannot be null or empty.", nameof(path)); + + path = ConfigPath.CombinePath(_pathPrefix, path); + + return _config.GetSection(path); + } + + /// + /// 获取所有子 Section + /// + /// + public string[] GetChildrenNodes(bool? hasMoreSubsequentPath = null) + { + return _config.GetChildrenNodes(_pathPrefix, hasMoreSubsequentPath); + } + + /// + /// 设置配置值 + /// + /// 值的类型 + /// 路径 + /// 默认值 + public void SetValue(string path, TValue value) + { + if (string.IsNullOrEmpty(path)) + throw new ArgumentException("Value cannot be null or empty.", nameof(path)); + + path = ConfigPath.CombinePath(_pathPrefix, path); + + _config.SetValue(path, value); + } + + + /// + /// 删除指定路径的所有项 + /// + /// + public void Remove(string path) + { + path = ConfigPath.CombinePath(_pathPrefix, path); + + _config.Remove(path); + } + + + /// + /// 执行更名 + /// + /// + /// + public void Rename(string originName, string newName) + { + var originPath = ConfigPath.CombinePath(_pathPrefix, originName); + var newPath = ConfigPath.CombinePath(_pathPrefix, newName); + + _config.Rename(originPath, newPath); + } + + #region Convert From / To JObject + + /// + /// 转换为JObject + /// + /// + public JObject ToJObject() + { + return _config.ToJObject(_pathPrefix); + } + + /// + /// 从 JObject 读取值, 然后组合到当前配置中 + /// + /// + /// + public void FromJObject(JObject jObject) + { + _config.FromJObject(_pathPrefix, jObject); + } + + #endregion + + #region Convert From / To Dictionary + + /// + /// 生成设置参数字符串 + /// + /// + public Dictionary ToDictionary() + { + return _config.ToDictionary(_pathPrefix); + } + + /// + /// 从 JObject 读取值, 然后组合到当前配置中 + /// + /// + /// + public void FromDictionary(Dictionary dict) + { + _config.FromDictionary(_pathPrefix, dict); + } + + #endregion + + #region Convert From / To Object + + /// + /// 从指定的节点装换为一个设置对象 + /// + /// + /// 是否声明子对象类型, (例如含有接口, 抽象类, 则需要传入 true) + /// + public T ToObject(bool declareType = false) + { + return _config.ToObject(_pathPrefix, declareType); + } + + /// + /// 从指定的节点装换为一个设置对象 + /// + /// 要转换的 obj + /// 是否声明子对象类型, (例如含有接口, 抽象类, 则需要传入 true) + /// + public void FromObject(object obj, bool declareType = false) + { + _config.FromObject(_pathPrefix, obj, declareType); + } + + #endregion + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigSectionView.cs b/Grinder.Infrastructure/Config/Configuration/ConfigSectionView.cs new file mode 100644 index 0000000..1e03bfd --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigSectionView.cs @@ -0,0 +1,29 @@ +using GrinderApp.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrinderApp.Configuration +{ + + public class ConfigSectionView + { + public string Name + { + get; + } + + public ConfigSection Section + { + get; + } + + public ConfigSectionView(string name, ConfigSection section) + { + Name = name; + Section = section; + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/ConfigValue.cs b/Grinder.Infrastructure/Config/Configuration/ConfigValue.cs new file mode 100644 index 0000000..bc6677c --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/ConfigValue.cs @@ -0,0 +1,119 @@ +using System; +using System.Reflection.Metadata.Ecma335; + +namespace GrinderApp.Configuration +{ + /// + /// 配置参数的值 + /// + public class ConfigValue + { + /// + /// 存储的值 + /// + public object Value + { + get; + set; + } + + /// Returns a string that represents the current object. + /// A string that represents the current object. + public override string ToString() + { + return $"{Value}"; + } + + /// + /// 读取值 + /// + /// + /// + /// + public TValue GetValue(TValue defaultValue = default) + { + return (TValue) GetValue(typeof(TValue), defaultValue); + } + + /// + /// 读取值 + /// + /// + /// + /// + public object GetValue(Type expectedType, object defaultValue = default) + { + if (expectedType.IsInstanceOfType(Value)) + return Value; + + if (Value == null) + return defaultValue; + + // 尝试转换类型 + try + { + // 枚举的单独处理 + if (expectedType.IsEnum) + { + if (Value is string) + return Enum.Parse(expectedType, Value.ToString() ?? string.Empty, false); + + // ReSharper disable once PossibleInvalidCastException + return Value; + } + + // 处理其他情况 + var result = Convert.ChangeType(Value, expectedType); + return result; + } + catch + { + // ignored + } + + // 保存的值无法读取,返回默认值 + return defaultValue; + } + + /// + /// 获取值,如果无法转换,抛出异常 + /// + /// + /// + public TValue GetValueOrThrow() + { + if (Value is TValue) + return (TValue) Value; + + if (Value == null) + { + if (typeof(TValue).IsClass) + return default(TValue); + + throw new InvalidCastException($"Can not cast null to {typeof(TValue).Name}"); + } + + var result = (TValue) Convert.ChangeType(Value, typeof(TValue)); + return result; + } + + + /// + /// 写入值 + /// + /// + /// + /// return TRUE if value was changed, otherwise return FALSE + public bool SetValue(TValue value) + { + if (!Equals(value, Value)) + { + Value = value; + + return true; + } + + return false; + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/Configuration.csproj b/Grinder.Infrastructure/Config/Configuration/Configuration.csproj new file mode 100644 index 0000000..5c05d05 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/Configuration.csproj @@ -0,0 +1,29 @@ + + + + net8.0-windows7.0 + Oulida.Configuration + latest + enable + AnyCPU + AnyCPU;x64;x86 + + + none + false + + + none + false + + + none + false + + + + + + + + diff --git a/Grinder.Infrastructure/Config/Configuration/Document/configuration-class-dependencies-graph.png b/Grinder.Infrastructure/Config/Configuration/Document/configuration-class-dependencies-graph.png new file mode 100644 index 0000000..4688c6a Binary files /dev/null and b/Grinder.Infrastructure/Config/Configuration/Document/configuration-class-dependencies-graph.png differ diff --git a/Grinder.Infrastructure/Config/Configuration/Document/configuration-concept.png b/Grinder.Infrastructure/Config/Configuration/Document/configuration-concept.png new file mode 100644 index 0000000..610ce09 Binary files /dev/null and b/Grinder.Infrastructure/Config/Configuration/Document/configuration-concept.png differ diff --git a/Grinder.Infrastructure/Config/Configuration/Helper/AsyncEventHandler.cs b/Grinder.Infrastructure/Config/Configuration/Helper/AsyncEventHandler.cs new file mode 100644 index 0000000..03c9071 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/Helper/AsyncEventHandler.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace GrinderApp.Configuration.Helper +{ + public delegate Task AsyncEventHandler(object sender, EventArgs e); + + public delegate Task AsyncEventHandler(object sender, TEventArgs e); + +} diff --git a/Grinder.Infrastructure/Config/Configuration/Helper/OneWaiterTaskQueue.cs b/Grinder.Infrastructure/Config/Configuration/Helper/OneWaiterTaskQueue.cs new file mode 100644 index 0000000..e700fc3 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/Helper/OneWaiterTaskQueue.cs @@ -0,0 +1,82 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace GrinderApp.Configuration.Helper +{ + /// + /// 只允许一个等待着的任务队列,该队列允许一个执行者工作中,同时一个等待着在等待并进入工作 + /// 如果一个任务入队,并且发现已经有一个等待着,则返回False + /// + /// 场景:配置文件许多模块要求写入磁盘,如果当前有一个等待着等待写入,那么就把这件事情交给他吧,因为大家都是调用相同方法写入文件 + public class OneWaiterTaskQueue + { + /// + /// 等待者 + /// + private readonly SemaphoreSlim _waiter; + + /// + /// 执行者 + /// + private readonly SemaphoreSlim _executor; + + public OneWaiterTaskQueue() + { + _waiter = new SemaphoreSlim(1); + _executor = new SemaphoreSlim(1); + } + + /// + /// 是否有人在等待 + /// + public bool HasWaiter => _waiter.CurrentCount == 0; + + /// + /// 是否拥有异常 + /// + public bool HasFaulted => LastException != null; + + /// + /// 返回最后一个错误 + /// + public Exception LastException { get; private set; } + + /// + /// 尝试入队执行,如果已经有一个等待着,放弃入队尝试 + /// + /// + /// + public bool TryEnqueue(Func taskGenerator) + { + // 如果已经拥有一个等待者,return false; + if (_waiter.Wait(0) == false) + return false; + + Task.Run(async () => + { + // 得到等待权,开始等待执行 + await _executor.WaitAsync(); + + // 得到执行权,我不再是等待者,释放等待权信号,让给下一个等待者 + _waiter.Release(); + + try + { + await taskGenerator(); + } + catch (Exception ex) + { + LastException = ex; + } + finally + { + // 释放执行信号 + _executor.Release(); + } + }); + + return true; + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/IConfigStore.cs b/Grinder.Infrastructure/Config/Configuration/IConfigStore.cs new file mode 100644 index 0000000..c8bba12 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/IConfigStore.cs @@ -0,0 +1,47 @@ +using System.Threading.Tasks; +using GrinderApp.Configuration.Helper; + +namespace GrinderApp.Configuration +{ + /// + /// 配置信息持久化接口 + /// + /// 取消持久化的目标流,持久化方法自己完成实现 + public interface IConfigStore + { + /// + /// 从数据流中加载 配置文件 对象 + /// + /// + Task LoadAsync(); + + + /// + /// 加载配置文件对象 + /// + /// + ConfigData Load(); + + + /// + /// 配置文件保存至数据流 + /// + /// 配置信息数据 + /// + Task SaveAsync(ConfigData data); + + + /// + /// 配置文件保存至数据流 + /// + /// + /// + void Save(ConfigData data); + + + /// + /// 数据源变更事件 + /// + event AsyncEventHandler SourceChanged; + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/IDefaultConfigWriter.cs b/Grinder.Infrastructure/Config/Configuration/IDefaultConfigWriter.cs new file mode 100644 index 0000000..ed0f439 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/IDefaultConfigWriter.cs @@ -0,0 +1,14 @@ +namespace GrinderApp.Configuration +{ + /// + /// 接口负责把默认的配置文件参数持久化(这样一来用户可以直接修改被持久化的文件,提高效率) + /// + public interface IDefaultConfigWriter + { + /// + /// 把默认值持久化写入 + /// + /// + void WriteDefaultValue(); + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/PersistencePropertyAttribute.cs b/Grinder.Infrastructure/Config/Configuration/PersistencePropertyAttribute.cs new file mode 100644 index 0000000..c4799cf --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/PersistencePropertyAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace GrinderApp.Configuration +{ + /// + /// 需要持久化的设置参数特性头声明 + /// + public class PersistencePropertyAttribute : Attribute + { + public object DefaultValue { get; } + + public PersistencePropertyAttribute(object defaultValue) + { + DefaultValue = defaultValue; + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/README.md b/Grinder.Infrastructure/Config/Configuration/README.md new file mode 100644 index 0000000..9ebac5c --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/README.md @@ -0,0 +1,303 @@ +| Property | Value +|:-------------|:----------------------- +| Project Name | Configuration +| Author | Liu Wan Li +| Create Time | 2019-04-09 + + +| Date | Author | Description +|:-------------|:-----------|:------------------------------------- +| 2019-05-09 | Liu Wan Li | 重构配置参数模块,并更新文档 + + +# Summary +该模块用来为处理配置信息提供支持 +- 支持配置信息呈树状结构呈现 +- 支持配置信息多种方式的持久化 + +# Concept +**ConfigSection** + +ConfigSection 为配置资源的核心对象。一个 ConfigSection 就像一个目录,用来保存多个 +由 ConfigSection 和 object 组成的集合。参数信息以 object 的形态,保存于 ConfigSection +的集合中。 + +**Value** + +Value 用来保存参数的具体数据,可以为多种基础数据类型,例如 int double Guid DateTime等。 +值以 object 的形式保存与配置参数中,调用者需要清楚的了解值的类型。通过以下方法读取值: +```C# +// 调用者需清楚的知道,返回的数据类型为 int,在调用时传入期待返回的值类型 +var path = "Employee.Age"; +int value = data.GetValue(path); +``` + +> 原则上,Value 参数可以为任何类型,但是受限制于目前持久化的的设计,仅支持基础数据类型,例如 int string +> double float DateTime Guid 等。如果有需要,再扩充这部分功能。 + +从下图中可以看出,ConfigSection 就像 Windows 的目录,可以保存子 ConfigSection,亦可保存 Value。 + +![Configuration Concept](Document/configuration-concept.png) + +**Path** + +Path 为路径,用来读取或者写入配置文件提供查找的方式。例如上图中,通过 +路径 Section1.Section2.Value1 可以获取到相应存储的值。 + +路径之间使用 "." 进行分隔,路径名称取名原则上考虑序列化目标的命名约束情况, +建议使用字母开头,可以包含字母和数字的一个字符串。 + +路径名有以下约束: +- 不得为 null 或者空字符串或者仅空格的字符串 +- 大小写敏感 +- 不得含有路径分隔符 + +# Usage + +这部分介绍配置参数的两种读写方式、以及数据保存部分对象结构。 + +可以通过字典操作的方法,读写配置参数,也可以通过强类型的方法读写。 + +## 字典方式读写 + +调用者通过传入路径字符串读取或者写入值。 + +```C# +// 设置员工的年龄 +data.SetValue("Employee.Age", 30); + +// 读取员工年龄时,第二个参数为默认值,当读取失败时,返回该默认值 +int age = data.GetValue("Employee.Age", 0); // should return 30 +int value = data.GetValue("Employee.NewField", -1); // should return -1 +``` + +> **注意** +> 编译器可以通过参数类型,推断出 `data.SetValue(path, age);` 的方法泛型类型, +> 因此直接写为`data.SetValue(path, age)`即可,后续代码我将省略泛型类型; + +读写时也可以读取出一个Section,然后单独操作该Section读写方法,代码如下; + +```C# +// 初始化一个含有一个 Employee 节点的配置对象 +var root = new ConfigSection(rootSect => +{ + rootSect.SetValue("Employee", new ConfigSection(employeeSection => + { + employeeSection.SetValue("Name", "Nepton"); + employeeSection.SetValue("Sex", "M"); + employeeSection.SetValue("Age", 38); + employeeSection.SetValue("DateOfJoin", new DateTime(2019, 2, 26)); + })); +}); + +// 读取 employee 节点 +var employee = root.GetSection("Employee"); +Assert.IsNotNull(employee); + +// 通过使用 employee 读取,注意使用相对路径 +string name = employee.GetValue("Name", ""); // should return "Nepton" +int age = employee.GetValue("Age", 0); // should return 38 +string sex = employee.GetValue("Sex", ""); // should return "M" +``` + +## 强类型对象 + +### 定义 + +基于字典的读取方式灵活,但是可能出现由于路径拼写错误而无法正确读写内容的情况。可以定义配置文件对象, +通过强命名的方式读写配置信息,尽可能避免防止出现路径拼写错误的情况。使用强类型对象的步骤: +1. 创建一个配置文件类,继承于 ConfigBase +2. 重写 Path 属性,提供该对象读取配置参数的路径 +3. 定义需要的参数,例如 LineWidth,调用基类的 GetValue 和 SetValue 读写配置属性 + +下边为演示代码 +```C# +public class YourConfig : ConfigBase +{ + public YourConfig(IConfigSection configSection) : base(configSection) { } + + public override string Path => "GeometryEditor.Appearance"; + + public int LineWidth + { + get => GetValue(1); + set => SetValue(value); + } +} +``` + +### 使用 + +使用 YourConfig 对象可以通过以下几种方式进行 + +1. 使用 Ioc 注入的方式,在构造函数处注入对象 + +```C# +public class YourClass +{ + private YourConfig _config; + + public YourClass(YourConfig config) + { + _config = config; + } + + public void YourMethod() + { + // 读取信息 + var lineWidth = _config.LineWidth; + + // 保存信息 + _config.LineWidth = 5; + } +} +``` + +2. 通过创建对象的方式读写配置参数 +```C# +public class YourClass +{ + public void YourMethod() + { + // 初始化 通过对象创建的方式创建 config 对象 + var configData = _resolver.Resolve(); + var config = new YourConfig(configData); + + // 读取信息 + var lineWidth = config.LineWidth; + + // 保存信息 + config.LineWidth = 5; + } +} +``` + +3. 通过单件模式创建 +```C# +public class YourClass +{ + public void YourMethod() + { + // 如果配置信息对象通过静态属性的方式创建了单件实例,通过该方法获取配置对象 + var config = YourConfig.Instance; + + // 读取信息 + var lineWidth = config.LineWidth; + + // 保存信息 + config.LineWidth = 5; + } +} +``` + +## 实现持久化存储 + +IConfigStore 定义了配置参数持久化存储的接口,LoadAsync方法用来从持久化流读取数据;SaveAsync用 +来把配置参数数据写入到持久化存储区;而SourceChanged用来监听持久化存储区是否由外界更改了数据内容。 + +```C# +public interface IConfigStore +{ + Task LoadAsync(); + + Task SaveAsync(ConfigSection sections); + + event AsyncEventHandler SourceChanged; +} + +``` +> 目前 IConfigStore 没有针对IConfigSection,而针对实例类 ConfigSection + +目前实现了 JSON 文件格式的持久化模块,具体参见项目 Configuration.Store.Json,使用方法如下: + +```C# +var fileStream = new FileStreamProvider("config.json"); +var jsonStore = new JsonConfigStore(fileStream); + +var configSection = await jsonStore.LoadAsync(); +var employeeName = configSection.GetValue("Employee.Name"); +``` + +存储的数据按照对象的层级进行了格式化,开发人员或者部署人员可以通过第三方工具轻松的调整设置参数。 + +```JSON +{ + "Emplopyee": { + "Name": "Nepton", + "Sex" : "M" + } +} +``` + +## 在项目中配置 + +ConfigData 继承于 ConfigSection,除了用来保存配置信息以外,ConfigData 引入了 IConfigStore,用来持久化保存配置参数 +信息至磁盘、数据库等位置。 + +该对象可以以单件的方式,注册在Ioc中,并关联 IConfigSection 接口,强类型的对象注入时引用 ConfigData 对象,实现 +全局数据的静态读写。 + +```c# +// 注册配置文件模块 +var configFilePath = Path.Combine( + AppDomain.CurrentDomain.BaseDirectory, "setting.json"); + +containerRegistry.RegisterInstance( + new JsonConfigStore(new FileStreamProvider(configFilePath))); +containerRegistry.RegisterSingleton(); +``` + +手动保存配置信息,如果要批量设置参数后,一次性保存,可以设置为手动保存配置信息,那么在适当的时候,请调 +用 SaveAsync(或者相应同步方法)执行保存操作。 +```C# +public class YourClass +{ + public async Task YourMethod() + { + var configData = _resolver.Resolve(ConfigData); + await configData.SaveAsync(); // 或者 configData.Save(); 同步调用 + } +} +``` + +手动重新读取配置信息,如果外部配置信息改变,或者在手动保存模式下,想撤销内存的更改,调用 +LoadAsync(或者相应同步方法)执行读取操作,当读取完成后,内存数据将丢失。 + +```C# +public class YourClass +{ + public async Task YourMethod() + { + // 调用Save函数立即保存 + var configData = _resolver.Resolve(ConfigData); + await configData.LoadAsync(); // 或者 configData.Load(); 同步调用 + } +} +``` + +# Struction + +下图是配置参数对象关系静态图: + +![Configuration Class Dependencies Graph](Document/configuration-class-dependencies-graph.png) + +- **IConfigSection** + 接口 IConfigSection 声明了保存配置参数以及子 Section 的对象功能的函数声明,具体实现类为 ConfigSection + +- **ConfigSection** + 接口 IConfigSection 的默认实现版本 + +- **ConfigData** + 继承于 ConfigSection,在基类的基础上扩展并提供了持久化存储的功能,与接口 IConfigStore 建立了关系。 + +- **IConfigStore** + 接口 IConfigStore 声明了一个持久化存储对象如何把 IConfigSection 进行存储,以及如何读取存储数据并生成 IConfigSection 的对象。 + +- **ConfigBase** + ConfigBase 类包装了 IConfigSection,并提供一套机制让开发者实现强类型检查的配置参数对象。 + +- **ConfigPath** + 这是一个辅助类,负责处理路径解析,例如 Employee.Age 这样的配置参数查询路径的解析工作由 ConfigPath 处理 + +> Notice +> 其他类仅负责处理周边的代码和功能,不一一详细描述 diff --git a/Grinder.Infrastructure/Config/Configuration/SaveMethods.cs b/Grinder.Infrastructure/Config/Configuration/SaveMethods.cs new file mode 100644 index 0000000..d25b8e6 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/SaveMethods.cs @@ -0,0 +1,18 @@ +namespace GrinderApp.Configuration +{ + /// + /// 刷新至数据库的方法 + /// + public enum SaveMethods + { + /// + /// 当属性更新时,自动更新 + /// + PropertyChanged, + + /// + /// 手动刷新 + /// + Manual, + } +} \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/Configuration/Store/JsonConfigStore.cs b/Grinder.Infrastructure/Config/Configuration/Store/JsonConfigStore.cs new file mode 100644 index 0000000..af34308 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/Store/JsonConfigStore.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using GrinderApp.Configuration.Helper; +using GrinderApp.Configuration.StreamProvider; + +namespace GrinderApp.Configuration.Store.Json +{ + /// + /// JSON 配置文件转换流 + /// + public class JsonConfigStore : IConfigStore + { + /// + /// 配置文件读写流(这个设计到技术资源,例如磁盘, 单独抽出来为了更好的单元测试) + /// + private readonly IStreamProvider _configStream; + + public JsonConfigStore(IStreamProvider configStream) + { + if (configStream == null) + throw new ArgumentNullException(nameof(configStream)); + + _configStream = configStream; + } + + /// + /// 数据源由外部更新事件 + /// + public event AsyncEventHandler SourceChanged + { + add + { + if (_configStream is IStreamProviderWithNotification streamProviderWithNotification) + streamProviderWithNotification.StreamSourceChanged += value; + } + remove + { + if (_configStream is IStreamProviderWithNotification streamProviderWithNotification) + streamProviderWithNotification.StreamSourceChanged -= value; + } + } + + #region Implementation of IConfigStream + + /// + /// 从数据流中加载 配置文件 对象 + /// + /// + public async Task LoadAsync() + { + await using (var stream = _configStream.OpenRead()) + { + using var reader = new StreamReader(stream); + // 读取配置信息 + var settingString = await reader.ReadToEndAsync(); + if (string.IsNullOrEmpty(settingString)) + return new ConfigData(); + + return ExtraSettingFromString(settingString); + } + } + + /// + /// 加载配置文件对象 + /// + /// + public ConfigData Load() + { + using (var stream = _configStream.OpenRead()) + using (var reader = new StreamReader(stream)) + { + // 读取配置信息 + var settingString = reader.ReadToEnd(); + if (string.IsNullOrEmpty(settingString)) + return new ConfigData(); + + return ExtraSettingFromString(settingString); + } + } + + /// + /// 配置文件保存至数据流 + /// + /// 配置信息数据 + /// + public async Task SaveAsync(ConfigData section) + { + string settingString = GenerateSettingString(section); + await using (var stream = _configStream.OpenWrite()) + await using (var writer = new StreamWriter(stream)) + { + await writer.WriteAsync(settingString); + await writer.FlushAsync(); + } + } + + + /// + /// 配置文件保存至数据流 + /// + /// 配置信息数据 + /// + public void Save(ConfigData section) + { + string settingString = GenerateSettingString(section); + using (var stream = _configStream.OpenWrite()) + using (var writer = new StreamWriter(stream)) + { + writer.Write(settingString); + writer.Flush(); + } + } + + /// + /// 生成设置参数字符串 + /// + /// + /// + private static string GenerateSettingString(ConfigData data) + { + if (data == null) + throw new ArgumentNullException(nameof(data)); + + // 转换为一个Dictionary嵌套,然后写入 + + var stack = new Stack>>(); + var referenced = new HashSet(); // 保存已经读取的节点 + + var rootDict = new Dictionary(); + stack.Push(new Tuple>("", rootDict)); + + // 递归转换 + while (stack.Any()) + { + var cur = stack.Pop(); + var curPath = cur.Item1; + var curDict = cur.Item2; + + // 检查循环引用 + if (referenced.Contains(curPath)) + throw new ArgumentException("Circular reference in config section"); + referenced.Add(curPath); + + // 处理值 + var valuePaths = data.GetChildrenNodes(curPath, false); + foreach (var valuePath in valuePaths) + { + var path = ConfigPath.CombinePath(curPath, valuePath); + var cp = new ConfigPath(valuePath); + var configValue = data.OpenConfigValue(path); + + var value = configValue.Value; + + // 处理枚举 + if (value is Enum e) + value = e.ToString(); + + curDict.Add(cp.Current, value); + } + + // 处理节点 + var sectionPaths = data.GetChildrenNodes(curPath, true); + foreach (var sectionPath in sectionPaths) + { + var path = ConfigPath.CombinePath(curPath, sectionPath); + + var dictionary = new Dictionary(); + var cp = new ConfigPath(sectionPath); + curDict.Add(cp.Current, dictionary); + + stack.Push(new Tuple>(path, dictionary)); + } + } + + // 执行序列化,并写到流中 + var settingString = JsonConvert.SerializeObject(rootDict, Formatting.Indented); + return settingString; + } + + #endregion + + /// + /// 解析处理设置参数 + /// + /// + /// + private static ConfigData ExtraSettingFromString(string settingString) + { + // 执行转换 + var root = JsonConvert.DeserializeObject(settingString) as JObject; + var data = new ConfigData(); + var stack = new Stack>(new[] + { + new Tuple(root, "") + }); + var referenced = new HashSet(); + + // 递归执行JObject至ConfigSection的转换 + while (stack.Any()) + { + var cur = stack.Pop(); + var curItem = cur.Item1; + var curPath = cur.Item2; + + // 引用检查 + if (referenced.Contains(curItem)) + continue; + referenced.Add(curItem); + + // 执行本次转换 + foreach (var item in curItem) + { + var combinePath = ConfigPath.CombinePath(curPath, item.Key); + + if (item.Value == null) + continue; + + // 如果是一个 JObject,递归转换下去 + if (item.Value is JObject jObject) + { + stack.Push(new Tuple(jObject, combinePath)); + continue; + } + + // 处理值 + if (item.Value is JValue jValue) + { + var cv = data.OpenOrCreateConfigValue(combinePath); + cv.Value = jValue.Value; + continue; + } + + throw new NotSupportedException($"Object type {item.Value.GetType().Name} is not supported"); + } + } + + return data; + } + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/Store/JsonConfigStore.md b/Grinder.Infrastructure/Config/Configuration/Store/JsonConfigStore.md new file mode 100644 index 0000000..b6cc1fc --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/Store/JsonConfigStore.md @@ -0,0 +1,9 @@ +| Property | Value +|:-------------|:----------------------- +| Project Name | ConfigStream.Json +| Author | Liu Wan Li +| Create Time | 2019-04-09 + +# Summary +这个项目实现一个Json 文件格式的配置文件持久化版本。采用 Newtonsoft.Json 作为转换提供 +接口的定义在 TampingFoundation 项目的 Configs 中 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/Configuration/StreamProvider/FileStreamProvider.cs b/Grinder.Infrastructure/Config/Configuration/StreamProvider/FileStreamProvider.cs new file mode 100644 index 0000000..2862c89 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/StreamProvider/FileStreamProvider.cs @@ -0,0 +1,109 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using GrinderApp.Configuration.Helper; +using Serilog; + +namespace GrinderApp.Configuration.StreamProvider +{ + /// + /// 文件流提供者 + /// + public class FileStreamProvider : IStreamProviderWithNotification + { + private readonly string _filePath; + + /// + /// 构造函数 + /// + /// 文件名,包含文件全路径 + /// 监视文件更改 + public FileStreamProvider(string filePath, bool watchForChanged = false) + { + if (filePath == null) + throw new ArgumentNullException(nameof(filePath)); + + if (watchForChanged) + { + Task.Run(() => WatchForChanged(filePath)); + } + + _filePath = filePath; + } + + /// + /// 监视文件更改 + /// + /// 文件路径 + /// + private async Task WatchForChanged(string filePath) + { + var watcher = new FileSystemWatcher(filePath); + int errorCount = 0; + + while (true) + { + try + { + watcher.WaitForChanged(WatcherChangeTypes.All); + + await Task.Delay(1000); + StreamSourceChanged?.Invoke(this, EventArgs.Empty); + } + catch (Exception ex) + { + errorCount += 1; + if (errorCount > 100) + { + Log.Error(ex, "Configuration file watching ERR, terminal."); + return; + } + + Log.Error(ex, "Configuration file watching ERR, retry after 5 seconds."); + await Task.Delay(5000); + } + } + } + + /// + /// 流更新事件。 + /// 注意:OpenWrite 写入流不会激发该事件,该事件由程序外部激发调用 + /// + public event AsyncEventHandler StreamSourceChanged; + + #region Implementation of IStreamProvider + + /// + /// 以读取的方式打开流 + /// + /// + public Stream OpenRead() + { + if (File.Exists(_filePath)) + return File.OpenRead(_filePath); + + // 文件不存在, 返回空流 + return new MemoryStream(); + } + + /// + /// 为写入打开流 + /// + /// + public Stream OpenWrite() + { + var fullPath = Path.GetFullPath(_filePath); + var dir = Path.GetDirectoryName(fullPath); + + if (string.IsNullOrEmpty(dir)) + throw new Exception($"Invalid file path: {_filePath}"); + + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + + return File.Create(_filePath); + } + + #endregion + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/StreamProvider/IStreamProvider.cs b/Grinder.Infrastructure/Config/Configuration/StreamProvider/IStreamProvider.cs new file mode 100644 index 0000000..1bdb9e9 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/StreamProvider/IStreamProvider.cs @@ -0,0 +1,22 @@ +using System.IO; + +namespace GrinderApp.Configuration.StreamProvider +{ + /// + /// 数据流提供接口,例如内存流,文件流等 + /// + public interface IStreamProvider + { + /// + /// 以读取的方式打开流 + /// + /// + Stream OpenRead(); + + /// + /// 为写入打开流 + /// + /// + Stream OpenWrite(); + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/StreamProvider/IStreamProviderWithNotification.cs b/Grinder.Infrastructure/Config/Configuration/StreamProvider/IStreamProviderWithNotification.cs new file mode 100644 index 0000000..eb55a94 --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/StreamProvider/IStreamProviderWithNotification.cs @@ -0,0 +1,16 @@ +using GrinderApp.Configuration.Helper; + +namespace GrinderApp.Configuration.StreamProvider +{ + /// + /// 含有通知消息的接口 + /// + public interface IStreamProviderWithNotification : IStreamProvider + { + /// + /// 流更新事件。 + /// 注意:OpenWrite 写入流不会激发该事件,该事件由程序外部激发调用 + /// + event AsyncEventHandler StreamSourceChanged; + } +} diff --git a/Grinder.Infrastructure/Config/Configuration/StreamProvider/MemoryStreamProvider.cs b/Grinder.Infrastructure/Config/Configuration/StreamProvider/MemoryStreamProvider.cs new file mode 100644 index 0000000..dc820cd --- /dev/null +++ b/Grinder.Infrastructure/Config/Configuration/StreamProvider/MemoryStreamProvider.cs @@ -0,0 +1,57 @@ +using System; +using System.IO; + +namespace GrinderApp.Configuration.StreamProvider +{ + /// + /// 内存流提供者,主要用于单元测试 + /// + public class MemoryStreamProvider : IStreamProvider + { + private MemoryStream _stream = new MemoryStream(); + + public MemoryStreamProvider() + { + } + + /// Initializes a new instance of the class. + public MemoryStreamProvider(MemoryStream stream) + { + if (stream == null) + throw new ArgumentNullException(nameof(stream)); + + _stream = stream; + } + + /// + /// 内存流 + /// + public byte[] Buffer => _stream.ToArray(); + + #region Implementation of IStreamProvider + + /// + /// 以读取的方式打开流 + /// + /// + public Stream OpenRead() + { + var buffer = _stream.ToArray(); + var stream = new MemoryStream(buffer, false); + + return stream; + } + + /// + /// 为写入打开流 + /// + /// + public Stream OpenWrite() + { + _stream = new MemoryStream(); + return _stream; + } + + #endregion + } +} diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/ConfigDataTests.cs b/Grinder.Infrastructure/Config/ConfigurationTests/ConfigDataTests.cs new file mode 100644 index 0000000..bafd5dc --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/ConfigDataTests.cs @@ -0,0 +1,158 @@ +using System; +using FirstLineTamping.Configuration; +using Moq; +using NUnit.Framework; + +namespace ConfigurationTests +{ + [TestFixture()] + public class ConfigDataTests + { + /// + /// 正常读写 + /// + [Test()] + public void PathSetValue() + { + var data = new Config(); + + string path = "Employee.Age"; + int age = 30; + + // 设置员工的年龄 + data.SetValue(path, age); + + // 读取时,第二个参数为默认值,当读取失败时,返回该默认值 + int actual = data.GetValue(path, 0); + + Assert.AreEqual(age, actual); + } + + [Test] + public void TestEmployee() + { + // 初始化一个含有一个 Employee 节点的配置对象 + var root = new Config(); + var employee = root.GetSection("Employee"); + employee.SetValue("Name", "Nepton"); + employee.SetValue("Sex", "M"); + employee.SetValue("Age", 38); + employee.SetValue("DateOfJoin", new DateTime(2019, 2, 26)); + + // 读取 employee 节点 + employee = root.GetSection("Employee"); + Assert.IsNotNull(employee); + + // 通过使用 employee 读取,注意使用相对路径 + string name = employee.GetValue("Name", ""); // should return "Nepton" + int age = employee.GetValue("Age", 0); // should return "38" + string sex = employee.GetValue("Sex", ""); // should return "M" + + Assert.AreEqual("Nepton", name); + Assert.AreEqual(38, age); + Assert.AreEqual("M", sex); + } + + + /// + /// 正常读写 + /// + [Test()] + public void PathSetValueThenGetFromBranch() + { + var data = new Config(); + const int expected = 100; + + // 分两段设置 + data.SetValue("A.B.C.D.E", expected); + + // 另一种方式分两段读取 + var a = data.GetSection("A"); + var bc = a.GetSection("B.C"); + var de = bc.GetValue("D.E"); + + Assert.AreEqual(expected, de); + } + + /// + /// 正常读写 + /// + [Test()] + public void PathSetValueThenGetFromBranch2() + { + var data = new Config(); + const int expected = 100; + + // 分两段设置 + data.SetValue("A.B.C.D.E", expected); + + // 另一种方式分两段读取 + var actual = data.GetSection("A.B.C").GetValue("D.E", 0); + + Assert.AreEqual(expected, actual); + } + + + /// + /// 测试无效路径 + /// + [Test] + public void PassIncorrectPath_Throw() + { + var data = new Config(); + + var invalidPaths = new[] + { + "A. .B", + "", + ".", + "A..B", + "A.", + ".B", + "..B", + null, + }; + + foreach (var path in invalidPaths) + { + Assert.That(() => data.SetValue(path, 100), Throws.Exception.AssignableTo()); + Assert.That(() => data.GetValue(path, 100), Throws.Exception.AssignableTo()); + } + } + + /// + /// 测试属性更改的时候正确调用保存 + /// + [Test] + public void SetValueThenSerializeToStore() + { + var mock = new Mock(); + var data = new Config(mock.Object) + { + SaveMethod = SaveMethods.PropertyChanged + }; + + data.SetValue("A", 100); + mock.Verify(c => c.SaveAsync(It.IsNotNull()), Times.Once); + } + + /// + /// 把一个Section读出来,单独修改,ConfigData内数据同步变化 + /// + [Test] + public void ValidateSectionReference() + { + var configData = new Config(); + configData.SetValue("Name.LastName", "Liu"); + + var nameSection = configData.GetSection("Name"); + + nameSection.SetValue("LastName", "Fu"); + Assert.AreEqual(nameSection.GetValue("LastName"), configData.GetValue("Name.LastName")); + + var name2Section = configData.GetSection("Name"); + name2Section.SetValue("LastName", "Hang"); + Assert.AreEqual(nameSection.GetValue("LastName"), name2Section.GetValue("LastName")); + } + } +} diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/ConfigurationTests.csproj b/Grinder.Infrastructure/Config/ConfigurationTests/ConfigurationTests.csproj new file mode 100644 index 0000000..17ad922 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/ConfigurationTests.csproj @@ -0,0 +1,173 @@ + + + + + + Debug + AnyCPU + {8BB020AD-7BF9-49E8-A7CF-BEA438FF7760} + Library + Properties + ConfigurationTests + ConfigurationTests + v4.6.2 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + 7.0 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + 7.0 + + + true + bin\x86\Debug\ + DEBUG;TRACE + full + x86 + 6 + prompt + MinimumRecommendedRules.ruleset + + + bin\x86\Release\ + TRACE + true + pdbonly + x86 + prompt + MinimumRecommendedRules.ruleset + + + + ..\packages\Castle.Core.4.3.1\lib\net45\Castle.Core.dll + + + ..\packages\CommonServiceLocator.2.0.4\lib\net46\CommonServiceLocator.dll + + + ..\packages\Moq.4.10.1\lib\net45\Moq.dll + + + ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll + True + + + + + ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.2\lib\netstandard1.0\System.Runtime.CompilerServices.Unsafe.dll + + + ..\packages\System.Threading.Tasks.Extensions.4.5.3\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll + + + ..\packages\Unity.5.11.1\lib\net46\Unity.Abstractions.dll + + + ..\packages\Unity.5.11.1\lib\net46\Unity.Container.dll + + + + + + + + + + + + + + + + + + + + + {5D22F933-5BF8-4DB1-BE3C-CAC8765DABCE} + Configuration.Store.Json + + + {E70AADB2-1940-454B-8BFF-1A674F7D39F0} + Configuration + + + {971A0CC3-AFAF-4FA8-AE17-446534A349B1} + TampingDbService + + + {B3F4ED07-9A02-4926-B827-3AFA00330E58} + TampingInfrastructure + + + {F9A128D3-2CCB-4918-9B96-3DA8DDAF3AB5} + TampingContract + + + + + + + + + + + False + + + False + + + False + + + False + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/ConfigurationTests.csproj.DotSettings b/Grinder.Infrastructure/Config/ConfigurationTests/ConfigurationTests.csproj.DotSettings new file mode 100644 index 0000000..73e9656 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/ConfigurationTests.csproj.DotSettings @@ -0,0 +1,2 @@ + + CSharp60 \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/OneWaiterTaskQueueTests.cs b/Grinder.Infrastructure/Config/ConfigurationTests/OneWaiterTaskQueueTests.cs new file mode 100644 index 0000000..f3050a1 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/OneWaiterTaskQueueTests.cs @@ -0,0 +1,62 @@ +using System; +using System.Threading.Tasks; +using FirstLineTamping.Configuration; +using FirstLineTamping.Configuration.Helper; +using NUnit.Framework; + +namespace ConfigurationTests +{ + [TestFixture()] + public class OneWaiterTaskQueueTests + { + /// + /// 测试任务队列正确性 + /// + [Test()] + public void TryEnqueueTest() + { + var queue = new OneWaiterTaskQueue(); + + // 第一个任务入队 + var result = queue.TryEnqueue(LongTimeTask); + Assert.IsTrue(result); + + // 第二个任务入队 + result = queue.TryEnqueue(LongTimeTask); + Assert.IsTrue(result); + Assert.IsTrue(queue.HasWaiter); + + // 第三个任务入队 + result = queue.TryEnqueue(LongTimeTask); + Assert.IsFalse(result); + } + + public async Task LongTimeTask() + { + // 在单元测试函数完成前,不要退出 + await Task.Delay(10000000); + } + + /// + /// 测试任务队列遇到异常情况 + /// + [Test()] + public void TryEnqueueHasException() + { + var queue = new OneWaiterTaskQueue(); + + var ex = new NotSupportedException("Not Supported"); + + var result = queue.TryEnqueue(()=>ExceptionTask(ex)); + Assert.IsTrue(result); + Assert.IsTrue(queue.HasFaulted); + Assert.AreEqual(ex, queue.LastException); + } + + public Task ExceptionTask(NotSupportedException ex) + { + throw ex; + } + + } +} diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/PermissionTest.cs b/Grinder.Infrastructure/Config/ConfigurationTests/PermissionTest.cs new file mode 100644 index 0000000..bba32d5 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/PermissionTest.cs @@ -0,0 +1,97 @@ +using CommonServiceLocator; +using FirstLineTamping.Configuration; +using Moq; +using FirstLineTamping.Contract.Authorities; +using NUnit.Framework; +using TampingDbService.Entities; +using TampingDbService.Entities.Users; +using Unity; + +namespace ConfigurationTests +{ + [TestFixture] + public class PermissionTest + { + /// + /// 含有权限的读写, 允许操作 + /// + [Test] + public void AuthorityPermitTest() + { + var admin = new User("admin", "", new UserAuthority(Authority.All)); + + var loginUserSessionMock = new Mock(); + loginUserSessionMock.Setup(c => c.GetLoginUser()).Returns(admin); + + // register service locator + var serviceLocatorMock = new Mock(); + serviceLocatorMock.Setup(c => c.GetInstance()).Returns(loginUserSessionMock.Object); + ServiceLocator.SetLocatorProvider(() => serviceLocatorMock.Object); + + var cs = new Config(); + var path = "Employee.Name"; + cs.SetAuthority(path, new ObjectAuthority(Authority.Anonymous), new ObjectAuthority(Authority.Anonymous)); + + var nepton = "Nepton"; + cs.SetValue(path, nepton); + var actual = cs.GetValue(path, ""); + + Assert.AreEqual(nepton, actual); + } + + + /// + /// 含有权限的读写,拒绝,并抛异常 + /// + [Test] + public void AuthorityDenyTest() + { + var baby = new User("admin", "", new UserAuthority(Authority.None)); + + var loginUserSessionMock = new Mock(); + loginUserSessionMock.Setup(c => c.GetLoginUser()).Returns(baby); + + // register service locator + var serviceLocatorMock = new Mock(); + serviceLocatorMock.Setup(c => c.GetInstance()).Returns(loginUserSessionMock.Object); + ServiceLocator.SetLocatorProvider(() => serviceLocatorMock.Object); + + var cs = new Config(); + var path = "Employee.Name"; + cs.SetAuthority(path, new ObjectAuthority(Authority.Anonymous), new ObjectAuthority(Authority.Anonymous)); + + var nepton = "Nepton"; + Assert.Throws(() => cs.SetValue(path, nepton)); + Assert.Throws(() => cs.GetValue(path, "")); + } + + + /// + /// 含有权限的读写,拒绝,并抛异常 + /// + [Test] + public void AuthorityReadOnlyTest() + { + var user = new User("test", "", new UserAuthority(Authority.Anonymous)); + + var loginUserSessionMock = new Mock(); + loginUserSessionMock.Setup(c => c.GetLoginUser()).Returns(user); + + // register service locator + var serviceLocatorMock = new Mock(); + serviceLocatorMock.Setup(c => c.GetInstance()).Returns(loginUserSessionMock.Object); + ServiceLocator.SetLocatorProvider(() => serviceLocatorMock.Object); + + var cs = new Config(); + var path = "Employee.Name"; + var nepton = "Nepton"; + cs.SetValue(path, nepton); + + cs.SetAuthority(path, new ObjectAuthority(Authority.Anonymous), new ObjectAuthority(Authority.Engineer)); + Assert.Throws(() => cs.SetValue(path, "Test123")); + + var actual = cs.GetValue(path, ""); + Assert.AreEqual(nepton, actual); + } + } +} diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/Properties/AssemblyInfo.cs b/Grinder.Infrastructure/Config/ConfigurationTests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9d44b81 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ConfigurationTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ConfigurationTests")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8bb020ad-7bf9-49e8-a7cf-bea438ff7760")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/StrongNameConfigTest.cs b/Grinder.Infrastructure/Config/ConfigurationTests/StrongNameConfigTest.cs new file mode 100644 index 0000000..20574fa --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/StrongNameConfigTest.cs @@ -0,0 +1,35 @@ +using CommonServiceLocator; +using FirstLineTamping.Contract.Authorities; +using Moq; +using NUnit.Framework; +using NUnit.Framework.Internal; +using TampingDbService.Entities; +using TampingDbService.Entities.Users; + +namespace ConfigurationTests +{ + [TestFixture] + public class StrongNameConfigTest + { + /// + /// 强命名配置测试 + /// + [Test] + public void StrongNameAuthorityConfigTest() + { + var loginUserSessionMock = new Mock(); + loginUserSessionMock.Setup(c => c.GetLoginUser()).Returns(new User("Operate", "", new UserAuthority(Authority.Operator, Authority.None))); + + // register service locator + var serviceLocatorMock = new Mock(); + serviceLocatorMock.Setup(c => c.GetInstance()).Returns(loginUserSessionMock.Object); + ServiceLocator.SetLocatorProvider(() => serviceLocatorMock.Object); + + var config = new StrongNameConfigTestSample(); + + var age = 38; + Assert.Throws(() => config.Age = age); + Assert.AreEqual(0, config.Age); + } + } +} diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/StrongNameConfigTestSample.cs b/Grinder.Infrastructure/Config/ConfigurationTests/StrongNameConfigTestSample.cs new file mode 100644 index 0000000..8ecdedc --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/StrongNameConfigTestSample.cs @@ -0,0 +1,38 @@ +using FirstLineTamping.Configuration; +using FirstLineTamping.Contract.Authorities; + +namespace ConfigurationTests +{ + public class StrongNameConfigTestSample : ConfigBase + { + /// + /// 构造函数, + /// + public StrongNameConfigTestSample() : base(new Config()) + { + } + + /// + /// 定义作用域(包) + /// 在多个配置文件合并的时候,用来区分不同的作用域 + /// + public override string PathName => "Employee"; + + /// + /// 年龄 + /// + [ConfigAuthorize(Authority.Anonymous | Authority.Operator, + Authority.Engineer | Authority.Remote)] + public int Age + { + get + { + return GetPropertyValue(0); + } + set + { + SetPropertyValue(value); + } + } + } +} diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/app.config b/Grinder.Infrastructure/Config/ConfigurationTests/app.config new file mode 100644 index 0000000..58b89cf --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/app.config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Grinder.Infrastructure/Config/ConfigurationTests/packages.config b/Grinder.Infrastructure/Config/ConfigurationTests/packages.config new file mode 100644 index 0000000..6fb0702 --- /dev/null +++ b/Grinder.Infrastructure/Config/ConfigurationTests/packages.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/GrinderApp.Core/GrinderApp.Core.csproj b/GrinderApp.Core/GrinderApp.Core.csproj new file mode 100644 index 0000000..2a4d336 --- /dev/null +++ b/GrinderApp.Core/GrinderApp.Core.csproj @@ -0,0 +1,15 @@ + + + net8.0-windows7.0 + true + + + + + + + + + + + \ No newline at end of file diff --git a/GrinderApp.Core/Interface/IConfigurationEditorViewLoader.cs b/GrinderApp.Core/Interface/IConfigurationEditorViewLoader.cs new file mode 100644 index 0000000..5526c3a --- /dev/null +++ b/GrinderApp.Core/Interface/IConfigurationEditorViewLoader.cs @@ -0,0 +1,13 @@ +using GrinderApp.Configuration; + +namespace GrinderApp.Core.Interface +{ + /// + /// 配置文件编辑器 + /// + public interface IConfigurationEditorViewLoader + { + void Show(string regionName, params ConfigSectionView[] configSectionView); + public void Show(string regionName); + } +} diff --git a/GrinderApp.Core/Interface/IModuleViewLoader.cs b/GrinderApp.Core/Interface/IModuleViewLoader.cs new file mode 100644 index 0000000..51a9677 --- /dev/null +++ b/GrinderApp.Core/Interface/IModuleViewLoader.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrinderApp.Core.Interface +{ + /// + /// 模块视图 + /// + public interface IModuleViewLoader : IViewLoader + { + /// + /// 显示视图 + /// + /// + // void Show(string regionName); + + /// + /// 名称 + /// + string Name { get; } + + /// + /// 图标 todo 没有完全想好支持的格式, 目前支持 MaterialDesign 图标的枚举 + /// + string Icon { get; } + + /// + /// 默认显示顺序 + /// + int DefaultIndex { get; } + } +} diff --git a/GrinderApp.Core/Interface/IViewLoader.cs b/GrinderApp.Core/Interface/IViewLoader.cs new file mode 100644 index 0000000..c8bd1c6 --- /dev/null +++ b/GrinderApp.Core/Interface/IViewLoader.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrinderApp.Core.Interface +{ + /// + /// 视图加载接口 + /// + public interface IViewLoader + { + /// + /// 显示视图 + /// + /// + void Show(string regionName); + } +} diff --git a/GrinderApp.Core/Mvvm/EventSubscriptionHolder.cs b/GrinderApp.Core/Mvvm/EventSubscriptionHolder.cs new file mode 100644 index 0000000..a675a01 --- /dev/null +++ b/GrinderApp.Core/Mvvm/EventSubscriptionHolder.cs @@ -0,0 +1,41 @@ +using Prism.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrinderApp.Core.Mvvm +{ + /// + /// 订阅事件的 Holder + /// + public class EventSubscriptionHolder + { + /// Initializes a new instance of the class. + public EventSubscriptionHolder(EventBase @event, SubscriptionToken token, params object[] keeps) + { + Event = @event; + Token = token; + Keeps = keeps; + } + + /// + /// 事件对象 + /// + public EventBase Event { get; } + + /// + /// 卸载用 Token + /// + public SubscriptionToken Token { get; } + + /// + /// 需要引用持有的对象 + /// + public object[] Keeps { get; } + + public override string ToString() => $"Hold << {Event.GetType().Name} >>"; + } + +} diff --git a/GrinderApp.Core/Mvvm/ObservesPropertyAttribute.cs b/GrinderApp.Core/Mvvm/ObservesPropertyAttribute.cs new file mode 100644 index 0000000..0def3ce --- /dev/null +++ b/GrinderApp.Core/Mvvm/ObservesPropertyAttribute.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrinderApp.Core.Mvvm +{ + + /// + /// 激发该属性 Changed 的观察目标,如果目标Changed ,声明该特性的属性同时激发 Changed + /// 通常, 我们使用 PropertyChanged.Fody 实现变化跟踪, 但是如果要跟踪的属性在基类中的话, PropertyChanged.Fody 就无能为力了. + /// 因此, 我们使用 ObservesProperty 特性头, 作为补充, 实现功能 + /// + /// How to use + /// 在属性头上声明 ObservesProperty 要跟踪的属性即可 + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] + public class ObservesPropertyAttribute : Attribute + { + public string PropertyName + { + get; + } + + /// + /// + /// + /// 观察目标,如果目标更改,那么该属性也以激发更改 + public ObservesPropertyAttribute(string propertyName) + { + PropertyName = propertyName; + } + } + +} diff --git a/GrinderApp.Core/Mvvm/RegionViewModelBase.cs b/GrinderApp.Core/Mvvm/RegionViewModelBase.cs new file mode 100644 index 0000000..0a8bb2a --- /dev/null +++ b/GrinderApp.Core/Mvvm/RegionViewModelBase.cs @@ -0,0 +1,36 @@ +using System; +using Prism.Navigation.Regions; + + +namespace GrinderApp.Core.Mvvm +{ + public class RegionViewModelBase : ViewModelBase, INavigationAware, IConfirmNavigationRequest + { + protected IRegionManager RegionManager { get; private set; } + + public RegionViewModelBase(IRegionManager regionManager) + { + RegionManager = regionManager; + } + + public virtual void ConfirmNavigationRequest(NavigationContext navigationContext, Action continuationCallback) + { + continuationCallback(true); + } + + public virtual bool IsNavigationTarget(NavigationContext navigationContext) + { + return true; + } + + public virtual void OnNavigatedFrom(NavigationContext navigationContext) + { + + } + + public virtual void OnNavigatedTo(NavigationContext navigationContext) + { + + } + } +} diff --git a/GrinderApp.Core/Mvvm/ViewModelBase.cs b/GrinderApp.Core/Mvvm/ViewModelBase.cs new file mode 100644 index 0000000..6428486 --- /dev/null +++ b/GrinderApp.Core/Mvvm/ViewModelBase.cs @@ -0,0 +1,392 @@ +using Prism.Events; +using Prism.Mvvm; +using Prism.Navigation; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Windows; + +namespace GrinderApp.Core.Mvvm +{ + + /// + /// ViewModel 的基类 + /// + /// + /// IDestructible 接口不靠谱, 不要使用, 不当使用会导致内存泄露 + /// + public abstract class ViewModelBase : BindableBase + { + /// + /// 视图模型对象Id + /// + public string InstanceId { get; } + + protected ViewModelBase() + { + InstanceId = $"{Guid.NewGuid().ToString().ToUpper()[..8]}-{GetType().Name}"; + + // 初始化 ObservesProperty + InitializeObservesPropertyChanged(); + } + + protected override void OnPropertyChanged(PropertyChangedEventArgs args) + { + // 激发 ObservesProperty + UpdateObservesPropertyChanged(args); + + base.OnPropertyChanged(args); + } + + #region Event subscribe + + /// + /// 保存 Token + /// + private readonly ConcurrentDictionary> _eventTokens = + new(); + + /// + /// 保持订阅的 Token + /// + /// + public SubscriptionToken AddSubscribedToken(EventSubscriptionHolder holder) + { + var period = IsLoaded == true ? TrackingPeriod.OnUnloaded : TrackingPeriod.OnDestroy; + return AddSubscribedToken(holder, period); + } + + /// + /// 保持事件的订阅状态 + /// + /// + /// + public SubscriptionToken AddSubscribedToken(EventSubscriptionHolder holder, TrackingPeriod period) + { + if (holder == null) + throw new ArgumentNullException(nameof(holder)); + + var list = _eventTokens.GetOrAdd(period, new ConcurrentBag()); + list.Add(holder); + + return holder.Token; + } + + /// + /// 取消指定周期的 Token 订阅 + /// + public void UnsubscribeTokens(TrackingPeriod period) + { + if (!_eventTokens.TryRemove(period, out var list)) + return; + + while (list.TryTake(out var handler)) + { + handler.Token.Dispose(); + } + } + + + /// + /// 取消所有的 Token 订阅 + /// + public void ReleaseAllSubscribedTokens() + { + var keys = _eventTokens.Keys.ToArray(); + foreach (var key in keys) + { + UnsubscribeTokens(key); + } + } + + #endregion + + #region Cached property + + /// + /// 保存缓存的属性值 + /// + private readonly ConcurrentDictionary _propertyCached = new(); + + + /// + /// 获取缓存的属性值, 这个版本性能更好 + /// 用法: + /// public ICommand TestCommand => GetPropertyCached(new DelegateCommand(.....)); + /// + /// + /// + /// + /// + protected T GetPropertyCached(Func cache, [CallerMemberName] string propertyName = null) + { + if (string.IsNullOrEmpty(propertyName)) + throw new ArgumentException("PropertyName cannot be null or empty.", nameof(propertyName)); + + return (T)_propertyCached.GetOrAdd(propertyName, _ => cache()); + } + + #endregion + + #region Loaded or Unloaded + + /// + /// 绑定至视图 + /// + /// + public void BindingView(FrameworkElement view) + { + _view = view; + + if (view.IsLoaded) + { + OnLoaded(); + } + + view.Loaded -= FrameworkElement_Loaded; + view.Loaded += FrameworkElement_Loaded; + + view.Unloaded -= FrameworkElement_Unloaded; + view.Unloaded += FrameworkElement_Unloaded; + } + + /// + /// 解除与视图的绑定 + /// + /// + public void UnbindingView(FrameworkElement view) + { + view.Loaded -= FrameworkElement_Loaded; + view.Unloaded -= FrameworkElement_Unloaded; + _view = null; + } + + /// + /// 是否被加载 + /// + public bool? IsLoaded + { + get + { + if (_view is not { } view) + return false; + + if (view.Dispatcher.CheckAccess()) + return view.IsLoaded; + + bool isLoaded = false; + view.Dispatcher.Invoke(() => isLoaded = view.IsLoaded); + + return isLoaded; + } + } + + /// + /// 绑定的视图 + /// + private FrameworkElement _view; + + private void FrameworkElement_Loaded(object sender, RoutedEventArgs e) + { + OnLoaded(); + } + + private void FrameworkElement_Unloaded(object sender, RoutedEventArgs e) + { + try + { + OnUnloaded(); + } + finally + { + // 释放 Token, 如果用户重写了 OnLoaded 但是没有 base.OnLoaded(), 这里是用来兜底 + UnsubscribeTokens(TrackingPeriod.OnUnloaded); + } + } + + /// + /// 加载时执行, 确保 BindingLoadedUnloadedAction 被 View 构造函数调用 + /// + public virtual void OnLoaded() + { + } + + /// + /// 卸载时执行, 确保 BindingLoadedUnloadedAction 被 View 构造函数调用 + /// + public virtual void OnUnloaded() + { + } + + #endregion + + #region ObservesProperty + + /// + /// 保存更改观察字典表 + /// + private Dictionary _observesDict; + + /// + /// 初始化属性变更激发观察者 + /// + private void InitializeObservesPropertyChanged() + { + _observesDict = CreateObservesPropertyDictionaryOrOpenCache(GetType()); + } + + /// + /// 属性更改观察字典 Cache + /// + private static readonly Dictionary> _observesDictCache = + new Dictionary>(); + + /// + /// 创建或者从缓存打开属性观察字典, 为了提高效率,一次创建完成的字典,将被多个对象复用 + /// + /// + /// + private static Dictionary CreateObservesPropertyDictionaryOrOpenCache(Type type) + { + // 返回已有字典 + if (_observesDictCache.TryGetValue(type, out var result)) + return result; + + // 新建字典缓存并返回 + var dict = CreateObservesPropertyDictionary(type); + _observesDictCache.Add(type, dict); + + return dict; + } + + /// + /// 创建指定类型的属性更改观察字典 + /// + /// + /// + private static Dictionary CreateObservesPropertyDictionary(Type type) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + var observesList = new List>(); + + var props = type.GetProperties(); + foreach (var prop in props) + { + // 获取属性上的所有如下声明 + // [ObservesProperty(nameof(FirstName)] + // [ObservesProperty(nameof(LastName)] + var attrs = prop.GetCustomAttributes(typeof(ObservesPropertyAttribute), true) + .OfType().ToArray(); + + // 去重 + var propertyNames = attrs.Select(c => c.PropertyName).Distinct(); + + // 转换为 FirstName => Name LastName => Name 的结构 + var list = from name in propertyNames + select new KeyValuePair(name, prop.Name); + + // 存储观察映射结构 + observesList.AddRange(list); + } + + // 存储观察字典 + var dict = (from item in observesList + group item by item.Key + into g + select g).ToDictionary(c => c.Key, c => c.Select(v => v.Value).ToArray()); + + // 检查循环依赖 + CheckCircleDependency(dict); + + return dict; + } + + /// + /// 递归检查循环引用的声明情况,防止程序后续崩溃. + /// + /// + private static void CheckCircleDependency(Dictionary dependencyDict) + { + // 存储当前属性和,当前属性的路径 + // 例如: (Name, [LastName, Name]) + Stack<(string, string[])> stack = new Stack<(string, string[])>(); + + // 初始化递归栈变量 + foreach (var item in dependencyDict) + { + stack.Push((item.Key, new string[0])); + } + + // 递归处理 + while (stack.Any()) + { + // 判断依据:对于当前属性,如果他的某个子节点已经存在于路径 parentPathOfDependency 中,则判断循环依赖 + // 例如路径 A.B.C.D, 验证目标 X 的子节点数组 [A,Y,Z] 中,A在他的parentPathOfDependency已经存在,则判断循环依赖 + + // 属性名,当前属性的依赖路径 + var (propertyName, parentPathOfDependency) = stack.Pop(); + + // 尝试获取检查属性节点的子节点 + if (!dependencyDict.TryGetValue(propertyName, out var children)) + continue; + + // 递归检查每一个子节点 + foreach (var child in children) + { + // 检查 child 节点是否存在依赖 + if (parentPathOfDependency.Contains(child)) + throw new ArgumentException($"{string.Join("-", parentPathOfDependency)}-{child}"); + + // 递归向下检查 + stack.Push((child, parentPathOfDependency.Union(new[] { child }).ToArray())); + } + } + } + + private void UpdateObservesPropertyChanged(PropertyChangedEventArgs args) + { + // 如果该属性订阅了事件通知的依赖属性 + if (_observesDict.TryGetValue(args.PropertyName, out var dependencyPropertyNames)) + { + foreach (var propertyName in dependencyPropertyNames) + { + RaisePropertyChanged(propertyName); + } + } + } + + #endregion + } + /// + /// 事件跟踪的时间段 + /// + public enum TrackingPeriod + { + OnUnloaded, + OnDestroy, + } + //public abstract class _ViewModelBase : BindableBase, IDestructible + //{ + // protected _ViewModelBase() + // { + // InstanceId = $"{Guid.NewGuid().ToString().ToUpper()[..8]}-{GetType().Name}"; + + // // 初始化 ObservesProperty + // // InitializeObservesPropertyChanged(); + // } + // /// + // /// 视图模型对象Id + // /// + // public string InstanceId { get; } + + // public virtual void Destroy() + // { + + // } + //} +} diff --git a/GrinderApp.Core/RegionNames.cs b/GrinderApp.Core/RegionNames.cs new file mode 100644 index 0000000..1942273 --- /dev/null +++ b/GrinderApp.Core/RegionNames.cs @@ -0,0 +1,7 @@ +namespace GrinderApp.Core +{ + public static class RegionNames + { + public const string ContentRegion = "ContentRegion"; + } +} diff --git a/GrinderApp/GrinderApp.sln b/GrinderApp/GrinderApp.sln new file mode 100644 index 0000000..520bd13 --- /dev/null +++ b/GrinderApp/GrinderApp.sln @@ -0,0 +1,73 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35514.174 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{560C8FDA-B322-4831-9947-F3597BB2FA8E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{3CF79AE9-2100-4B03-BCB0-9BD67784E06A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{3F73F93D-0B10-4FC2-BCF6-29D31151F254}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrinderApp", "GrinderApp\GrinderApp.csproj", "{4C5EE941-6B15-4CDC-91E6-B37C80D49B5A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrinderApp.Core", "..\GrinderApp.Core\GrinderApp.Core.csproj", "{57942328-DB8D-4E81-AF0F-3776A3B9DDFB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrinderApp.Services", "..\Services\GrinderApp.Services\GrinderApp.Services.csproj", "{EBF839AE-EB10-4807-979D-6983669B15E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrinderApp.Services.Interfaces", "..\Services\GrinderApp.Services.Interfaces\GrinderApp.Services.Interfaces.csproj", "{27F6296C-6144-4508-B973-F239057B023E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GrinderApp.Modules.ModuleName", "..\Modules\GrinderApp.Modules.ModuleName\GrinderApp.Modules.ModuleName.csproj", "{0644D152-A161-4687-924B-86AF1FC32F1D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Grinder.Infrastructure", "Grinder.Infrastructure", "{306C4966-F4C7-4F82-9FE3-6D04168B1421}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Configuration", "..\Grinder.Infrastructure\Config\Configuration\Configuration.csproj", "{E0A59C7B-E122-47FA-AB12-079F3A2BA43E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigurationEditor", "Modules\ConfigurationEditor\ConfigurationEditor.csproj", "{BB76CB5E-81E5-4371-B661-1E1F6A96AD67}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4C5EE941-6B15-4CDC-91E6-B37C80D49B5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C5EE941-6B15-4CDC-91E6-B37C80D49B5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C5EE941-6B15-4CDC-91E6-B37C80D49B5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C5EE941-6B15-4CDC-91E6-B37C80D49B5A}.Release|Any CPU.Build.0 = Release|Any CPU + {57942328-DB8D-4E81-AF0F-3776A3B9DDFB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {57942328-DB8D-4E81-AF0F-3776A3B9DDFB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {57942328-DB8D-4E81-AF0F-3776A3B9DDFB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {57942328-DB8D-4E81-AF0F-3776A3B9DDFB}.Release|Any CPU.Build.0 = Release|Any CPU + {EBF839AE-EB10-4807-979D-6983669B15E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EBF839AE-EB10-4807-979D-6983669B15E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EBF839AE-EB10-4807-979D-6983669B15E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EBF839AE-EB10-4807-979D-6983669B15E4}.Release|Any CPU.Build.0 = Release|Any CPU + {27F6296C-6144-4508-B973-F239057B023E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27F6296C-6144-4508-B973-F239057B023E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27F6296C-6144-4508-B973-F239057B023E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27F6296C-6144-4508-B973-F239057B023E}.Release|Any CPU.Build.0 = Release|Any CPU + {0644D152-A161-4687-924B-86AF1FC32F1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0644D152-A161-4687-924B-86AF1FC32F1D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0644D152-A161-4687-924B-86AF1FC32F1D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0644D152-A161-4687-924B-86AF1FC32F1D}.Release|Any CPU.Build.0 = Release|Any CPU + {E0A59C7B-E122-47FA-AB12-079F3A2BA43E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0A59C7B-E122-47FA-AB12-079F3A2BA43E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0A59C7B-E122-47FA-AB12-079F3A2BA43E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0A59C7B-E122-47FA-AB12-079F3A2BA43E}.Release|Any CPU.Build.0 = Release|Any CPU + {BB76CB5E-81E5-4371-B661-1E1F6A96AD67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BB76CB5E-81E5-4371-B661-1E1F6A96AD67}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BB76CB5E-81E5-4371-B661-1E1F6A96AD67}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BB76CB5E-81E5-4371-B661-1E1F6A96AD67}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {EBF839AE-EB10-4807-979D-6983669B15E4} = {3F73F93D-0B10-4FC2-BCF6-29D31151F254} + {27F6296C-6144-4508-B973-F239057B023E} = {3F73F93D-0B10-4FC2-BCF6-29D31151F254} + {0644D152-A161-4687-924B-86AF1FC32F1D} = {3CF79AE9-2100-4B03-BCB0-9BD67784E06A} + {E0A59C7B-E122-47FA-AB12-079F3A2BA43E} = {306C4966-F4C7-4F82-9FE3-6D04168B1421} + {BB76CB5E-81E5-4371-B661-1E1F6A96AD67} = {3CF79AE9-2100-4B03-BCB0-9BD67784E06A} + EndGlobalSection +EndGlobal diff --git a/GrinderApp/GrinderApp/App.xaml b/GrinderApp/GrinderApp/App.xaml new file mode 100644 index 0000000..1db21a2 --- /dev/null +++ b/GrinderApp/GrinderApp/App.xaml @@ -0,0 +1,13 @@ + + + + + + + + diff --git a/GrinderApp/GrinderApp/App.xaml.cs b/GrinderApp/GrinderApp/App.xaml.cs new file mode 100644 index 0000000..31ac6c1 --- /dev/null +++ b/GrinderApp/GrinderApp/App.xaml.cs @@ -0,0 +1,47 @@ +using System.Windows; +using ConfigurationEditor.DependencyInjection; +using GrinderApp.Configuration; +using GrinderApp.Modules.ModuleName; +using GrinderApp.Services; +using GrinderApp.Services.Interfaces; +using GrinderApp.Views; + +using Prism.Ioc; +using Prism.Modularity; + +namespace GrinderApp +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App + { + protected override Window CreateShell() + { + return Container.Resolve(); + } + + protected override void RegisterTypes(IContainerRegistry containerRegistry) + { + containerRegistry.RegisterInstance(ConfigFactory.Config); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton (); + containerRegistry.RegisterForNavigation(); + } + + protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog) + { + moduleCatalog.AddModule(); + moduleCatalog.AddModule(); + } + //protected override async void OnStartup(StartupEventArgs e) + //{ + + + //} + protected override void ConfigureViewModelLocator() + { + base.ConfigureViewModelLocator(); + } + } +} diff --git a/GrinderApp/GrinderApp/AppConfig.cs b/GrinderApp/GrinderApp/AppConfig.cs new file mode 100644 index 0000000..7cf1bcc --- /dev/null +++ b/GrinderApp/GrinderApp/AppConfig.cs @@ -0,0 +1,55 @@ +using GrinderApp.Configuration; +using GrinderApp.Services.Interfaces; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GrinderApp +{ + public class AppConfig : ConfigBase, IAppConfig + { + public AppConfig(Config config) : base(config) + { + + } + #region Overrides of ConfigBase + + public TValue GetValueOrThrow(string path) + { + return Config.GetValueOrThrow(path); + } + + /// + /// 定义作用域(包) + /// 在多个配置文件合并的时候,用来区分不同的作用域 + /// + + public override string PathName => "GrinderApp"; + + #endregion + + /// + /// full screen mode. + /// we cache this value because invoke high frequency by WndProc. + /// + [Description("全屏显示模式 ")] + public bool FullScreenMode + { + get => GetPropertyValue(false); + set => SetPropertyValue(value); + } + /// + /// full screen mode. + /// we cache this value because invoke high frequency by WndProc. + /// + [Description("plc ip ")] + public string PLcIpAddress + { + get => GetPropertyValue("192.168.0.5"); + set => SetPropertyValue(value); + } + } +} diff --git a/GrinderApp/GrinderApp/GrinderApp.csproj b/GrinderApp/GrinderApp/GrinderApp.csproj new file mode 100644 index 0000000..e3a8ca0 --- /dev/null +++ b/GrinderApp/GrinderApp/GrinderApp.csproj @@ -0,0 +1,22 @@ + + + WinExe + net8.0-windows7.0 + true + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GrinderApp/GrinderApp/ViewModels/HomeMenuViewModel.cs b/GrinderApp/GrinderApp/ViewModels/HomeMenuViewModel.cs new file mode 100644 index 0000000..76c91b1 --- /dev/null +++ b/GrinderApp/GrinderApp/ViewModels/HomeMenuViewModel.cs @@ -0,0 +1,163 @@ +using GrinderApp.Configuration; +using GrinderApp.Core; +using GrinderApp.Core.Interface; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Navigation.Regions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Input; + +namespace GrinderApp.ViewModels +{ + public class HomeMenuViewModel : BindableBase, INavigationAware + { + Config config; + public HomeMenuViewModel(IEnumerable moduleViewLoaders, + Config config + ) + { + this.config = config; + foreach (var moduleViewLoader in moduleViewLoaders) + { + //if (moduleViewLoader is ISettingModuleLoader) + //{ + // settingModuleLoader = moduleViewLoader as ISettingModuleLoader; + //} + //if (moduleViewLoader is IWorkingModuleLoader) + //{ + // workingModuleLoader = moduleViewLoader as IWorkingModuleLoader; + //} + //if (moduleViewLoader is IJogsModuleLoader) + //{ + // jogsModuleLoader = moduleViewLoader as IJogsModuleLoader; + //} + if (moduleViewLoader is IConfigurationEditorViewLoader) + { + configurationEditorViewLoader = moduleViewLoader as IConfigurationEditorViewLoader; + } + //if (moduleViewLoader is IDevelopmentModuleLoader) + //{ + // developmentModuleLoader = moduleViewLoader as IDevelopmentModuleLoader; + //} + //if (moduleViewLoader is IBackDoorModuleLoader) + //{ + // backDoorModuleLoader = moduleViewLoader as IBackDoorModuleLoader; + //} + //if (moduleViewLoader is IScriptEditorModuleLoader) + //{ + // scriptEditorModuleLoader = moduleViewLoader as IScriptEditorModuleLoader; + //} + + //if (moduleViewLoader is ILogViewLoader) + // { + // logViewLoader = moduleViewLoader as ILogViewLoader; + // } + } + + + } + private bool _isRunning=false ; + /// + /// 设备是否在运行 + /// + public bool IsRunning + { + get => _isRunning; + set => SetProperty(ref _isRunning, value); + } + + public IConfigurationEditorViewLoader configurationEditorViewLoader { get; } + + private DelegateCommand _launchCommand; + public DelegateCommand LaunchCommand => + _launchCommand ?? (_launchCommand = new DelegateCommand(ExecuteLaunchCommand, CanExecuteLaunchCommand)) + .ObservesProperty(() => IsRunning); + void ExecuteLaunchCommand(IViewLoader viewLoader) + { + try + { + viewLoader?.Show(RegionNames.ContentRegion); + } + catch (Exception e) + { + + throw; + } + + } + + bool CanExecuteLaunchCommand(IViewLoader viewLoader) + { + if (viewLoader == null) + return false; + + // 允许任何时候运行的模块 + //if (viewLoader is IAlarmMessageViewLoader) + // return true; + /* + // 作业模块 + if (viewLoader is IWorkingModuleLoader) + { + //note: 为使PLC 通讯故障时还能进入作业,进行手动解算和测试取消进入作业限制 + //return !IsMeasureRunning && WorkingState != RunningStateTable.WorkingStates.Failure; + return true; + } + + // Setting + if (viewLoader is ISettingModuleLoader) + { + // return !IsTampingJobRunning && !IsRecorderRunning && WorkingState != RunningStateTable.WorkingStates.Failure; + return true; + } + if (viewLoader is ISettingModuleLoader) + { + // return !IsTampingJobRunning && !IsRecorderRunning && WorkingState != RunningStateTable.WorkingStates.Failure; + return true; + } + //手动 + if (viewLoader is IConfigurationEditorViewLoader) + { + // return !IsTampingJobRunning && !IsRecorderRunning && WorkingState != RunningStateTable.WorkingStates.Failure; + return true; + } + + // 开发测试 + if (viewLoader is IDevelopmentModuleLoader) + { + // return !IsTampingJobRunning && !IsRecorderRunning && WorkingState != RunningStateTable.WorkingStates.Failure; + return true; + } + + //// 记录仪模块 + //if (viewLoader is IRecorderViewLoader) + //{ + // // return !IsMeasureRunning && RecorderState != RunningStateTable.RecorderStates.Failure; + // return true; + //} + + // 其他普通模块 + // return !IsTampingJobRunning && !IsMeasureRunning && !IsRecorderRunning; + */ + return true; + } + + public void OnNavigatedTo(NavigationContext navigationContext) + { + IsRunning = true; + } + + public bool IsNavigationTarget(NavigationContext navigationContext) + { + // throw new NotImplementedException(); + return true ; + } + + public void OnNavigatedFrom(NavigationContext navigationContext) + { + // + } + } + +} diff --git a/GrinderApp/GrinderApp/ViewModels/MainWindowViewModel.cs b/GrinderApp/GrinderApp/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..2912af8 --- /dev/null +++ b/GrinderApp/GrinderApp/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,69 @@ +using ControlzEx.Theming; +using GrinderApp.Core; +using GrinderApp.Core.Mvvm; +using GrinderApp.Views; +using MaterialDesignThemes.Wpf; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Navigation.Regions; +using System.Windows; +using System.Windows.Input; +using System.Xml.Linq; + +namespace GrinderApp.ViewModels +{ + public class MainWindowViewModel : ViewModelBase + { + IRegionManager regionManager; + private string _title = "Prism Application"; + public string Title + { + get { return _title; } + set { SetProperty(ref _title, value); } + } + private bool _IsLightTheme = false; + public bool IsLightTheme + { + get => _IsLightTheme; + set + { + if (SetProperty(ref _IsLightTheme, value)) + { + ModifyTheme(value); + } + } + } + private static void ModifyTheme(bool isDarkTheme) + { + var paletteHelper = new PaletteHelper(); + var theme = paletteHelper.GetTheme(); + theme.SetBaseTheme(isDarkTheme ? BaseTheme.Dark : BaseTheme.Light); + paletteHelper.SetTheme(theme); + } + + // public override void on + public MainWindowViewModel(IRegionManager regionManager) + { + this.regionManager = regionManager; + // ChangeAccentCommand = new ICommand(o => true, this.DoChangeTheme); + } + + public ICommand HomeCommand => new DelegateCommand(() => + { + regionManager.RequestNavigate(RegionNames.ContentRegion, nameof(HomeMenu)); + }); + + public ICommand ChangeAccentCommand => new DelegateCommand((o) => + { + DoChangeTheme(o); + }); + + protected virtual void DoChangeTheme(string? name) + { + if (name is not null) + { + ThemeManager.Current.ChangeThemeColorScheme(Application.Current, name); + } + } + } +} diff --git a/GrinderApp/GrinderApp/Views/HomeMenu.xaml b/GrinderApp/GrinderApp/Views/HomeMenu.xaml new file mode 100644 index 0000000..7bac2ce --- /dev/null +++ b/GrinderApp/GrinderApp/Views/HomeMenu.xaml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +