Repository: neuecc/MasterMemory Branch: master Commit: 863383d77681 Files: 142 Total size: 448.9 KB Directory structure: gitextract_t6q9u1cd/ ├── .editorconfig ├── .github/ │ ├── dependabot.yaml │ └── workflows/ │ ├── build-debug.yaml │ ├── build-release.yaml │ ├── prevent-github-change.yaml │ ├── stale.yaml │ └── toc.yaml ├── .gitignore ├── Directory.Build.props ├── LICENSE ├── MasterMemory.sln ├── README.md ├── sandbox/ │ ├── Benchmark/ │ │ ├── Benchmark.csproj │ │ ├── Program.cs │ │ └── Utils/ │ │ └── Helper.cs │ ├── ConsoleApp/ │ │ ├── ConsoleApp.csproj │ │ └── Program.cs │ ├── GeneratorSandbox/ │ │ ├── GeneratorSandbox.csproj │ │ └── Program.cs │ └── PerfTest2/ │ ├── Engines/ │ │ ├── Dictionary_Test.cs │ │ ├── ITest.cs │ │ ├── LiteDB_Test.cs │ │ ├── MasterMemory_Test.cs │ │ ├── RavenDB_Test.cs │ │ └── SQLite_Test.cs │ ├── Generated/ │ │ ├── DatabaseBuilder.cs │ │ ├── ImmutableBuilder.cs │ │ ├── MasterMemoryResolver.cs │ │ ├── MemoryDatabase.cs │ │ └── Tables/ │ │ └── TestDocTable.cs │ ├── PerfTest2.csproj │ ├── Program.cs │ └── Utils/ │ └── Helper.cs ├── src/ │ ├── MasterMemory/ │ │ ├── DatabaseBuilderBase.cs │ │ ├── DatabaseBuilderBaseExtensions.cs │ │ ├── IValidatable.cs │ │ ├── ImmutableBuilderBase.cs │ │ ├── Internal/ │ │ │ ├── BinarySearch.cs │ │ │ ├── ByteBufferWriter.cs │ │ │ ├── ExpandableArray.cs │ │ │ ├── HeaderFormatterResolver.cs │ │ │ └── InternStringResolver.cs │ │ ├── MasterMemory.csproj │ │ ├── MemoryDatabaseBase.cs │ │ ├── Meta/ │ │ │ └── Meta.cs │ │ ├── RangeView.cs │ │ ├── TableBase.cs │ │ ├── Validation/ │ │ │ ├── ExpressionDumper.cs │ │ │ ├── ExpressionParameterNameModifier.cs │ │ │ ├── ITableUniqueValidate.cs │ │ │ ├── ReferenceSet.cs │ │ │ ├── ValidatableSet.Sequential.cs │ │ │ ├── ValidatableSet.Sequential.tt │ │ │ ├── ValidatableSet.cs │ │ │ ├── ValidateResult.cs │ │ │ ├── ValidationDatabase.cs │ │ │ └── Validator.cs │ │ ├── _InternalVisibleTo.cs │ │ └── _MessagePackResolver.cs │ ├── MasterMemory.Annotations/ │ │ ├── Attributes.cs │ │ └── MasterMemory.Annotations.csproj │ ├── MasterMemory.SourceGenerator/ │ │ ├── DiagnosticDescriptors.cs │ │ ├── GeneratorCore/ │ │ │ ├── CodeGenerator.cs │ │ │ ├── DatabaseBuilderTemplate.cs │ │ │ ├── DatabaseBuilderTemplate.tt │ │ │ ├── GenerationContext.cs │ │ │ ├── ImmutableBuilderTemplate.cs │ │ │ ├── ImmutableBuilderTemplate.tt │ │ │ ├── MemoryDatabaseTemplate.cs │ │ │ ├── MemoryDatabaseTemplate.tt │ │ │ ├── MessagePackResolverTemplate.cs │ │ │ ├── MessagePackResolverTemplate.tt │ │ │ ├── TableTemplate.cs │ │ │ ├── TableTemplate.tt │ │ │ └── Template.cs │ │ ├── MasterMemory.SourceGenerator.csproj │ │ ├── MasterMemoryGenerator.cs │ │ ├── MasterMemoryGeneratorOptions.cs │ │ ├── Polyfill/ │ │ │ └── System.CodeDom.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ └── Utility/ │ │ ├── EquatableArray.cs │ │ └── IgnoreEquality.cs │ └── MasterMemory.Unity/ │ ├── Assets/ │ │ ├── NuGet.config │ │ ├── NuGet.config.meta │ │ ├── Packages.meta │ │ ├── Scenes/ │ │ │ ├── Main.unity │ │ │ └── Main.unity.meta │ │ ├── Scenes.meta │ │ ├── Scripts/ │ │ │ ├── NewBehaviourScript.cs │ │ │ └── NewBehaviourScript.cs.meta │ │ ├── Scripts.meta │ │ ├── packages.config │ │ └── packages.config.meta │ ├── Packages/ │ │ ├── manifest.json │ │ └── packages-lock.json │ └── ProjectSettings/ │ ├── AudioManager.asset │ ├── ClusterInputManager.asset │ ├── DynamicsManager.asset │ ├── EditorBuildSettings.asset │ ├── EditorSettings.asset │ ├── GraphicsSettings.asset │ ├── InputManager.asset │ ├── MemorySettings.asset │ ├── NavMeshAreas.asset │ ├── NetworkManager.asset │ ├── PackageManagerSettings.asset │ ├── Physics2DSettings.asset │ ├── PresetManager.asset │ ├── ProjectSettings.asset │ ├── ProjectVersion.txt │ ├── QualitySettings.asset │ ├── SceneTemplateSettings.json │ ├── TagManager.asset │ ├── TimeManager.asset │ ├── UnityConnectSettings.asset │ ├── VFXManager.asset │ ├── VersionControlSettings.asset │ └── XRSettings.asset └── tests/ ├── MasterMemory.SourceGenerator.Tests/ │ ├── AssemblyAtrributeTest.cs │ ├── DiagnosticsTest.cs │ ├── GenerateTest.cs │ ├── IncrementalGeneratorTest.cs │ ├── MasterMemory.SourceGenerator.Tests.csproj │ ├── TestBase.cs │ └── Utility/ │ ├── CSharpGeneratorRunner.cs │ └── CodeGeneratorHelper.cs └── MasterMemory.Tests/ ├── BinarySearchTest.cs ├── DatabaseTest.cs ├── IssueTest.cs ├── MasterMemory.Tests.csproj ├── MemoryKeyTest.cs ├── MemoryTest.cs ├── MessagePackResolver.cs ├── MetaTest.cs ├── RangeViewTest.cs ├── TestStructures/ │ ├── PersonModel.cs │ ├── QuestMaster.cs │ ├── Sample.cs │ ├── SkillMaster.cs │ ├── TestMaster.cs │ └── UserLevel.cs └── ValidatorTest.cs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # top-most EditorConfig file root = true [*] charset = utf-8 end_of_line = lf indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true # Visual Studio Spell checker configs (https://learn.microsoft.com/en-us/visualstudio/ide/text-spell-checker?view=vs-2022#how-to-customize-the-spell-checker) spelling_exclusion_path = ./exclusion.dic [*.cs] indent_size = 4 charset = utf-8-bom end_of_line = unset # Solution files [*.{sln,slnx}] end_of_line = unset # MSBuild project files [*.{csproj,props,targets}] end_of_line = unset # Xml config files [*.{ruleset,config,nuspec,resx,runsettings,DotSettings}] end_of_line = unset [*{_AssemblyInfo.cs,.notsupported.cs}] generated_code = true # C# code style settings [*.{cs}] dotnet_diagnostic.IDE0044.severity = none # IDE0044: Make field readonly # https://stackoverflow.com/questions/79195382/how-to-disable-fading-unused-methods-in-visual-studio-2022-17-12-0 dotnet_diagnostic.IDE0051.severity = none # IDE0051: Remove unused private member dotnet_diagnostic.IDE0130.severity = none # IDE0130: Namespace does not match folder structure ================================================ FILE: .github/dependabot.yaml ================================================ # ref: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot version: 2 updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" # Check for updates to GitHub Actions every week groups: dependencies: patterns: - "*" cooldown: default-days: 14 # Wait 14 days before creating another PR for the same dependency. This will prevent vulnerability on the package impact. ignore: # I just want update action when major/minor version is updated. patch updates are too noisy. - dependency-name: "*" update-types: - version-update:semver-patch ================================================ FILE: .github/workflows/build-debug.yaml ================================================ name: Build-Debug on: push: branches: - "master" pull_request: branches: - "master" jobs: build-dotnet: permissions: contents: read runs-on: ubuntu-24.04 timeout-minutes: 15 steps: - uses: Cysharp/Actions/.github/actions/checkout@main - uses: Cysharp/Actions/.github/actions/setup-dotnet@main with: dotnet-version: | 9.0.x - run: dotnet build -c Release - run: dotnet test -c Release --no-build - run: dotnet pack -c Release --no-build -p:IncludeSymbols=true -p:SymbolPackageFormat=snupkg -o $GITHUB_WORKSPACE/artifacts ================================================ FILE: .github/workflows/build-release.yaml ================================================ name: Build-Release on: workflow_dispatch: inputs: tag: description: "tag: git tag you want create. (sample 1.0.0)" required: true dry-run: description: "dry-run: true will never create relase/nuget." required: true default: false type: boolean jobs: build-dotnet: permissions: contents: read runs-on: ubuntu-24.04 timeout-minutes: 10 steps: - uses: Cysharp/Actions/.github/actions/checkout@main - uses: Cysharp/Actions/.github/actions/setup-dotnet@main # pack nuget - run: dotnet build -c Release -p:Version=${{ inputs.tag }} - run: dotnet test -c Release --no-build - run: dotnet pack -c Release --no-build -p:Version=${{ inputs.tag }} -o ./publish - uses: Cysharp/Actions/.github/actions/upload-artifact@main with: name: nuget path: ./publish retention-days: 1 # release create-release: needs: [build-dotnet] permissions: contents: write id-token: write # required for NuGet Trusted Publish uses: Cysharp/Actions/.github/workflows/create-release.yaml@main with: commit-id: ${{ github.sha }} dry-run: ${{ inputs.dry-run }} tag: ${{ inputs.tag }} nuget-push: true secrets: inherit ================================================ FILE: .github/workflows/prevent-github-change.yaml ================================================ name: Prevent github change on: pull_request: paths: - ".github/**/*.yaml" - ".github/**/*.yml" jobs: detect: permissions: contents: read uses: Cysharp/Actions/.github/workflows/prevent-github-change.yaml@main ================================================ FILE: .github/workflows/stale.yaml ================================================ name: "Close stale issues" on: workflow_dispatch: schedule: - cron: "0 0 * * *" jobs: stale: permissions: contents: read pull-requests: write issues: write uses: Cysharp/Actions/.github/workflows/stale-issue.yaml@main ================================================ FILE: .github/workflows/toc.yaml ================================================ name: TOC Generator on: push: paths: - 'README.md' jobs: toc: permissions: contents: write uses: Cysharp/Actions/.github/workflows/toc-generator.yaml@main with: TOC_TITLE: "## Table of Contents" secrets: inherit ================================================ FILE: .gitignore ================================================ # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) [Bb]in/ [Oo]bj/ # mstest test results TestResults ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.sln.docstates # Build results [Dd]ebug/ [Rr]elease/ x64/ *_i.c *_p.c *.ilk *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.log *.vspscc *.vssscc .builds # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf # Visual Studio profiler *.psess *.vsp *.vspx # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper* # NCrunch *.ncrunch* .*crunch*.local.xml # Installshield output folder [Ee]xpress # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish # Publish Web Output *.Publish.xml # NuGet Packages Directory *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore # packages # upm pacakge will use Packages # **/[Pp]ackages/* # except build/, which is used as an MSBuild target. # !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Windows Azure Build Output csx *.build.csdef # Windows Store app package directory AppPackages/ # Others [Bb]in [Oo]bj sql TestResults [Tt]est[Rr]esult* *.Cache ClientBin [Ss]tyle[Cc]op.* ~$* *.dbmdl Generated_Code #added for RIA/Silverlight projects # Backup & report files from converting an old project file to a newer # Visual Studio version. Backup files are not needed, because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML .vs/config/applicationhost.config .vs/restore.dg .vs .vsconfig # Unity src/MasterMemory.Unity/bin/* src/MasterMemory.Unity/Library/* src/MasterMemory.Unity/obj/* src/MasterMemory.Unity/Temp/* src/MasterMemory.Unity/[Uu]ser[Ss]ettings/ src/MasterMemory.Unity/*.sln src/MasterMemory.Unity/*.csproj src/MasterMemory.Unity/*.unitypackage !src/MasterMemory.Unity/Packages/ ================================================ FILE: Directory.Build.props ================================================  13 false $(Version) Cysharp Cysharp © Cysharp, Inc. database, embedded, inmemory, unity https://github.com/Cysharp/MasterMemory README.md $(PackageProjectUrl) git MIT Icon.png ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2019 Yoshifumi Kawai / Cysharp, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: MasterMemory.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.12.35527.113 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{60662102-4523-441E-8D6C-D87A3246C648}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{BDAFF1CB-8E53-412B-B389-42A15343C7A3}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "sandbox", "sandbox", "{FFAA235C-D30F-4958-BC4E-60CD08979464}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MasterMemory", "src\MasterMemory\MasterMemory.csproj", "{D2720BBB-C233-4A1E-9768-1F00C9602180}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MasterMemory.Tests", "tests\MasterMemory.Tests\MasterMemory.Tests.csproj", "{8C5EBACA-C6C7-463B-B85C-C6A05E5DEB9F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MasterMemory.Annotations", "src\MasterMemory.Annotations\MasterMemory.Annotations.csproj", "{A13F40DD-7777-4E97-9FC4-6324722CA964}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmark", "sandbox\Benchmark\Benchmark.csproj", "{205509EA-78C8-4ED0-B2B5-8030DDFB0BF0}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConsoleApp", "sandbox\ConsoleApp\ConsoleApp.csproj", "{2657C9C5-0BEA-4616-BE41-A19E8298C591}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PerfTest2", "sandbox\PerfTest2\PerfTest2.csproj", "{AA5B5485-C42E-449C-843A-98A99A0D10B2}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MasterMemory.SourceGenerator", "src\MasterMemory.SourceGenerator\MasterMemory.SourceGenerator.csproj", "{73F0ABAF-E55F-4E63-923B-4ABDE794D490}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GeneratorSandbox", "sandbox\GeneratorSandbox\GeneratorSandbox.csproj", "{D1D2B635-99CC-4C90-BAAB-57D188B5BE42}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MasterMemory.SourceGenerator.Tests", "tests\MasterMemory.SourceGenerator.Tests\MasterMemory.SourceGenerator.Tests.csproj", "{96F54302-35CD-4CDC-AAAC-8A6858DCAFEB}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {D2720BBB-C233-4A1E-9768-1F00C9602180}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D2720BBB-C233-4A1E-9768-1F00C9602180}.Debug|Any CPU.Build.0 = Debug|Any CPU {D2720BBB-C233-4A1E-9768-1F00C9602180}.Release|Any CPU.ActiveCfg = Release|Any CPU {D2720BBB-C233-4A1E-9768-1F00C9602180}.Release|Any CPU.Build.0 = Release|Any CPU {8C5EBACA-C6C7-463B-B85C-C6A05E5DEB9F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8C5EBACA-C6C7-463B-B85C-C6A05E5DEB9F}.Debug|Any CPU.Build.0 = Debug|Any CPU {8C5EBACA-C6C7-463B-B85C-C6A05E5DEB9F}.Release|Any CPU.ActiveCfg = Release|Any CPU {8C5EBACA-C6C7-463B-B85C-C6A05E5DEB9F}.Release|Any CPU.Build.0 = Release|Any CPU {A13F40DD-7777-4E97-9FC4-6324722CA964}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A13F40DD-7777-4E97-9FC4-6324722CA964}.Debug|Any CPU.Build.0 = Debug|Any CPU {A13F40DD-7777-4E97-9FC4-6324722CA964}.Release|Any CPU.ActiveCfg = Release|Any CPU {A13F40DD-7777-4E97-9FC4-6324722CA964}.Release|Any CPU.Build.0 = Release|Any CPU {205509EA-78C8-4ED0-B2B5-8030DDFB0BF0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {205509EA-78C8-4ED0-B2B5-8030DDFB0BF0}.Debug|Any CPU.Build.0 = Debug|Any CPU {205509EA-78C8-4ED0-B2B5-8030DDFB0BF0}.Release|Any CPU.ActiveCfg = Release|Any CPU {205509EA-78C8-4ED0-B2B5-8030DDFB0BF0}.Release|Any CPU.Build.0 = Release|Any CPU {2657C9C5-0BEA-4616-BE41-A19E8298C591}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2657C9C5-0BEA-4616-BE41-A19E8298C591}.Debug|Any CPU.Build.0 = Debug|Any CPU {2657C9C5-0BEA-4616-BE41-A19E8298C591}.Release|Any CPU.ActiveCfg = Release|Any CPU {2657C9C5-0BEA-4616-BE41-A19E8298C591}.Release|Any CPU.Build.0 = Release|Any CPU {AA5B5485-C42E-449C-843A-98A99A0D10B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA5B5485-C42E-449C-843A-98A99A0D10B2}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA5B5485-C42E-449C-843A-98A99A0D10B2}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA5B5485-C42E-449C-843A-98A99A0D10B2}.Release|Any CPU.Build.0 = Release|Any CPU {73F0ABAF-E55F-4E63-923B-4ABDE794D490}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {73F0ABAF-E55F-4E63-923B-4ABDE794D490}.Debug|Any CPU.Build.0 = Debug|Any CPU {73F0ABAF-E55F-4E63-923B-4ABDE794D490}.Release|Any CPU.ActiveCfg = Release|Any CPU {73F0ABAF-E55F-4E63-923B-4ABDE794D490}.Release|Any CPU.Build.0 = Release|Any CPU {D1D2B635-99CC-4C90-BAAB-57D188B5BE42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D1D2B635-99CC-4C90-BAAB-57D188B5BE42}.Debug|Any CPU.Build.0 = Debug|Any CPU {D1D2B635-99CC-4C90-BAAB-57D188B5BE42}.Release|Any CPU.ActiveCfg = Release|Any CPU {D1D2B635-99CC-4C90-BAAB-57D188B5BE42}.Release|Any CPU.Build.0 = Release|Any CPU {96F54302-35CD-4CDC-AAAC-8A6858DCAFEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {96F54302-35CD-4CDC-AAAC-8A6858DCAFEB}.Debug|Any CPU.Build.0 = Debug|Any CPU {96F54302-35CD-4CDC-AAAC-8A6858DCAFEB}.Release|Any CPU.ActiveCfg = Release|Any CPU {96F54302-35CD-4CDC-AAAC-8A6858DCAFEB}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {D2720BBB-C233-4A1E-9768-1F00C9602180} = {60662102-4523-441E-8D6C-D87A3246C648} {8C5EBACA-C6C7-463B-B85C-C6A05E5DEB9F} = {BDAFF1CB-8E53-412B-B389-42A15343C7A3} {A13F40DD-7777-4E97-9FC4-6324722CA964} = {60662102-4523-441E-8D6C-D87A3246C648} {205509EA-78C8-4ED0-B2B5-8030DDFB0BF0} = {FFAA235C-D30F-4958-BC4E-60CD08979464} {2657C9C5-0BEA-4616-BE41-A19E8298C591} = {FFAA235C-D30F-4958-BC4E-60CD08979464} {AA5B5485-C42E-449C-843A-98A99A0D10B2} = {FFAA235C-D30F-4958-BC4E-60CD08979464} {73F0ABAF-E55F-4E63-923B-4ABDE794D490} = {60662102-4523-441E-8D6C-D87A3246C648} {D1D2B635-99CC-4C90-BAAB-57D188B5BE42} = {FFAA235C-D30F-4958-BC4E-60CD08979464} {96F54302-35CD-4CDC-AAAC-8A6858DCAFEB} = {BDAFF1CB-8E53-412B-B389-42A15343C7A3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {0121A3C4-6AE0-4622-BE04-05D9A3E729AC} EndGlobalSection EndGlobal ================================================ FILE: README.md ================================================ [![GitHub Actions](https://github.com/Cysharp/MasterMemory/workflows/Build-Debug/badge.svg)](https://github.com/Cysharp/MasterMemory/actions) [![Releases](https://img.shields.io/github/release/Cysharp/MasterMemory.svg)](https://github.com/Cysharp/MasterMemory/releases) MasterMemory === Source Generator based Embedded Typed Readonly In-Memory Document Database for .NET and Unity. ![image](https://user-images.githubusercontent.com/46207/61031896-61890800-a3fb-11e9-86b7-84c821d347a4.png) **4700** times faster than SQLite and achieves zero allocation per query. Also the DB size is small. When SQLite is 3560kb then MasterMemory is only 222kb. Source Generator automatically generates a typed database structure from schemas (classes), which ensures that all queries are type-safe with full autocompletion support. ![image](https://github.com/user-attachments/assets/e804fa52-f6a5-4972-a510-0b3b17a31230) ![image](https://user-images.githubusercontent.com/46207/61035808-cb58e000-a402-11e9-9209-d51665d1cd56.png) This ensures both optimal performance and excellent usability. ## Table of Contents - [Concept](#concept) - [Getting Started(.NET)](#getting-startednet) - [Getting Started(Unity)](#getting-startedunity) - [DataTable configuration](#datatable-configuration) - [MemoryDatabase/RangeView](#memorydatabaserangeview) - [Extend Table](#extend-table) - [ImmutableBuilder](#immutablebuilder) - [Validator](#validator) - [Metadata](#metadata) - [Inheritance](#inheritance) - [Optimization](#optimization) - [MasterMemoryGeneratorOptions](#mastermemorygeneratoroptions) - [v2 -> v3 migration](#v2---v3-migration) - [License](#license) Concept --- * **Memory Efficient**, Only use underlying data memory and do aggressively string interning. * **Performance**, Similar as dictionary lookup. * **TypeSafe**, 100% Type safe by Source Generator. * **Fast load speed**, MasterMemory save data by [MessagePack for C#, a fastest C# serializer](https://github.com/neuecc/MessagePack-CSharp) so load speed is blazing fast. * **Flexible Search**, Supports multiple key, multiple result, range/closest query. * **Validator**, You can define custom data validation by C#. * **Metadata**, To make custom importer/exporter, get the all database metadata. These features are suitable for master data management(write-once, read-heavy) on embedded application, data analysis, game, etc. MasterMemory has better performance than any other database solutions. [PalDB](https://github.com/linkedin/PalDB) developed by LinkedIn has a similar concept(embeddable write-once key-value store), but the implementation and performance characteristics are completely different. Getting Started(.NET) --- Install the [MasterMemory](https://www.nuget.org/packages/MasterMemory) library(Runtime, Source Generator(Analyzer) via NuGet. ``` dotnet add package MasterMemory ``` Prepare the example table definition like following. ```csharp public enum Gender { Male, Female, Unknown } // table definition marked by MemoryTableAttribute. // database-table must be serializable by MessagePack-CSsharp [MemoryTable("person"), MessagePackObject(true)] public record Person { // index definition by attributes. [PrimaryKey] public required int PersonId { get; init; } // secondary index can add multiple(discriminated by index-number). [SecondaryKey(0), NonUnique] [SecondaryKey(1, keyOrder: 1), NonUnique] public required int Age { get; init; } [SecondaryKey(2), NonUnique] [SecondaryKey(1, keyOrder: 0), NonUnique] public required Gender Gender { get; init; } public required string Name { get; init; } } ``` Data in MasterMemory is readonly, so it is recommended to use an immutable structure. While both records and classes are supported, records might be preferable as they generate more readable ToString methods. MasterMemory's Source Generator detects types marked with the `MemoryTable` attribute and automatically generates types like the following: ![image](https://github.com/user-attachments/assets/e804fa52-f6a5-4972-a510-0b3b17a31230) Finally, you can regsiter and query by these files. ```csharp using ...; // Your project default namespace // to create database, use DatabaseBuilder and Append method. var builder = new DatabaseBuilder(); builder.Append(new Person[] { new (){ PersonId = 0, Age = 13, Gender = Gender.Male, Name = "Dana Terry" }, new (){ PersonId = 1, Age = 17, Gender = Gender.Male, Name = "Kirk Obrien" }, new (){ PersonId = 2, Age = 31, Gender = Gender.Male, Name = "Wm Banks" }, new (){ PersonId = 3, Age = 44, Gender = Gender.Male, Name = "Karl Benson" }, new (){ PersonId = 4, Age = 23, Gender = Gender.Male, Name = "Jared Holland" }, new (){ PersonId = 5, Age = 27, Gender = Gender.Female, Name = "Jeanne Phelps" }, new (){ PersonId = 6, Age = 25, Gender = Gender.Female, Name = "Willie Rose" }, new (){ PersonId = 7, Age = 11, Gender = Gender.Female, Name = "Shari Gutierrez" }, new (){ PersonId = 8, Age = 63, Gender = Gender.Female, Name = "Lori Wilson" }, new (){ PersonId = 9, Age = 34, Gender = Gender.Female, Name = "Lena Ramsey" }, }); // build database binary(you can also use `WriteToStream` for save to file). byte[] data = builder.Build(); // ----------------------- // for query phase, create MemoryDatabase. // (MemoryDatabase is recommended to store in singleton container(static field/DI)). var db = new MemoryDatabase(data); // .PersonTable.FindByPersonId is fully typed by code-generation. Person person = db.PersonTable.FindByPersonId(5); // Multiple key is also typed(***And * **), Return value is multiple if key is marked with `NonUnique`. RangeView result = db.PersonTable.FindByGenderAndAge((Gender.Female, 23)); // Get nearest value(choose lower(default) or higher). RangeView age1 = db.PersonTable.FindClosestByAge(31); // Get range(min-max inclusive). RangeView age2 = db.PersonTable.FindRangeByAge(20, 29); ``` All table(marked by `MemoryTableAttribute`) and methods(created by `PrimaryKeyAttribute` or `SecondaryKeyAttribute`) are typed. ![image](https://user-images.githubusercontent.com/46207/61035808-cb58e000-a402-11e9-9209-d51665d1cd56.png) You can invoke all indexed query by IntelliSense. Getting Started(Unity) --- The minimum supported Unity version will be `2022.3.12f1`, as it is necessary to support C# Incremental Source Generator(Compiler Version, 4.3.0). Since this library is provided via NuGet, install [NuGetForUnity](https://github.com/GlitchEnzo/NuGetForUnity), then navigate to Open Window from NuGet -> Manage NuGet Packages, Search "MasterMemory" and Press Install. First, it is recommended to define assembly attributes in any cs file to enable the use of `init`. ```csharp // Optional: Unity can't load default namespace to Source Generator // If not specified, 'MasterMemory' will be used by default, // but you can use this attribute if you want to specify a different namespace. [assembly: MasterMemoryGeneratorOptions(Namespace = "MyProj")] // Optional: If you want to use init keyword, copy-and-paste this. namespace System.Runtime.CompilerServices { internal sealed class IsExternalInit { } } ``` Everything else is the same as the standard .NET version. While the `required` keyword can't be used since it's from C# 11, using `init` alone is sufficient to guarantee immutability. ```csharp public enum Gender { Male, Female, Unknown } // table definition marked by MemoryTableAttribute. // database-table must be serializable by MessagePack-CSsharp [MemoryTable("person"), MessagePackObject(true)] public record Person { // index definition by attributes. [PrimaryKey] public int PersonId { get; init; } // secondary index can add multiple(discriminated by index-number). [SecondaryKey(0), NonUnique] [SecondaryKey(1, keyOrder: 1), NonUnique] public int Age { get; init; } [SecondaryKey(2), NonUnique] [SecondaryKey(1, keyOrder: 0), NonUnique] public Gender Gender { get; init; } public string Name { get; init; } } ``` Also, for use with IL2CPP, you need to add the generated `MasterMemoryResolver` to MessagePack's Resolver. If you need other generated Resolvers, such as those from [MagicOnion](https://github.com/Cysharp/MagicOnion), please add and compose them here. ```csharp public static class Initializer { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] public static void SetupMessagePackResolver() { // Create CompositeResolver StaticCompositeResolver.Instance.Register(new[]{ MasterMemoryResolver.Instance, // set MasterMemory generated resolver StandardResolver.Instance // set default MessagePack resolver }); // Create options with resolver var options = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance); // Optional: as default. MessagePackSerializer.DefaultOptions = options; } } ``` DataTable configuration --- Element type of datatable must be marked by `[MemoryTable(tableName)]`, datatable is generated from marked type. `string tableName` is saved in database binary, you can rename class name if tableName is same. `[PrimaryKey(keyOrder = 0)]`, `[SecondaryKey(indexNo, keyOrder)]`, `[NonUnique]` can add to public property, `[PrimaryKey]` must use in MemoryTable, `[SecondaryKey]` is option. Both `PrimaryKey` and `SecondaryKey` can add to multiple properties, it will be generated `***And***And***...`. `keyOrder` is order of column names, default is zero(sequential in which they appear). ```csharp [MemoryTable("sample"), MessagePackObject(true)] public class Sample { [PrimaryKey] public int Foo { get; set; } [PrimaryKey] public int Bar { get; set; } } db.Sample.FindByFooAndBar((int Foo, int Bar)) // ---- [MemoryTable("sample"), MessagePackObject(true)] public class Sample { [PrimaryKey(keyOrder: 1)] public int Foo { get; set; } [PrimaryKey(keyOrder: 0)] public int Bar { get; set; } } db.Sample.FindByBarAndFoo((int Bar, int Foo)) ``` Default of `FindBy***` return type is single(if not found, returns `null`). It means key is unique by default. If mark `[NonUnique]` in same AttributeList, return type is `RangeView`(if not found, return empty). ```csharp [MemoryTable("sample"), MessagePackObject(true)] public class Sample { [PrimaryKey, NonUnique] public int Foo { get; set; } [PrimaryKey, NonUnique] public int Bar { get; set; } } RangeView q = db.Sample.FindByFooAndBar((int Foo, int Bar)) ``` ```csharp [MemoryTable("sample"), MessagePackObject(true)] public class Sample { [PrimaryKey] [SecondaryKey(0)] public int Foo { get; set; } [SecondaryKey(0)] [SecondaryKey(1)] public int Bar { get; set; } } db.Sample.FindByFoo(int Foo) db.Sample.FindByFooAndBar((int Foo, int Bar)) db.Sample.FindByBar(int Bar) ``` `[StringComparisonOption]` allow to configure how compare if key is string. Default is `Ordinal`. ```csharp [MemoryTable("sample"), MessagePackObject(true)] public class Sample { [PrimaryKey] [StringComparisonOption(StringComparison.InvariantCultureIgnoreCase)] public string Foo { get; set; } } ``` If computation property exists, add `[IgnoreMember]` of MessagePack should mark. ```csharp [MemoryTable("person"), MessagePackObject(true)] public class Person { [PrimaryKey] public int Id { get;} public string FirstName { get; } public string LastName { get; } [IgnoreMember] public string FullName => FirstName + LastName; } ``` MemoryDatabase/RangeView --- In default, `MemoryDatabase` do all string data automatically interning(see: [Wikipedia/String interning](https://en.wikipedia.org/wiki/String_interning)). If multiple same string value exists in database(ex: "goblin","goblin", "goblin", "goblin", "goblin"....), standard database creates string value per query or store multiple same values. But MasterMemory stores single string value reference, it can save much memory if data is denormalized. Use intern or not is selected in constructor. If you want to disable automatically interning, use `internString:false`. `MemoryDatabase(byte[] databaseBinary, bool internString = true, MessagePack.IFormatterResolver formatterResolver = null, int maxDegreeOfParallelism = 1)`. MemoryDatabase has three(or four) query methods. * `T|RangeView` FindBy***(TKey key) * bool TryFindBy***(TKey key, out T result) * `T|RangeView` FindClosestBy***(TKey key, bool selectLower = true) * `RangeView` FindRangeBy***(TKey min, TKey max, bool ascendant = true) If index key is unique, generates `FindBy***` and `TryFindBy***` methods and then `FindBy***` throws `KeyNotFoundException` when key is not found. `By***` is generated by `PrimaryKey` and `SecondaryKey` defines. And has some utility properties. * `int` Count * `RangeView` All * `RangeView` AllReverse * `RangeView` SortBy*** * `T[] GetRawDataUnsafe()` `struct RangeView : IEnumerable` is the view of database elements. It has following property/method. * `T` [int index] * `int` Count * `T` First * `T` Last * `RangeView` Reverse * `IEnumerator` GetEnumerator() Extend Table --- Generated table class is defined partial class so create same namespace and class name's partial class on another file, you can add your custom method to generated table. Table class also defined partial `OnAfterConstruct` method, it called after table has been constructed. You can use it to store custom data to field after all data has been constructed. ```csharp // create MonsterTable.Partial.cs public sealed partial class MonsterTable { int maxHp; #pragma warning disable CS0649 readonly int minHp; #pragma warning restore CS0649 // called after constructed partial void OnAfterConstruct() { maxHp = All.Select(x => x.MaxHp).Max(); // you can use Unsafe.AsRef to set readonly field Unsafe.AsRef(minHp) = All.Select(x => x.MaxHp).Min(); } // add custom method other than standard Find method public IEnumerable GetRangedMonster(int arg1) { return All.Where....(); } } ``` ImmutableBuilder --- If you want to add/modify data to loaded database, you can use `ToImmutableBuilder` method. ```csharp // Create ImmutableBuilder from original database. var builder = db.ToImmutableBuilder(); // Add Or Replace compare with PrimaryKey builder.Diff(addOrReplaceData); // Remove by PrimaryKey builder.RemovePerson(new[] { 1, 10, 100 }); // Replace all data builder.ReplaceAll(newData); // Finally create new database MemoryDatabase newDatabase = builder.Build(); // If you want to save new database, you can convert to MemoryDatabase->DatabaseBuilder var newBuilder = newDatabase.ToDatabaseBuilder(); var newBinary = newBuilder.Build(); // or use WriteToStream ``` MemoryDatabase's reference can use as snapshot. ```csharp // 1 game per 1 instance public class GameRoom { MemoryDatabase database; // The reference is a snapshot of the timing of game begins. public GameRoom(MemoryDatabase database) { this.database = database; } } ``` Validator --- You can validate data by `MemoryDatabase.Validate` method. In default, it check unique key(data duplicated) and you can define custom validate logics. ```csharp // Implements IValidatable to targeted validation [MemoryTable("quest_master"), MessagePackObject(true)] public class Quest : IValidatable { // If index is Unique, validate duplicate in default. [PrimaryKey] public int Id { get; } public string Name { get; } public int RewardId { get; } public int Cost { get; } void IValidatable.Validate(IValidator validator) { // get the external reference table var items = validator.GetReferenceSet(); // Custom if logics. if (this.RewardId > 0) { // RewardId must exists in Item.ItemId items.Exists(x => x.RewardId, x => x.ItemId); } // Range check, Cost must be 10..20 validator.Validate(x => x.Cost >= 10); validator.Validate(x => x.Cost <= 20); // In this region, only called once so enable to validate overall of tables. if (validator.CallOnce()) { var quests = validator.GetTableSet(); // Check unique othe than index property. quests.Where(x => x.RewardId != 0).Unique(x => x.RewardId); } } } [MemoryTable("item_master"), MessagePackObject(true)] public class Item { [PrimaryKey] public int ItemId { get; } } void Main() { var db = new MemoryDatabase(bin); // Get the validate result. var validateResult = db.Validate(); if (validateResult.IsValidationFailed) { // Output string format. Console.WriteLine(validateResult.FormatFailedResults()); // Get the raw FaildItem[]. (.Type, .Message, .Data) // validateResult.FailedResults } } ``` Following is list of validation methods. ```csharp // all void methods are assert function, it stores message to ValidateResult if failed. interface IValidator { ValidatableSet GetTableSet(); ReferenceSet GetReferenceSet(); void Validate(Expression> predicate); void Validate(Func predicate, string message); void ValidateAction(Expression> predicate); void ValidateAction(Func predicate, string message); void Fail(string message); bool CallOnce(); } class ReferenceSet { IReadOnlyList TableData { get; } void Exists(Expression> elementSelector, Expression> referenceElementSelector); void Exists(Expression> elementSelector, Expression> referenceElementSelector, EqualityComparer equalityComparer); } class ValidatableSet { IReadOnlyList TableData { get; } void Unique(Expression> selector); void Unique(Expression> selector, IEqualityComparer equalityComparer); void Unique(Func selector, string message); void Unique(Func selector, IEqualityComparer equalityComparer, string message); void Sequential(Expression> selector, bool distinct = false); ValidatableSet Where(Func predicate); } ``` Metadata --- You can get the table-info, properties, indexes by metadata api. It helps to make custom importer/exporter application. ```csharp var metaDb = MemoryDatabase.GetMetaDatabase(); foreach (var table in metaDb.GetTableInfos()) { // for example, generate CSV header var sb = new StringBuilder(); foreach (var prop in table.Properties) { if (sb.Length != 0) sb.Append(","); // Name can convert to LowerCamelCase or SnakeCase. sb.Append(prop.NameSnakeCase); } File.WriteAllText(table.TableName + ".csv", sb.ToString(), new UTF8Encoding(false)); } ``` If creates console-app, our [ConsoleAppFramework](https://github.com/Cysharp/ConsoleAppFramework/) can easy to make helper applications. Here is sample of reading and creating dynamic from csv. `builder.AppendDynamic` and `System.Runtime.Serialization.FormatterServices.GetUninitializedObject` will help it. ```csharp var csv = @"monster_id,name,max_hp 1,foo,100 2,bar,200"; var fileName = "monster"; var builder = new DatabaseBuilder(); var meta = MemoryDatabase.GetMetaDatabase(); var table = meta.GetTableInfo(fileName); var tableData = new List(); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(csv))) using (var sr = new StreamReader(ms, Encoding.UTF8)) using (var reader = new TinyCsvReader(sr)) { while ((reader.ReadValuesWithHeader() is Dictionary values)) { // create data without call constructor // use System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject instead on .NET 8 var data = System.Runtime.Serialization.FormatterServices.GetUninitializedObject(table.DataType); foreach (var prop in table.Properties) { if (values.TryGetValue(prop.NameSnakeCase, out var rawValue)) { var value = ParseValue(prop.PropertyInfo.PropertyType, rawValue); if (prop.PropertyInfo.SetMethod == null) { throw new Exception("Target property does not exists set method. If you use {get;}, please change to { get; private set; }, Type:" + prop.PropertyInfo.DeclaringType + " Prop:" + prop.PropertyInfo.Name); } prop.PropertyInfo.SetValue(data, value); } else { throw new KeyNotFoundException($"Not found \"{prop.NameSnakeCase}\" in \"{fileName}.csv\" header."); } } tableData.Add(data); } } // add dynamic collection. builder.AppendDynamic(table.DataType, tableData); var bin = builder.Build(); var database = new MemoryDatabase(bin); static object ParseValue(Type type, string rawValue) { if (type == typeof(string)) return rawValue; if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { if (string.IsNullOrWhiteSpace(rawValue)) return null; return ParseValue(type.GenericTypeArguments[0], rawValue); } if (type.IsEnum) { var value = Enum.Parse(type, rawValue); return value; } switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: // True/False or 0,1 if (int.TryParse(rawValue, out var intBool)) { return Convert.ToBoolean(intBool); } return Boolean.Parse(rawValue); case TypeCode.Char: return Char.Parse(rawValue); case TypeCode.SByte: return SByte.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Byte: return Byte.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Int16: return Int16.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.UInt16: return UInt16.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Int32: return Int32.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.UInt32: return UInt32.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Int64: return Int64.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.UInt64: return UInt64.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Single: return Single.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Double: return Double.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Decimal: return Decimal.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.DateTime: return DateTime.Parse(rawValue, CultureInfo.InvariantCulture); default: if (type == typeof(DateTimeOffset)) { return DateTimeOffset.Parse(rawValue, CultureInfo.InvariantCulture); } else if (type == typeof(TimeSpan)) { return TimeSpan.Parse(rawValue, CultureInfo.InvariantCulture); } else if (type == typeof(Guid)) { return Guid.Parse(rawValue); } // or other your custom parsing. throw new NotSupportedException(); } } // Non string escape, tiny reader with header. public class TinyCsvReader : IDisposable { static char[] trim = new[] { ' ', '\t' }; readonly StreamReader reader; public IReadOnlyList Header { get; private set; } public TinyCsvReader(StreamReader reader) { this.reader = reader; { var line = reader.ReadLine(); if (line == null) throw new InvalidOperationException("Header is null."); var index = 0; var header = new List(); while (index < line.Length) { var s = GetValue(line, ref index); if (s.Length == 0) break; header.Add(s); } this.Header = header; } } string GetValue(string line, ref int i) { var temp = new char[line.Length - i]; var j = 0; for (; i < line.Length; i++) { if (line[i] == ',') { i += 1; break; } temp[j++] = line[i]; } return new string(temp, 0, j).Trim(trim); } public string[] ReadValues() { var line = reader.ReadLine(); if (line == null) return null; if (string.IsNullOrWhiteSpace(line)) return null; var values = new string[Header.Count]; var lineIndex = 0; for (int i = 0; i < values.Length; i++) { var s = GetValue(line, ref lineIndex); values[i] = s; } return values; } public Dictionary ReadValuesWithHeader() { var values = ReadValues(); if (values == null) return null; var dict = new Dictionary(); for (int i = 0; i < values.Length; i++) { dict.Add(Header[i], values[i]); } return dict; } public void Dispose() { reader.Dispose(); } } } ``` Inheritance --- Currently MasterMemory does not support inheritance. Recommend way to create common method, use interface and extension method. But if you want to create common method with common cached field(made by `OnAfterConstruct`), for workaround, create abstract class and all data properties to abstract. ```csharp public abstract class FooAndBarBase { // all data properties to virtual public virtual int Prop1 { get; protected set; } public virtual int Prop2 { get; protected set; } [IgnoreMember] public int Prop3 => Prop1 + Prop2; public IEnumerable CommonMethod() { throw new NotImplementedException(); } } [MemoryTable("foo_table"), MessagePackObject(true)] public class FooTable : FooAndBarBase { [PrimaryKey] public override int Prop1 { get; protected set; } public override int Prop2 { get; protected set; } } [MemoryTable("bar_table"), MessagePackObject(true)] public class BarTable : FooAndBarBase { [PrimaryKey] public override int Prop1 { get; protected set; } public override int Prop2 { get; protected set; } } ``` Optimization --- When invoking `new MemoryDatabase(byte[] databaseBinary...)`, read and construct database from binary. If binary size is large then construct performance will slow down. `MemoryDatabase` has `ctor(..., int maxDegreeOfParallelism = 1)` option in constructor to construct in parallel. ```csharp var database = new MemoryDatabase(bin, maxDegreeOfParallelism: Environment.ProcessorCount); ``` The use of Parallel can greatly improve the construct performance. Recommend to use `Environment.ProcessorCount`. If you want to reduce code size of generated code, Validator and MetaDatabase info can omit in runtime. Generated code has two symbols `DISABLE_MASTERMEMORY_VALIDATOR` and `DISABLE_MASTERMEMORY_METADATABASE`. By defining them, can be erased from the build code. The database generation/loading speed and size are affected by MessagePack's serialization format. Using `[MessagePackObject]` with `[Key]` attributes instead of `[MessagePackObject(true)]` can improve loading speed and reduce size. However, regarding size, since LZ4 compression is used by default, the difference may not be significant. MasterMemoryGeneratorOptions --- The Source Generator settings are configured using the assembly attribute `[MasterMemoryGeneratorOptions]`. By placing it in any file, you can configure the following settings. ```csharp [assembly: MasterMemoryGeneratorOptions( Namespace = "MyConsoleApp", IsReturnNullIfKeyNotFound = true, PrefixClassName = "Foo" )] ``` * `Namespace`: Changes the namespace of generated files. If not specified, it tries to get the `RootNamespace` set in the csproj file; if that's not available, it defaults to `MasterMemory` * `IsReturnNullIfKeyNotFound`: By default, the `Find` method throws a `KeyNotFoundException` when a key is not found. If set to true, the return type becomes `T?` and returns null instead * `PrefixClassName`: Adds a prefix to the class names of generated files. For example, `DatabaseBuilder` becomes `FooDatabaseBuilder`. This allows you to distinguish between multiple MasterMemory projects by name. v2 -> v3 migration --- Since there are no changes to the API, binary format, or behavior, you can migrate simply by changing the command-line tool settings to the assembly attribute `[MasterMemoryGeneratorOptions]`. * The code generator (MSBuild Task, .NET Core Global/Local Tools) has been removed and replaced with Source Generator * Tool options are now available through `MasterMemoryGeneratorOptions` (e.g. `-usingNamespace`) * The `-addImmutableConstructor` option has been completely removed; please use C#'s record or init keyword instead * The library is now only available through NuGet for Unity. Please use NuGetForUnity License --- This library is under the MIT License. ================================================ FILE: sandbox/Benchmark/Benchmark.csproj ================================================  Exe net6.0 1701;1702;NU1904 Analyzer false ================================================ FILE: sandbox/Benchmark/Program.cs ================================================ #pragma warning disable using BenchmarkDotNet.Attributes; using System.Linq; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Diagnosers; using BenchmarkDotNet.Environments; using BenchmarkDotNet.Exporters; using BenchmarkDotNet.Exporters.Csv; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; using LiteDB; using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using System.IO; using TestPerfLiteDB; using Enyim.Caching; using Enyim.Caching.Configuration; using Enyim.Caching.Memcached; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Enyim.Caching.Memcached.Transcoders; using RocksDbSharp; using MessagePack; using System.Text; namespace Benchmark { class Program { static void Main(string[] args) { BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args); } } public class BenchmarkConfig : ManualConfig { public BenchmarkConfig() { // run quickly:) var baseConfig = Job.ShortRun.WithIterationCount(1).WithWarmupCount(1); // Add(baseConfig.With(Runtime.Clr).With(Jit.RyuJit).With(Platform.X64)); Add(baseConfig.With(Runtime.Core).With(Jit.RyuJit).With(Platform.X64)); // Add(baseConfig.With(InProcessEmitToolchain.Instance)); Add(MarkdownExporter.GitHub); Add(CsvExporter.Default); Add(MemoryDiagnoser.Default); } } [Config(typeof(BenchmarkConfig))] public class SimpleRun { MemoryDatabase db; SQLite_Test sqliteMemory; SQLite_Test sqliteFile; LiteDB_Test defaultLiteDb; LiteDB_Test inmemoryLiteDb; LiteDB_Test2 liteDb2; MemcachedClient localMemcached; Dictionary dictionary; RocksDb rocksDb; const int QueryId = 741; public SimpleRun() { var bin = new DatabaseBuilder().Append(MakeDoc(5000)).Build(); db = new MemoryDatabase(bin); sqliteMemory = new SQLite_Test(5000, null, false, true); sqliteMemory.Prepare(); sqliteMemory.Insert(); sqliteMemory.CreateIndex(); sqliteFile = new SQLite_Test(5000, null, false, false); sqliteFile.Prepare(); sqliteFile.Insert(); sqliteFile.CreateIndex(); defaultLiteDb = new LiteDB_Test(5000, null, new LiteDB.FileOptions { Journal = true, FileMode = LiteDB.FileMode.Shared }); defaultLiteDb.Prepare(); defaultLiteDb.Insert(); defaultLiteDb.CreateIndex(); inmemoryLiteDb = new LiteDB_Test(5000); inmemoryLiteDb.Prepare(); inmemoryLiteDb.Insert(); inmemoryLiteDb.CreateIndex(); liteDb2 = new LiteDB_Test2(5000); liteDb2.Prepare(); liteDb2.Insert(); liteDb2.CreateIndex(); dictionary = new Dictionary(); foreach (var item in MakeDoc(5000)) { dictionary.Add(item.id, item); } { var options = new DbOptions().SetCreateIfMissing(true); var tempPath = Guid.NewGuid() + ".bin"; rocksDb = RocksDb.Open(options, tempPath); foreach (var item in MakeDoc(5000)) { rocksDb.Put(Encoding.UTF8.GetBytes("testdata." + item.id), MessagePackSerializer.Serialize(item)); } } var config = new MemcachedClientConfiguration(new LoggerDummy(), new Dummy()); localMemcached = new MemcachedClient(new LoggerDummy(), config); foreach (var item in MakeDoc(5000)) { localMemcached.Add("testdoc2." + item.id, item, 9999); } } public IEnumerable MakeDoc(int count) { foreach (var doc in Helper.GetDocs(count)) { var v = new TestDoc { id = (int)doc["_id"], name = (string)doc["name"], lorem = (string)doc["lorem"] }; yield return v; } } [Benchmark(Baseline = true)] public TestDoc MasterMemoryQuery() { return db.TestDocTable.FindByid(QueryId); } [Benchmark] public TestDoc SQLiteInMemoryQuery() { using (var cmd = new SQLiteCommand("SELECT * FROM col WHERE id = @id", sqliteMemory._db)) { cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); cmd.Parameters["id"].Value = QueryId; using (var r = cmd.ExecuteReader()) { r.Read(); var id = r.GetInt32(0); var name = r.GetString(1); var lorem = r.GetString(2); return new TestDoc { id = 1, name = name, lorem = lorem }; } } } [Benchmark] public TestDoc SQLiteFileQuery() { using (var cmd = new SQLiteCommand("SELECT * FROM col WHERE id = @id", sqliteFile._db)) { cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); cmd.Parameters["id"].Value = QueryId; using (var r = cmd.ExecuteReader()) { r.Read(); var id = r.GetInt32(0); var name = r.GetString(1); var lorem = r.GetString(2); return new TestDoc { id = 1, name = name, lorem = lorem }; } } } [Benchmark] public BsonDocument LiteDbDefaultQuery() { return defaultLiteDb._db.FindOne("col", LiteDB.Query.EQ("_id", QueryId)); } [Benchmark] public BsonDocument LiteDbInMemoryQuery() { return inmemoryLiteDb._db.FindOne("col", LiteDB.Query.EQ("_id", QueryId)); } [Benchmark] public object LocalMemcachedQuery() { return localMemcached.Get("testdoc2." + QueryId); } //[Benchmark] //public TestDoc DictionaryQuery() //{ // return dictionary.TryGetValue(QueryId, out var r) ? r : null; //} [Benchmark] public TestDoc RocksDbQuery() { return MessagePackSerializer.Deserialize(rocksDb.Get(Encoding.UTF8.GetBytes("testdata." + QueryId))); } } public class SQLite_Test { private string _filename; public SQLiteConnection _db; private int _count; public int Count { get { return _count; } } public SQLite_Test(int count, string password, bool journal, bool memory = false) { _count = count; _filename = "sqlite-" + Guid.NewGuid().ToString("n") + ".db"; if (memory) { var cs = "Data Source=:memory:;New=True;"; _db = new SQLiteConnection(cs); } else { var cs = "Data Source=" + _filename; if (password != null) cs += "; Password=" + password; if (journal == false) cs += "; Journal Mode=Off"; _db = new SQLiteConnection(cs); } } public void Prepare() { _db.Open(); var table = new SQLiteCommand("CREATE TABLE col (id INTEGER NOT NULL PRIMARY KEY, name TEXT, lorem TEXT)", _db); table.ExecuteNonQuery(); var table2 = new SQLiteCommand("CREATE TABLE col_bulk (id INTEGER NOT NULL PRIMARY KEY, name TEXT, lorem TEXT)", _db); table2.ExecuteNonQuery(); } public void Insert() { // standard insert is slow, mod same as Bulk using (var trans = _db.BeginTransaction()) { var cmd = new SQLiteCommand("INSERT INTO col (id, name, lorem) VALUES (@id, @name, @lorem)", _db); cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("name", DbType.String)); cmd.Parameters.Add(new SQLiteParameter("lorem", DbType.String)); foreach (var doc in Helper.GetDocs(_count)) { cmd.Parameters["id"].Value = (int)doc["_id"]; cmd.Parameters["name"].Value = (string)doc["name"]; cmd.Parameters["lorem"].Value = (string)doc["lorem"]; cmd.ExecuteNonQuery(); } trans.Commit(); } } public void CreateIndex() { var cmd = new SQLiteCommand("CREATE INDEX idx1 ON col (name)", _db); cmd.ExecuteNonQuery(); } public void Query() { var cmd = new SQLiteCommand("SELECT * FROM col WHERE id = @id", _db); cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); for (var i = 0; i < _count; i++) { cmd.Parameters["id"].Value = i; var r = cmd.ExecuteReader(); r.Read(); var name = r.GetString(1); var lorem = r.GetString(2); r.Close(); } } public void Dispose() { _db.Dispose(); } } public class LiteDB_Test { private string _filename; public LiteEngine _db; private int _count; public int Count { get { return _count; } } public LiteDB_Test(int count, string password, LiteDB.FileOptions options) { _count = count; _filename = "dblite-" + Guid.NewGuid().ToString("n") + ".db"; var disk = new FileDiskService(_filename, options); _db = new LiteEngine(disk, password); } public LiteDB_Test(int count) { _count = count; _filename = "dblite-" + Guid.NewGuid().ToString("n") + ".db"; var ms = new MemoryStream(); var disk = new LiteDB.StreamDiskService(ms); //var disk = new FileDiskService(_filename, options); _db = new LiteEngine(disk); } public void Prepare() { } public void Insert() { foreach (var doc in Helper.GetDocs(_count)) { _db.Insert("col", doc); } } public void Bulk() { _db.Insert("col_bulk", Helper.GetDocs(_count)); } public void Update() { foreach (var doc in Helper.GetDocs(_count)) { _db.Update("col", doc); } } public void CreateIndex() { _db.EnsureIndex("col", "name", false); } public void Query() { for (var i = 0; i < _count; i++) { _db.Find("col", LiteDB.Query.EQ("_id", i)).Single(); } } public void Delete() { _db.Delete("col", LiteDB.Query.All()); } public void Drop() { _db.DropCollection("col_bulk"); } public void Dispose() { _db.Dispose(); File.Delete(_filename); } } public class LiteDB_Test2 { private string _filename; public LiteDatabase _db; private int _count; public LiteCollection _collection; public int Count { get { return _count; } } public LiteDB_Test2(int count, string password, LiteDB.FileOptions options) { _count = count; _filename = "dblite-" + Guid.NewGuid().ToString("n") + ".db"; var disk = new FileDiskService(_filename, options); _db = new LiteDatabase(disk); _collection = _db.GetCollection(); } public LiteDB_Test2(int count) { _count = count; _filename = "dblite-" + Guid.NewGuid().ToString("n") + ".db"; var ms = new MemoryStream(); var disk = new LiteDB.StreamDiskService(ms); //var disk = new FileDiskService(_filename, options); _db = new LiteDatabase(disk); _collection = _db.GetCollection(); } public void Prepare() { } public void Insert() { foreach (var doc in Helper.GetDocs(_count)) { _collection.Insert(new TestDoc { // id = (int)doc["_id"], name = (string)doc["name"], lorem = (string)doc["lorem"] }); } } public void CreateIndex() { //_collection.EnsureIndex(x => x.id, true); } } class Dummy : IOptions { public MemcachedClientOptions Value => new MemcachedClientOptions { Servers = new List { new Server { Address = "127.0.0.1", Port = 11211 } }, Protocol = MemcachedProtocol.Binary, // Transcoder = new BinaryFormatterTranscoder() }; } class LoggerDummy : ILoggerFactory { public void AddProvider(ILoggerProvider provider) { } public ILogger CreateLogger(string categoryName) { return new NullLogger(); } public void Dispose() { } class NullLogger : ILogger { public IDisposable BeginScope(TState state) { return new EmptyDisposable(); } public bool IsEnabled(LogLevel logLevel) { return false; } public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } class EmptyDisposable : IDisposable { public void Dispose() { } } } } } ================================================ FILE: sandbox/Benchmark/Utils/Helper.cs ================================================ #pragma warning disable using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using MessagePack; using LiteDB; using MasterMemory; namespace TestPerfLiteDB { [MemoryTable("TestDoc"), MessagePackObject(true)] public class TestDoc { [PrimaryKey] public int id { get; set; } public string name { get; set; } public string lorem { get; set; } public TestDoc() { } public TestDoc(int id, string name, string lorem) { this.id = id; this.name = name; this.lorem = lorem; } } public static class Helper { public static IEnumerable GetDocs(int count) { for (var i = 0; i < count; i++) { yield return new BsonDocument { { "_id", i }, { "name", Guid.NewGuid().ToString() }, { "lorem", LoremIpsum(3, 5, 2, 3, 3) } }; } } public static string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numParagraphs) { var words = new[] { "lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "sed", "diam", "nonummy", "nibh", "euismod", "tincidunt", "ut", "laoreet", "dolore", "magna", "aliquam", "erat" }; var rand = new Random(DateTime.Now.Millisecond); var numSentences = rand.Next(maxSentences - minSentences) + minSentences + 1; var numWords = rand.Next(maxWords - minWords) + minWords + 1; var result = new StringBuilder(); for (int p = 0; p < numParagraphs; p++) { for (int s = 0; s < numSentences; s++) { for (int w = 0; w < numWords; w++) { if (w > 0) { result.Append(" "); } result.Append(words[rand.Next(words.Length)]); } result.Append(". "); } result.AppendLine(); } return result.ToString(); } } } ================================================ FILE: sandbox/ConsoleApp/ConsoleApp.csproj ================================================ Exe net9.0 $(DefineConstants)TRACE; $(DefineConstants)TRACE; Analyzer false ================================================ FILE: sandbox/ConsoleApp/Program.cs ================================================ #pragma warning disable CS8618 #pragma warning disable CS8602 #pragma warning disable CS8603 using MasterMemory; using MessagePack; using System; using System.Buffers; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Text; [assembly: MasterMemoryGeneratorOptions( Namespace = "ConsoleApp" // , // IsReturnNullIfKeyNotFound = true, // PrefixClassName = "Foo" )] public class FooItem { public bool TryFindByItemId(int key, out Item result) { result = default!; return true; } } [MemoryTable("quest_master"), MessagePackObject(true)] public class Quest : IValidatable { [PrimaryKey] public int Id { get; set; } public string Name { get; set; } public int RewardId { get; set; } public int Cost { get; set; } public MyEnum MyProperty { get; set; } void IValidatable.Validate(IValidator validator) { var items = validator.GetReferenceSet(); if (this.RewardId > 0) { items.Exists(x => x.RewardId, x => x.ItemId); } validator.Validate(x => x.Cost >= 10); validator.Validate(x => x.Cost <= 20); if (validator.CallOnce()) { var quests = validator.GetTableSet(); quests.Where(x => x.RewardId != 0).Unique(x => x.RewardId); } } public enum MyEnum { A, B, C } } [MemoryTable("item"), MessagePackObject(true)] public class Item { [PrimaryKey] public int ItemId { get; set; } } namespace ConsoleApp.Tables { public sealed partial class MonsterTable { /* readonly */ int maxHp; partial void OnAfterConstruct() { maxHp = All.Select(x => x.MaxHp).Max(); } } } namespace ConsoleApp { [MemoryTable("monster"), MessagePackObject(true)] public partial class Monster { [PrimaryKey] public int MonsterId { get; private set; } public string Name { get; private set; } public int MaxHp { get; private set; } public Monster(int MonsterId, string Name, int MaxHp) { this.MonsterId = MonsterId; this.Name = Name; this.MaxHp = MaxHp; } } [MemoryTable("enumkeytable"), MessagePackObject(true)] public class EnumKeyTable { [PrimaryKey] public Gender Gender { get; set; } } public enum Gender { Male, Female } [MemoryTable("person"), MessagePackObject(true)] public class Person { [PrimaryKey(keyOrder: 1)] public int PersonId { get; set; } [SecondaryKey(0), NonUnique] [SecondaryKey(2, keyOrder: 1), NonUnique] public int Age { get; set; } [SecondaryKey(1), NonUnique] [SecondaryKey(2, keyOrder: 0), NonUnique] public Gender Gender { get; set; } public string Name { get; set; } public Person() { } public Person(int PersonId, int Age, Gender Gender, string Name) { this.PersonId = PersonId; this.Age = Age; this.Gender = Gender; this.Name = Name; } public override string ToString() { return $"{PersonId} {Age} {Gender} {Name}"; } } class ByteBufferWriter : IBufferWriter { byte[] buffer; int index; public int CurrentOffset => index; public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); public ReadOnlyMemory WrittenMemory => new ReadOnlyMemory(buffer, 0, index); public ByteBufferWriter() { buffer = new byte[1024]; index = 0; } public void Advance(int count) { index += count; } public Memory GetMemory(int sizeHint = 0) { AGAIN: var nextSize = index + sizeHint; if (buffer.Length < nextSize) { Array.Resize(ref buffer, Math.Max(buffer.Length * 2, nextSize)); } if (sizeHint == 0) { var result = new Memory(buffer, index, buffer.Length - index); if (result.Length == 0) { sizeHint = 1024; goto AGAIN; } return result; } else { return new Memory(buffer, index, sizeHint); } } public Span GetSpan(int sizeHint = 0) { return GetMemory(sizeHint).Span; } } [MemoryTable(nameof(Test1))] public class Test1 { [PrimaryKey] public int Id { get; set; } } [MessagePackObject(false)] [MemoryTable(nameof(Test2))] public class Test2 { [PrimaryKey] [Key(0)] public int Id { get; set; } } class Program { static void Main(string[] args) { var csv = @"monster_id,name,max_hp 1,foo,100 2,bar,200"; var fileName = "monster"; var builder = new DatabaseBuilder(); var meta = MemoryDatabase.GetMetaDatabase(); var table = meta.GetTableInfo(fileName); var tableData = new List(); using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(csv))) using (var sr = new StreamReader(ms, Encoding.UTF8)) using (var reader = new TinyCsvReader(sr)) { while ((reader.ReadValuesWithHeader() is Dictionary values)) { // create data without call constructor var data = RuntimeHelpers.GetUninitializedObject(table.DataType); foreach (var prop in table.Properties) { if (values.TryGetValue(prop.NameSnakeCase, out var rawValue)) { var value = ParseValue(prop.PropertyInfo.PropertyType, rawValue); if (prop.PropertyInfo.SetMethod == null) { throw new Exception("Target property does not exists set method. If you use {get;}, please change to { get; private set; }, Type:" + prop.PropertyInfo.DeclaringType + " Prop:" + prop.PropertyInfo.Name); } prop.PropertyInfo.SetValue(data, value); } else { throw new KeyNotFoundException($"Not found \"{prop.NameSnakeCase}\" in \"{fileName}.csv\" header."); } } tableData.Add(data); } } // add dynamic collection. builder.AppendDynamic(table.DataType, tableData); var bin = builder.Build(); var database = new MemoryDatabase(bin, maxDegreeOfParallelism: Environment.ProcessorCount); } static object ParseValue(Type type, string rawValue) { if (type == typeof(string)) return rawValue; if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) { if (string.IsNullOrWhiteSpace(rawValue)) return null; return ParseValue(type.GenericTypeArguments[0], rawValue); } if (type.IsEnum) { var value = Enum.Parse(type, rawValue); return value; } switch (Type.GetTypeCode(type)) { case TypeCode.Boolean: // True/False or 0,1 if (int.TryParse(rawValue, out var intBool)) { return Convert.ToBoolean(intBool); } return Boolean.Parse(rawValue); case TypeCode.Char: return Char.Parse(rawValue); case TypeCode.SByte: return SByte.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Byte: return Byte.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Int16: return Int16.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.UInt16: return UInt16.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Int32: return Int32.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.UInt32: return UInt32.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Int64: return Int64.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.UInt64: return UInt64.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Single: return Single.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Double: return Double.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.Decimal: return Decimal.Parse(rawValue, CultureInfo.InvariantCulture); case TypeCode.DateTime: return DateTime.Parse(rawValue, CultureInfo.InvariantCulture); default: if (type == typeof(DateTimeOffset)) { return DateTimeOffset.Parse(rawValue, CultureInfo.InvariantCulture); } else if (type == typeof(TimeSpan)) { return TimeSpan.Parse(rawValue, CultureInfo.InvariantCulture); } else if (type == typeof(Guid)) { return Guid.Parse(rawValue); } // or other your custom parsing. throw new NotSupportedException(); } } // Non string escape, tiny reader with header. public class TinyCsvReader : IDisposable { static char[] trim = new[] { ' ', '\t' }; readonly StreamReader reader; public IReadOnlyList Header { get; private set; } public TinyCsvReader(StreamReader reader) { this.reader = reader; { var line = reader.ReadLine(); if (line == null) throw new InvalidOperationException("Header is null."); var index = 0; var header = new List(); while (index < line.Length) { var s = GetValue(line, ref index); if (s.Length == 0) break; header.Add(s); } this.Header = header; } } string GetValue(string line, ref int i) { var temp = new char[line.Length - i]; var j = 0; for (; i < line.Length; i++) { if (line[i] == ',') { i += 1; break; } temp[j++] = line[i]; } return new string(temp, 0, j).Trim(trim); } public string[] ReadValues() { var line = reader.ReadLine(); if (line == null) return null; if (string.IsNullOrWhiteSpace(line)) return null; var values = new string[Header.Count]; var lineIndex = 0; for (int i = 0; i < values.Length; i++) { var s = GetValue(line, ref lineIndex); values[i] = s; } return values; } public Dictionary ReadValuesWithHeader() { var values = ReadValues(); if (values == null) return null; var dict = new Dictionary(); for (int i = 0; i < values.Length; i++) { dict.Add(Header[i], values[i]); } return dict; } public void Dispose() { reader.Dispose(); } } } } ================================================ FILE: sandbox/GeneratorSandbox/GeneratorSandbox.csproj ================================================  Exe net9.0 enable enable Analyzer false ================================================ FILE: sandbox/GeneratorSandbox/Program.cs ================================================ using MasterMemory; using MessagePack; using GeneratorSandbox; using System.Runtime.CompilerServices; //[assembly: MasterMemoryGeneratorOptions( // Namespace = "Z", // IsReturnNullIfKeyNotFound = true, // PrefixClassName = "")] // to create database, use DatabaseBuilder and Append method. var builder = new DatabaseBuilder(); builder.Append(new Person[] { new (){ PersonId = 0, Age = 13, Gender = Gender.Male, Name = "Dana Terry" }, new (){ PersonId = 1, Age = 17, Gender = Gender.Male, Name = "Kirk Obrien" }, new (){ PersonId = 2, Age = 31, Gender = Gender.Male, Name = "Wm Banks" }, new (){ PersonId = 3, Age = 44, Gender = Gender.Male, Name = "Karl Benson" }, new (){ PersonId = 4, Age = 23, Gender = Gender.Male, Name = "Jared Holland" }, new (){ PersonId = 5, Age = 27, Gender = Gender.Female, Name = "Jeanne Phelps" }, new (){ PersonId = 6, Age = 25, Gender = Gender.Female, Name = "Willie Rose" }, new (){ PersonId = 7, Age = 11, Gender = Gender.Female, Name = "Shari Gutierrez" }, new (){ PersonId = 8, Age = 63, Gender = Gender.Female, Name = "Lori Wilson" }, new (){ PersonId = 9, Age = 34, Gender = Gender.Female, Name = "Lena Ramsey" }, }); // build database binary(you can also use `WriteToStream` for save to file). byte[] data = builder.Build(); var db = new MemoryDatabase(data); // .PersonTable.FindByPersonId is fully typed by code-generation. Person person = db.PersonTable.FindByPersonId(5); // Multiple key is also typed(***And * **), Return value is multiple if key is marked with `NonUnique`. RangeView result = db.PersonTable.FindByGenderAndAge((Gender.Female, 23)); // Get nearest value(choose lower(default) or higher). RangeView age1 = db.PersonTable.FindClosestByAge(31); // Get range(min-max inclusive). RangeView age2 = db.PersonTable.FindRangeByAge(20, 29); public enum Gender { Male, Female, Unknown } // table definition marked by MemoryTableAttribute. // database-table must be serializable by MessagePack-CSsharp [MemoryTable("person"), MessagePackObject(true)] public record Person { // index definition by attributes. [PrimaryKey] public required int PersonId { get; init; } // secondary index can add multiple(discriminated by index-number). [SecondaryKey(0), NonUnique] [SecondaryKey(1, keyOrder: 1), NonUnique] public required int Age { get; init; } [SecondaryKey(2), NonUnique] [SecondaryKey(1, keyOrder: 0), NonUnique] public required Gender Gender { get; init; } public required string Name { get; init; } } ================================================ FILE: sandbox/PerfTest2/Engines/Dictionary_Test.cs ================================================ #pragma warning disable using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using TestPerfLiteDB; namespace TestPerfLiteDB { public class Dictionary_Test : ITest { private string _filename; private int _count; Dictionary dict; public int Count { get { return _count; } } public int FileLength { get { return (int)new FileInfo(_filename).Length; } } public Dictionary_Test(int count) { _count = count; _filename = "dict-" + Guid.NewGuid().ToString("n") + ".db"; dict = new Dictionary(); } public void Insert() { foreach (var doc in Helper.GetDocs(_count)) { var v = new TestDoc { id = doc["_id"].AsInt32, name = doc["name"].AsString, lorem = doc["lorem"].AsString }; dict.Add(v.id, v); } } public void Bulk() { } public void CreateIndex() { } public void Dispose() { } public void Prepare() { } public void Query() { for (var i = 0; i < _count; i++) { TestDoc d; dict.TryGetValue(i, out d); } } public void Update() { } } public class ConcurrentDictionary_Test : ITest { private string _filename; private int _count; ConcurrentDictionary dict; public int Count { get { return _count; } } public int FileLength { get { return (int)new FileInfo(_filename).Length; } } public ConcurrentDictionary_Test(int count) { _count = count; _filename = "concurrentdict-" + Guid.NewGuid().ToString("n") + ".db"; dict = new ConcurrentDictionary(); } public void Insert() { foreach (var doc in Helper.GetDocs(_count)) { var v = new TestDoc { id = doc["_id"].AsInt32, name = doc["name"].AsString, lorem = doc["lorem"].AsString }; dict.TryAdd(v.id, v); } } public void Bulk() { } public void CreateIndex() { } public void Dispose() { } public void Prepare() { } public void Query() { for (var i = 0; i < _count; i++) { TestDoc d; dict.TryGetValue(i, out d); } } public void Update() { } } public class ImmutableDictionary_Test : ITest { private string _filename; private int _count; ImmutableDictionary dict; public int Count { get { return _count; } } public int FileLength { get { return (int)new FileInfo(_filename).Length; } } public ImmutableDictionary_Test(int count) { _count = count; _filename = "immutabledict-" + Guid.NewGuid().ToString("n") + ".db"; //dict = new ImmutableDictionary(); } public void Insert() { var builder = ImmutableDictionary.CreateBuilder(); foreach (var doc in Helper.GetDocs(_count)) { var v = new TestDoc { id = doc["_id"].AsInt32, name = doc["name"].AsString, lorem = doc["lorem"].AsString }; builder.Add(v.id, v); } dict = builder.ToImmutableDictionary(); } public void Bulk() { } public void CreateIndex() { } public void Dispose() { } public void Prepare() { } public void Query() { for (var i = 0; i < _count; i++) { TestDoc d; dict.TryGetValue(i, out d); } } public void Update() { } } } ================================================ FILE: sandbox/PerfTest2/Engines/ITest.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using LiteDB; namespace TestPerfLiteDB { public interface ITest : IDisposable { int Count { get; } int FileLength { get; } void Prepare(); void Insert(); void Bulk(); void Update(); void CreateIndex(); void Query(); //void Delete(); //void Drop(); } } ================================================ FILE: sandbox/PerfTest2/Engines/LiteDB_Test.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using LiteDB; namespace TestPerfLiteDB { public class LiteDB_Test : ITest { private string _filename; private LiteEngine _db; private int _count; public int Count { get { return _count; } } public int FileLength { get { return (int)new FileInfo(_filename).Length; } } public LiteDB_Test(int count, string password, LiteDB.FileOptions options) { _count = count; _filename = "dblite-" + Guid.NewGuid().ToString("n") + ".db"; var disk = new FileDiskService(_filename, options); _db = new LiteEngine(disk, password); } public LiteDB_Test(int count) { _count = count; _filename = "dblite-" + Guid.NewGuid().ToString("n") + ".db"; var ms = new MemoryStream(); var disk = new LiteDB.StreamDiskService(ms); //var disk = new FileDiskService(_filename, options); _db = new LiteEngine(disk); } public void Prepare() { } public void Insert() { foreach (var doc in Helper.GetDocs(_count)) { _db.Insert("col", doc); } } public void Bulk() { _db.Insert("col_bulk", Helper.GetDocs(_count)); } public void Update() { foreach (var doc in Helper.GetDocs(_count)) { _db.Update("col", doc); } } public void CreateIndex() { _db.EnsureIndex("col", "name", false); } public void Query() { for (var i = 0; i < _count; i++) { _db.Find("col", LiteDB.Query.EQ("_id", i)).Single(); } } public void Delete() { _db.Delete("col", LiteDB.Query.All()); } public void Drop() { _db.DropCollection("col_bulk"); } public void Dispose() { _db.Dispose(); File.Delete(_filename); } } } ================================================ FILE: sandbox/PerfTest2/Engines/MasterMemory_Test.cs ================================================ #pragma warning disable using MasterMemory; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestPerfLiteDB { public class MasterMemory_Test : ITest { private string _filename; private int _count; MemoryDatabase database; public int Count { get { return _count; } } public int FileLength { get { return (int)new FileInfo(_filename).Length; } } public MasterMemory_Test(int count) { _count = count; _filename = "mastermemorydatabase-" + Guid.NewGuid().ToString("n") + ".db"; } public IEnumerable MakeDoc() { foreach (var doc in Helper.GetDocs(_count)) { var v = new TestDoc { id = doc["_id"].AsInt32, name = doc["name"].AsString, lorem = doc["lorem"].AsString }; yield return v; } } public void Insert() { var builder = new DatabaseBuilder(); builder.Append(MakeDoc()); var saved = builder.Build(); File.WriteAllBytes(_filename, saved); database = new MemoryDatabase(saved); } public void Bulk() { } public void CreateIndex() { } public void Dispose() { } public void Prepare() { } public void Query() { for (var i = 0; i < _count; i++) { //TestDoc d; database.TestDocTable.FindByid(i); } } public void Update() { } } } ================================================ FILE: sandbox/PerfTest2/Engines/RavenDB_Test.cs ================================================ //using Raven.Client; //using Raven.Client.Embedded; //using Raven.Client.Indexes; //using System; //using System.Collections.Generic; //using System.IO; //using System.Linq; //using System.Text; //using System.Threading.Tasks; //namespace TestPerfLiteDB //{ // public class TestDocCreation : AbstractIndexCreationTask // { // public TestDocCreation() // { // Map = xs => xs.Select(x => new { x.id }); // } // } // public class RavenDB_Test : ITest // { // private string _filename; // private int _count; // private bool isinmemory; // IDocumentStore store; // public int Count { get { return _count; } } // public int FileLength { get { return (int)new FileInfo(_filename).Length; } } // public RavenDB_Test(int count, bool isinmemory) // { // _count = count; // _filename = "ravendb-" + Guid.NewGuid().ToString("n") + ".db"; // this.isinmemory = isinmemory; // } // public void Bulk() // { // if (isinmemory) // { // } // //using (var store = new EmbeddableDocumentStore { RunInMemory = true }.Initialize()) // //{ // // using (var bulk = store.BulkInsert()) // // { // // bulk.Store(new MyClass { Id = 9999, MyProperty = 1000 }); // // } // // var session = store.OpenSession(); // // store.ExecuteIndex(new MyClassIndex()); // // var huga = session.Load("MyClasses/9999"); // //} // } // public void CreateIndex() // { // store.ExecuteIndex(new TestDocCreation()); // } // public void Dispose() // { // store.Dispose(); // } // IEnumerable MakeDoc() // { // foreach (var doc in Helper.GetDocs(_count)) // { // var v = new TestDoc // { // id = doc["_id"].AsInt32, // name = doc["name"].AsString, // lorem = doc["lorem"].AsString // }; // yield return v; // } // } // public void Insert() // { // using (var bulk = store.BulkInsert()) // { // foreach (var item in MakeDoc()) // { // bulk.Store(item); // } // } // } // public void Prepare() // { // if (isinmemory) // { // store = new EmbeddableDocumentStore { RunInMemory = true }.Initialize(); // } // else // { // // store = new EmbeddableDocumentStore { RunInMemory = true }.Initialize(); // } // } // public void Query() // { // for (var i = 0; i < _count; i++) // { // using (var session = store.OpenSession()) // { // session.Load("TestDoc/" + i); // } // } // } // public void Update() // { // } // } //} ================================================ FILE: sandbox/PerfTest2/Engines/SQLite_Test.cs ================================================ using System; using System.Collections.Generic; using System.Data; using System.Data.SQLite; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TestPerfLiteDB { public class SQLite_Test : ITest { private string _filename; private SQLiteConnection _db; private int _count; public int Count { get { return _count; } } public int FileLength { get { return (int)new FileInfo(_filename).Length; } } public SQLite_Test(int count, string password, bool journal, bool memory = false) { _count = count; _filename = "sqlite-" + Guid.NewGuid().ToString("n") + ".db"; if (memory) { var cs = "Data Source=:memory:;New=True;"; _db = new SQLiteConnection(cs); } else { var cs = "Data Source=" + _filename; if (password != null) cs += "; Password=" + password; if (journal == false) cs += "; Journal Mode=Off"; _db = new SQLiteConnection(cs); } } public void Prepare() { _db.Open(); var table = new SQLiteCommand("CREATE TABLE col (id INTEGER NOT NULL PRIMARY KEY, name TEXT, lorem TEXT)", _db); table.ExecuteNonQuery(); var table2 = new SQLiteCommand("CREATE TABLE col_bulk (id INTEGER NOT NULL PRIMARY KEY, name TEXT, lorem TEXT)", _db); table2.ExecuteNonQuery(); } public void Insert() { // standard insert is slow, mod same as Bulk using (var trans = _db.BeginTransaction()) { var cmd = new SQLiteCommand("INSERT INTO col (id, name, lorem) VALUES (@id, @name, @lorem)", _db); cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("name", DbType.String)); cmd.Parameters.Add(new SQLiteParameter("lorem", DbType.String)); foreach (var doc in Helper.GetDocs(_count)) { cmd.Parameters["id"].Value = doc["_id"].AsInt32; cmd.Parameters["name"].Value = doc["name"].AsString; cmd.Parameters["lorem"].Value = doc["lorem"].AsString; cmd.ExecuteNonQuery(); } trans.Commit(); } } public void Bulk() { using (var trans = _db.BeginTransaction()) { var cmd = new SQLiteCommand("INSERT INTO col_bulk (id, name, lorem) VALUES (@id, @name, @lorem)", _db); cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("name", DbType.String)); cmd.Parameters.Add(new SQLiteParameter("lorem", DbType.String)); foreach (var doc in Helper.GetDocs(_count)) { cmd.Parameters["id"].Value = doc["_id"].AsInt32; cmd.Parameters["name"].Value = doc["name"].AsString; cmd.Parameters["lorem"].Value = doc["lorem"].AsString; cmd.ExecuteNonQuery(); } trans.Commit(); } } public void Update() { var cmd = new SQLiteCommand("UPDATE col SET name = @name, lorem = @lorem WHERE id = @id", _db); cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); cmd.Parameters.Add(new SQLiteParameter("name", DbType.String)); cmd.Parameters.Add(new SQLiteParameter("lorem", DbType.String)); foreach (var doc in Helper.GetDocs(_count)) { cmd.Parameters["id"].Value = doc["_id"].AsInt32; cmd.Parameters["name"].Value = doc["name"].AsString; cmd.Parameters["lorem"].Value = doc["lorem"].AsString; cmd.ExecuteNonQuery(); } } public void CreateIndex() { var cmd = new SQLiteCommand("CREATE INDEX idx1 ON col (name)", _db); cmd.ExecuteNonQuery(); } public void Query() { var cmd = new SQLiteCommand("SELECT * FROM col WHERE id = @id", _db); cmd.Parameters.Add(new SQLiteParameter("id", DbType.Int32)); for (var i = 0; i < _count; i++) { cmd.Parameters["id"].Value = i; var r = cmd.ExecuteReader(); r.Read(); var name = r.GetString(1); var lorem = r.GetString(2); r.Close(); } } public void Delete() { var cmd = new SQLiteCommand("DELETE FROM col", _db); cmd.ExecuteNonQuery(); } public void Drop() { var cmd = new SQLiteCommand("DROP TABLE col_bulk", _db); cmd.ExecuteNonQuery(); } public void Dispose() { _db.Dispose(); } } } ================================================ FILE: sandbox/PerfTest2/Generated/DatabaseBuilder.cs ================================================ // using LiteDB; using MasterMemory; using MessagePack; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System; using TestPerfLiteDB; using TestPerfLiteDB.Tables; namespace TestPerfLiteDB { public sealed class DatabaseBuilder : DatabaseBuilderBase { public DatabaseBuilder() : this(null) { } public DatabaseBuilder(MessagePack.IFormatterResolver resolver) : base(resolver) { } public DatabaseBuilder Append(System.Collections.Generic.IEnumerable dataSource) { AppendCore(dataSource, x => x.id, System.Collections.Generic.Comparer.Default); return this; } } } ================================================ FILE: sandbox/PerfTest2/Generated/ImmutableBuilder.cs ================================================ // using LiteDB; using MasterMemory; using MessagePack; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System; using TestPerfLiteDB; using TestPerfLiteDB.Tables; namespace TestPerfLiteDB { public sealed class ImmutableBuilder : ImmutableBuilderBase { MemoryDatabase memory; public ImmutableBuilder(MemoryDatabase memory) { this.memory = memory; } public MemoryDatabase Build() { return memory; } public void ReplaceAll(System.Collections.Generic.IList data) { var newData = CloneAndSortBy(data, x => x.id, System.Collections.Generic.Comparer.Default); var table = new TestDocTable(newData); memory = new MemoryDatabase( table ); } public void RemoveTestDoc(int[] keys) { var data = RemoveCore(memory.TestDocTable.GetRawDataUnsafe(), keys, x => x.id, System.Collections.Generic.Comparer.Default); var newData = CloneAndSortBy(data, x => x.id, System.Collections.Generic.Comparer.Default); var table = new TestDocTable(newData); memory = new MemoryDatabase( table ); } public void Diff(TestDoc[] addOrReplaceData) { var data = DiffCore(memory.TestDocTable.GetRawDataUnsafe(), addOrReplaceData, x => x.id, System.Collections.Generic.Comparer.Default); var newData = CloneAndSortBy(data, x => x.id, System.Collections.Generic.Comparer.Default); var table = new TestDocTable(newData); memory = new MemoryDatabase( table ); } } } ================================================ FILE: sandbox/PerfTest2/Generated/MasterMemoryResolver.cs ================================================ // using LiteDB; using MasterMemory; using MessagePack; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System; using TestPerfLiteDB; using TestPerfLiteDB.Tables; namespace TestPerfLiteDB { public class MasterMemoryResolver : global::MessagePack.IFormatterResolver { public static readonly global::MessagePack.IFormatterResolver Instance = new MasterMemoryResolver(); MasterMemoryResolver() { } public global::MessagePack.Formatters.IMessagePackFormatter GetFormatter() { return FormatterCache.formatter; } static class FormatterCache { public static readonly global::MessagePack.Formatters.IMessagePackFormatter formatter; static FormatterCache() { var f = MasterMemoryResolverGetFormatterHelper.GetFormatter(typeof(T)); if (f != null) { formatter = (global::MessagePack.Formatters.IMessagePackFormatter)f; } } } } internal static class MasterMemoryResolverGetFormatterHelper { static readonly global::System.Collections.Generic.Dictionary lookup; static MasterMemoryResolverGetFormatterHelper() { lookup = new global::System.Collections.Generic.Dictionary(1) { {typeof(TestDoc[]), 0 }, }; } internal static object GetFormatter(Type t) { int key; if (!lookup.TryGetValue(t, out key)) return null; switch (key) { case 0: return new MessagePack.Formatters.ArrayFormatter(); default: return null; } } } } ================================================ FILE: sandbox/PerfTest2/Generated/MemoryDatabase.cs ================================================ // using LiteDB; using MasterMemory; using MessagePack; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System; using TestPerfLiteDB; using TestPerfLiteDB.Tables; namespace TestPerfLiteDB { public sealed class MemoryDatabase : MemoryDatabaseBase { public TestDocTable TestDocTable { get; private set; } public MemoryDatabase( TestDocTable TestDocTable ) { this.TestDocTable = TestDocTable; } public MemoryDatabase(byte[] databaseBinary, bool internString = true, MessagePack.IFormatterResolver formatterResolver = null, int maxDegreeOfParallelism = 1) : base(databaseBinary, internString, formatterResolver, maxDegreeOfParallelism) { } protected override void Init(Dictionary header, System.ReadOnlyMemory databaseBinary, MessagePack.MessagePackSerializerOptions options, int maxDegreeOfParallelism) { this.TestDocTable = ExtractTableData(header, databaseBinary, options, xs => new TestDocTable(xs)); } public ImmutableBuilder ToImmutableBuilder() { return new ImmutableBuilder(this); } public DatabaseBuilder ToDatabaseBuilder() { var builder = new DatabaseBuilder(); builder.Append(this.TestDocTable.GetRawDataUnsafe()); return builder; } } } ================================================ FILE: sandbox/PerfTest2/Generated/Tables/TestDocTable.cs ================================================ // using LiteDB; using MasterMemory; using MessagePack; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using System; using TestPerfLiteDB; namespace TestPerfLiteDB.Tables { public sealed partial class TestDocTable : TableBase { readonly Func primaryIndexSelector; public TestDocTable(TestDoc[] sortedData) : base(sortedData) { this.primaryIndexSelector = x => x.id; } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public TestDoc FindByid(int key) { var lo = 0; var hi = data.Length - 1; while (lo <= hi) { var mid = (int)(((uint)hi + (uint)lo) >> 1); var selected = data[mid].id; var found = (selected < key) ? -1 : (selected > key) ? 1 : 0; if (found == 0) { return data[mid]; } if (found < 0) { lo = mid + 1; } else { hi = mid - 1; } } return default; } public TestDoc FindClosestByid(int key, bool selectLower = true) { return FindUniqueClosestCore(data, primaryIndexSelector, System.Collections.Generic.Comparer.Default, key, selectLower); } public RangeView FindRangeByid(int min, int max, bool ascendant = true) { return FindUniqueRangeCore(data, primaryIndexSelector, System.Collections.Generic.Comparer.Default, min, max, ascendant); } } } ================================================ FILE: sandbox/PerfTest2/PerfTest2.csproj ================================================ Exe net9.0 1701;1702;NU1904 Analyzer false ================================================ FILE: sandbox/PerfTest2/Program.cs ================================================ #pragma warning disable using System; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using LiteDB; using System.IO; // based test code -> https://github.com/mbdavid/LiteDB-Perf namespace TestPerfLiteDB { class Program { static void Main(string[] args) { RunTest("LiteDB: default", new LiteDB_Test(5000, null, new LiteDB.FileOptions { Journal = true, FileMode = LiteDB.FileMode.Shared })); RunTest("LiteDB: encrypted", new LiteDB_Test(5000, "mypass", new LiteDB.FileOptions { Journal = true, FileMode = LiteDB.FileMode.Shared })); RunTest("LiteDB: exclusive no journal", new LiteDB_Test(5000, null, new LiteDB.FileOptions { Journal = false, FileMode = LiteDB.FileMode.Exclusive })); RunTest("LiteDB: in-memory", new LiteDB_Test(5000)); RunTest("SQLite: default", new SQLite_Test(5000, null, true)); //RunTest("SQLite: encrypted", new SQLite_Test(5000, "mypass", true)); //RunTest("SQLite: no journal", new SQLite_Test(5000, null, false)); RunTest("SQLite: in-memory", new SQLite_Test(5000, null, false, true)); RunTest("Dictionary", new Dictionary_Test(5000)); RunTest("ConcurrentDictionary", new ConcurrentDictionary_Test(5000)); RunTest("ImmutableDictionary", new ImmutableDictionary_Test(5000)); RunTest("MasterMemory", new MasterMemory_Test(5000)); Console.ReadKey(); // RunTest("RavenDB: in-memory", new RavenDB_Test(5000, true)); } static void RunTest(string name, ITest test) { var title = name + " - " + test.Count + " records"; Console.WriteLine(title); Console.WriteLine("=".PadLeft(title.Length, '=')); test.Prepare(); test.Run("Insert", test.Insert, true); test.Run("Bulk", test.Bulk, true); test.Run("CreateIndex", test.CreateIndex, true); test.Run("Query", test.Query, false); test.Run("Query", test.Query, false); test.Run("Query", test.Query, false); test.Run("Query", test.Query, false); try { Console.WriteLine("FileLength : " + Math.Round((double)test.FileLength / (double)1024, 2).ToString().PadLeft(5, ' ') + " kb"); } catch (System.IO.FileNotFoundException) { } test.Dispose(); Console.WriteLine(); } } } ================================================ FILE: sandbox/PerfTest2/Utils/Helper.cs ================================================ #pragma warning disable using System; using System.Collections.Generic; using System.Data.SQLite; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; using LiteDB; using MasterMemory; using MessagePack; namespace TestPerfLiteDB { [MemoryTable("TestDoc"), MessagePackObject(true)] public class TestDoc { [PrimaryKey] public int id { get; set; } public string name { get; set; } public string lorem { get; set; } } static class Helper { public static void Run(this ITest test, string name, Action action, bool dryrun) { var sw = new Stopwatch(); System.GC.Collect(2, GCCollectionMode.Forced, blocking: true); sw.Start(); action(); sw.Stop(); var time = sw.ElapsedMilliseconds.ToString().PadLeft(5, ' '); var seg = Math.Round(test.Count / sw.Elapsed.TotalSeconds).ToString().PadLeft(8, ' '); if (!dryrun) { Console.WriteLine(name.PadRight(15, ' ') + ": " + time + " ms - " + seg + " records/second"); } } public static IEnumerable GetDocs(int count) { for (var i = 0; i < count; i++) { yield return new BsonDocument { { "_id", i }, { "name", Guid.NewGuid().ToString() }, { "lorem", LoremIpsum(3, 5, 2, 3, 3) } }; } } public static string LoremIpsum(int minWords, int maxWords, int minSentences, int maxSentences, int numParagraphs) { var words = new[] { "lorem", "ipsum", "dolor", "sit", "amet", "consectetuer", "adipiscing", "elit", "sed", "diam", "nonummy", "nibh", "euismod", "tincidunt", "ut", "laoreet", "dolore", "magna", "aliquam", "erat" }; var rand = new Random(DateTime.Now.Millisecond); var numSentences = rand.Next(maxSentences - minSentences) + minSentences + 1; var numWords = rand.Next(maxWords - minWords) + minWords + 1; var result = new StringBuilder(); for (int p = 0; p < numParagraphs; p++) { for (int s = 0; s < numSentences; s++) { for (int w = 0; w < numWords; w++) { if (w > 0) { result.Append(" "); } result.Append(words[rand.Next(words.Length)]); } result.Append(". "); } result.AppendLine(); } return result.ToString(); } } } ================================================ FILE: src/MasterMemory/DatabaseBuilderBase.cs ================================================ using MasterMemory.Internal; using MessagePack; using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.InteropServices; namespace MasterMemory { public abstract class DatabaseBuilderBase { readonly ByteBufferWriter bufferWriter = new ByteBufferWriter(); // TableName, (Offset, Count) readonly Dictionary header = new Dictionary(); readonly MessagePackSerializerOptions? options; public DatabaseBuilderBase(MessagePackSerializerOptions? options) { // options keep null to lazily use default options if (options != null) { options = options.WithCompression(MessagePackCompression.Lz4Block); } } public DatabaseBuilderBase(IFormatterResolver? resolver) { if (resolver != null) { this.options = MessagePackSerializer.DefaultOptions .WithCompression(MessagePackCompression.Lz4Block) .WithResolver(resolver); } } protected void AppendCore(IEnumerable datasource, Func indexSelector, IComparer comparer) { var tableName = typeof(T).GetCustomAttribute(); if (tableName == null) throw new InvalidOperationException("Type is not annotated MemoryTableAttribute. Type:" + typeof(T).FullName); if (header.ContainsKey(tableName.TableName)) { throw new InvalidOperationException("TableName is already appended in builder. TableName: " + tableName.TableName + " Type:" + typeof(T).FullName); } if (datasource == null) return; // sort(as indexed data-table) var source = FastSort(datasource, indexSelector, comparer); // write data and store header-data. var useOption = options ?? MessagePackSerializer.DefaultOptions.WithCompression(MessagePackCompression.Lz4Block); var offset = bufferWriter.CurrentOffset; MessagePackSerializer.Serialize(bufferWriter, source, useOption); header.Add(tableName.TableName, (offset, bufferWriter.CurrentOffset - offset)); } static TElement[] FastSort(IEnumerable datasource, Func indexSelector, IComparer comparer) { var collection = datasource as ICollection; if (collection != null) { var array = new TElement[collection.Count]; var sortSource = new TKey[collection.Count]; var i = 0; foreach (var item in collection) { array[i] = item; sortSource[i] = indexSelector(item); i++; } Array.Sort(sortSource, array, 0, collection.Count, comparer); return array; } else { var array = new ExpandableArray(null!); var sortSource = new ExpandableArray(null!); foreach (var item in datasource) { array.Add(item); sortSource.Add(indexSelector(item)); } Array.Sort(sortSource.items, array.items, 0, array.count, comparer); Array.Resize(ref array.items, array.count); return array.items; } } public byte[] Build() { using (var ms = new MemoryStream()) { WriteToStream(ms); return ms.ToArray(); } } public void WriteToStream(Stream stream) { MessagePackSerializer.Serialize(stream, header, HeaderFormatterResolver.StandardOptions); MemoryMarshal.TryGetArray(bufferWriter.WrittenMemory, out var segment); stream.Write(segment.Array, segment.Offset, segment.Count); } } } ================================================ FILE: src/MasterMemory/DatabaseBuilderBaseExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Linq; namespace MasterMemory { public static class DatabaseBuilderExtensions { public static void AppendDynamic(this DatabaseBuilderBase builder, Type dataType, IList tableData) { var appendMethod = builder.GetType().GetMethods() .Where(x => x.Name == "Append") .Where(x => x.GetParameters()[0].ParameterType.GetGenericArguments()[0] == dataType) .FirstOrDefault(); if (appendMethod == null) { throw new InvalidOperationException("Append(IEnumerable) can not found. DataType:" + dataType); } var dynamicArray = Array.CreateInstance(dataType, tableData.Count); for (int i = 0; i < tableData.Count; i++) { dynamicArray.SetValue(Convert.ChangeType(tableData[i], dataType), i); } appendMethod.Invoke(builder, new object[] { dynamicArray }); } } } ================================================ FILE: src/MasterMemory/IValidatable.cs ================================================ using MasterMemory.Validation; using System; using System.Linq.Expressions; namespace MasterMemory { public interface IValidatable { void Validate(IValidator validator); } public interface IValidator { ValidatableSet GetTableSet(); ReferenceSet GetReferenceSet(); void Validate(Expression> predicate); void Validate(Func predicate, string message); void ValidateAction(Expression> predicate); void ValidateAction(Func predicate, string message); void Fail(string message); bool CallOnce(); } } ================================================ FILE: src/MasterMemory/ImmutableBuilderBase.cs ================================================ using MasterMemory.Internal; using System; using System.Collections.Generic; namespace MasterMemory { public abstract class ImmutableBuilderBase { static protected TElement[] CloneAndSortBy(IList elementData, Func indexSelector, IComparer comparer) { var array = new TElement[elementData.Count]; var sortSource = new TKey[elementData.Count]; for (int i = 0; i < elementData.Count; i++) { array[i] = elementData[i]; sortSource[i] = indexSelector(elementData[i]); } Array.Sort(sortSource, array, 0, array.Length, comparer); return array; } static protected List RemoveCore(TElement[] array, TKey[] keys, Func keySelector, IComparer comparer) { var removeIndexes = new HashSet(); foreach (var key in keys) { var index = BinarySearch.FindFirst(array, key, keySelector, comparer); if (index != -1) { removeIndexes.Add(index); } } var newList = new List(array.Length - removeIndexes.Count); for (int i = 0; i < array.Length; i++) { if (!removeIndexes.Contains(i)) { newList.Add(array[i]); } } return newList; } static protected List DiffCore(TElement[] array, TElement[] addOrReplaceData, Func keySelector, IComparer comparer) { var newList = new List(array.Length); var replaceIndexes = new Dictionary(); foreach (var data in addOrReplaceData) { var index = BinarySearch.FindFirst(array, keySelector(data), keySelector, comparer); if (index != -1) { replaceIndexes.Add(index, data); } else { newList.Add(data); } } for (int i = 0; i < array.Length; i++) { if (replaceIndexes.TryGetValue(i, out var data)) { newList.Add(data); } else { newList.Add(array[i]); } } return newList; } } } ================================================ FILE: src/MasterMemory/Internal/BinarySearch.cs ================================================ using System; using System.Collections.Generic; namespace MasterMemory.Internal { internal static class BinarySearch { public static int FindFirst(T[] array, TKey key, Func selector, IComparer comparer) { var lo = 0; var hi = array.Length - 1; while (lo <= hi) { var mid = (int)(((uint)hi + (uint)lo) >> 1); var found = comparer.Compare(selector(array[mid]), key); if (found == 0) return mid; if (found < 0) { lo = mid + 1; } else { hi = mid - 1; } } return -1; } public static int FindFirstIntKey(T[] array, int key, Func selector) { var lo = 0; var hi = array.Length - 1; while (lo <= hi) { var mid = (int)(((uint)hi + (uint)lo) >> 1); // compare inlining var selectedValue = selector(array[mid]); var found = (selectedValue < key) ? -1 : (selectedValue > key) ? 1 : 0; if (found == 0) return mid; if (found < 0) { lo = mid + 1; } else { hi = mid - 1; } } return -1; } // lo = 0, hi = Count. public static int FindClosest(T[] array, int lo, int hi, TKey key, Func selector, IComparer comparer, bool selectLower) { if (array.Length == 0) return -1; lo = lo - 1; while (hi - lo > 1) { var mid = lo + ((hi - lo) >> 1); var found = comparer.Compare(selector(array[mid]), key); if (found == 0) { lo = hi = mid; break; } if (found >= 1) { hi = mid; } else { lo = mid; } } return selectLower ? lo : hi; } // default lo = 0, hi = array.Count public static int LowerBound(T[] array, int lo, int hi, TKey key, Func selector, IComparer comparer) { while (lo < hi) { var mid = lo + ((hi - lo) >> 1); var found = comparer.Compare(key, selector(array[mid])); if (found <= 0) { hi = mid; } else { lo = mid + 1; } } var index = lo; if (index == -1 || array.Length <= index) { return -1; } // check final return (comparer.Compare(key, selector(array[index])) == 0) ? index : -1; } public static int UpperBound(T[] array, int lo, int hi, TKey key, Func selector, IComparer comparer) { while (lo < hi) { var mid = lo + ((hi - lo) >> 1); var found = comparer.Compare(key, selector(array[mid])); if (found >= 0) { lo = mid + 1; } else { hi = mid; } } var index = (lo == 0) ? 0 : lo - 1; if (index == -1 || array.Length <= index) { return -1; } // check final return (comparer.Compare(key, selector(array[index])) == 0) ? index : -1; } //... want the lowest index of Key <= Value //... returns 0 if key is <= all values in array //... returns array.Length if key is > all values in array public static int LowerBoundClosest(T[] array, int lo, int hi, TKey key, Func selector, IComparer comparer) { while (lo < hi) { var mid = lo + ((hi - lo) >> 1); var found = comparer.Compare(key, selector(array[mid])); if (found <= 0) //... Key is <= value at mid { hi = mid; } else { lo = mid + 1; //... Notice that lo starts at zero and can only increase } } var index = lo; //... index will always be zero or greater if ( array.Length <= index) { return array.Length; } // check final return (comparer.Compare(key, selector(array[index])) <= 0) ? index : -1; } //... want the highest index of Key >= Value //... returns -1 if key is < than all values in array //... returns array.Length - 1 if key is >= than all values in array public static int UpperBoundClosest(T[] array, int lo, int hi, TKey key, Func selector, IComparer comparer) { while (lo < hi) { var mid = lo + ((hi - lo) >> 1); var found = comparer.Compare(key, selector(array[mid])); if (found >= 0) //... Key >= value at mid { lo = mid + 1; //... Note lo starts at zero and can only increase } else { hi = mid; } } var index = (lo == 0) ? 0 : lo - 1; //... index will always be zero or greater if ( index >= array.Length ) { return array.Length; } // check final return (comparer.Compare(key, selector(array[index])) >= 0) ? index : -1; } } } ================================================ FILE: src/MasterMemory/Internal/ByteBufferWriter.cs ================================================ using System; using System.Buffers; namespace MasterMemory { internal class ByteBufferWriter : IBufferWriter { byte[] buffer; int index; public int CurrentOffset => index; public ReadOnlySpan WrittenSpan => buffer.AsSpan(0, index); public ReadOnlyMemory WrittenMemory => new ReadOnlyMemory(buffer, 0, index); public ByteBufferWriter() { buffer = new byte[1024]; index = 0; } public void Advance(int count) { index += count; } public Memory GetMemory(int sizeHint = 0) { AGAIN: var nextSize = index + sizeHint; if (buffer.Length < nextSize) { Array.Resize(ref buffer, Math.Max(buffer.Length * 2, nextSize)); } if (sizeHint == 0) { var result = new Memory(buffer, index, buffer.Length - index); if (result.Length == 0) { sizeHint = 1024; goto AGAIN; } return result; } else { return new Memory(buffer, index, sizeHint); } } public Span GetSpan(int sizeHint = 0) { return GetMemory(sizeHint).Span; } } } ================================================ FILE: src/MasterMemory/Internal/ExpandableArray.cs ================================================ using System; namespace MasterMemory.Internal { internal struct ExpandableArray { internal TElement[] items; internal int count; public ExpandableArray(object dummy) { items = Array.Empty(); count = 0; } internal void Add(TElement item) { if (items == null || items.Length == 0) { items = new TElement[4]; } else if (items.Length == (count + 1)) { Array.Resize(ref items, checked(count * 2)); } items[count++] = item; } } } ================================================ FILE: src/MasterMemory/Internal/HeaderFormatterResolver.cs ================================================ using MessagePack; using MessagePack.Formatters; using System; using System.Collections.Generic; namespace MasterMemory.Internal { // for AOT(IL2CPP) concrete generic formatter. internal class HeaderFormatterResolver : IFormatterResolver { public static readonly IFormatterResolver Instance = new HeaderFormatterResolver(); public static readonly MessagePackSerializerOptions StandardOptions = MessagePackSerializerOptions.Standard.WithResolver(Instance); public IMessagePackFormatter? GetFormatter() { if (typeof(T) == typeof(Dictionary)) { return (IMessagePackFormatter)(object)new DictionaryFormatter(); } else if (typeof(T) == typeof(string)) { return (IMessagePackFormatter)(object)NullableStringFormatter.Instance; } else if (typeof(T) == typeof((int, int))) { return (IMessagePackFormatter)(object)new IntIntValueTupleFormatter(); } else if (typeof(T) == typeof(int)) { return (IMessagePackFormatter)(object)Int32Formatter.Instance; } return null; } } internal sealed class IntIntValueTupleFormatter : IMessagePackFormatter> { public void Serialize(ref MessagePackWriter writer, (int, int) value, MessagePackSerializerOptions options) { writer.WriteArrayHeader(2); writer.WriteInt32(value.Item1); writer.WriteInt32(value.Item2); } public (int, int) Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { if (reader.IsNil) { throw new InvalidOperationException("Data is Nil, ValueTuple can not be null."); } var count = reader.ReadArrayHeader(); if (count != 2) throw new InvalidOperationException("Invalid ValueTuple count"); var item1 = reader.ReadInt32(); var item2 = reader.ReadInt32(); return new ValueTuple(item1, item2); } } } ================================================ FILE: src/MasterMemory/Internal/InternStringResolver.cs ================================================ using MessagePack; using MessagePack.Formatters; using System; namespace MasterMemory.Internal { #pragma warning disable MsgPack013 // Inaccessible formatter internal class InternStringResolver : IFormatterResolver, IMessagePackFormatter { readonly IFormatterResolver innerResolver; public InternStringResolver(IFormatterResolver innerResolver) { this.innerResolver = innerResolver; } public IMessagePackFormatter? GetFormatter() { if (typeof(T) == typeof(string)) { return (IMessagePackFormatter)this; } return innerResolver.GetFormatter(); } string? IMessagePackFormatter.Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) { var str = reader.ReadString(); if (str == null) { return null; } return string.Intern(str); } void IMessagePackFormatter.Serialize(ref MessagePackWriter writer, string? value, MessagePackSerializerOptions options) { throw new NotImplementedException(); } } } ================================================ FILE: src/MasterMemory/MasterMemory.csproj ================================================  netstandard2.0 MasterMemory MasterMemory 13 enable Library False Cysharp true 1701;1702;1705;1591 MasterMemory Embedded Typed Readonly In-Memory Document Database for .NET Core and Unity. true $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs TextTemplatingFileGenerator ValidatableSet.Sequential.cs True True ValidatableSet.Sequential.tt ================================================ FILE: src/MasterMemory/MemoryDatabaseBase.cs ================================================ using MasterMemory.Internal; using MessagePack; using MessagePack.Formatters; using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using System.Buffers; using MasterMemory.Validation; namespace MasterMemory { public abstract class MemoryDatabaseBase { protected MemoryDatabaseBase() { } public MemoryDatabaseBase(byte[] databaseBinary, bool internString = true, IFormatterResolver? formatterResolver = null, int maxDegreeOfParallelism = 1) { var reader = new MessagePackReader(databaseBinary); var formatter = new DictionaryFormatter(); var header = formatter.Deserialize(ref reader, HeaderFormatterResolver.StandardOptions); var resolver = formatterResolver ?? MessagePackSerializer.DefaultOptions.Resolver; if (internString) { resolver = new InternStringResolver(resolver); } if (maxDegreeOfParallelism < 1) { maxDegreeOfParallelism = 1; } Init(header!, databaseBinary.AsMemory((int)reader.Consumed), MessagePackSerializer.DefaultOptions.WithResolver(resolver).WithCompression(MessagePackCompression.Lz4Block), maxDegreeOfParallelism); } protected static TView ExtractTableData(Dictionary header, ReadOnlyMemory databaseBinary, MessagePackSerializerOptions options, Func createView) { var tableName = typeof(T).GetCustomAttribute(); if (tableName == null) throw new InvalidOperationException("Type is not annotated MemoryTableAttribute. Type:" + typeof(T).FullName); if (header.TryGetValue(tableName.TableName, out var segment)) { var data = MessagePackSerializer.Deserialize(databaseBinary.Slice(segment.offset, segment.count), options); return createView(data); } else { // return empty var data = Array.Empty(); return createView(data); } } protected abstract void Init(Dictionary header, ReadOnlyMemory databaseBinary, MessagePackSerializerOptions options, int maxDegreeOfParallelism); public static TableInfo[] GetTableInfo(byte[] databaseBinary, bool storeTableData = true) { var formatter = new DictionaryFormatter(); var reader = new MessagePackReader(databaseBinary); var header = formatter.Deserialize(ref reader, HeaderFormatterResolver.StandardOptions); return header.Select(x => new TableInfo(x.Key, x.Value.Item2, storeTableData ? databaseBinary : null, x.Value.Item1)).ToArray(); } protected void ValidateTable(IReadOnlyList table, ValidationDatabase database, string pkName, Delegate pkSelector, ValidateResult result) { var onceCalled = new System.Runtime.CompilerServices.StrongBox(false); foreach (var item in table) { if (item is IValidatable validatable) { var validator = new Validator(database, item, result, onceCalled, pkName, pkSelector); validatable.Validate(validator); } } } } /// /// Diagnostic info of MasterMemory's table. /// public class TableInfo { public string TableName { get; } public int Size { get; } byte[]? binaryData; public TableInfo(string tableName, int size, byte[]? rawBinary, int offset) { TableName = tableName; Size = size; if (rawBinary != null) { this.binaryData = new byte[size]; Array.Copy(rawBinary, offset, binaryData, 0, size); } } public string DumpAsJson() { return DumpAsJson(MessagePackSerializer.DefaultOptions); } public string DumpAsJson(MessagePackSerializerOptions options) { if (binaryData == null) { throw new InvalidOperationException("DumpAsJson can only call from GetTableInfo(storeTableData = true)."); } return MessagePackSerializer.ConvertToJson(binaryData, options.WithCompression(MessagePackCompression.Lz4Block)); } } } ================================================ FILE: src/MasterMemory/Meta/Meta.cs ================================================ using System; using System.Linq; using System.Collections.Generic; using System.Reflection; using System.Text; namespace MasterMemory.Meta { public class MetaDatabase { IDictionary tableInfos; public MetaDatabase(IDictionary tableInfos) { this.tableInfos = tableInfos; } public int Count => tableInfos.Count; public IEnumerable GetTableInfos() { foreach (var item in tableInfos.Values) { yield return item; } } public MetaTable? GetTableInfo(string tableName) { return tableInfos.TryGetValue(tableName, out var table) ? table : null; } } public class MetaTable { public Type DataType { get; } public Type TableType { get; } public string TableName { get; } public IReadOnlyList Properties { get; } public IReadOnlyList Indexes { get; } public MetaTable(Type dataType, Type tableType, string tableName, IReadOnlyList properties, IReadOnlyList Indexes) { this.DataType = dataType; this.TableType = tableType; this.TableName = tableName; this.Properties = properties; this.Indexes = Indexes; } public override string ToString() { return TableName; } } public class MetaProperty { public PropertyInfo PropertyInfo { get; } public string Name => PropertyInfo.Name; public string NameLowerCamel => ToCamelCase(PropertyInfo.Name); public string NameSnakeCase => ToSnakeCase(PropertyInfo.Name); public MetaProperty(PropertyInfo? propertyInfo) { PropertyInfo = propertyInfo!; } public override string ToString() { return Name; } /// /// MyProperty -> myProperty /// static string ToCamelCase(string s) { if (string.IsNullOrEmpty(s) || char.IsLower(s, 0)) { return s; } var array = s.ToCharArray(); array[0] = char.ToLowerInvariant(array[0]); return new string(array); } /// /// MyProperty -> my_property /// static string ToSnakeCase(string s) { if (string.IsNullOrEmpty(s)) return s; var sb = new StringBuilder(); for (int i = 0; i < s.Length; i++) { var c = s[i]; if (Char.IsUpper(c)) { // first if (i == 0) { sb.Append(char.ToLowerInvariant(c)); } else if (char.IsUpper(s[i - 1])) // WriteIO => write_io { sb.Append(char.ToLowerInvariant(c)); } else { sb.Append("_"); sb.Append(char.ToLowerInvariant(c)); } } else { sb.Append(c); } } return sb.ToString(); } } public class MetaIndex { public IReadOnlyList IndexProperties { get; } public bool IsPrimaryIndex { get; } public bool IsUnique { get; } public System.Collections.IComparer Comparer { get; } public bool IsReturnRangeValue => IndexProperties.Count != 1; public MetaIndex(IReadOnlyList indexProperties, bool isPrimaryIndex, bool isUnique, System.Collections.IComparer comparer) { IndexProperties = indexProperties; IsPrimaryIndex = isPrimaryIndex; IsUnique = isUnique; Comparer = comparer; } public override string ToString() { return string.Join(", ", IndexProperties.Select(x => x.Name)); } } } ================================================ FILE: src/MasterMemory/RangeView.cs ================================================ using System; using System.Collections; using System.Collections.Generic; namespace MasterMemory { public readonly struct RangeView : IEnumerable, IReadOnlyList, IList { public static RangeView Empty => new RangeView(null, -1, -1, false); readonly T[]? orderedData; readonly int left; readonly int right; readonly bool ascendant; readonly bool hasValue; public int Count => (!hasValue) ? 0 : (right - left) + 1; public T First => this[0]; public T Last => this[Count - 1]; public RangeView Reverse => new RangeView(orderedData, left, right, !ascendant); internal int FirstIndex => ascendant ? left : right; internal int LastIndex => ascendant ? right : left; bool ICollection.IsReadOnly => true; public T this[int index] { get { if (!hasValue) throw new ArgumentOutOfRangeException("view is empty"); if (index < 0) throw new ArgumentOutOfRangeException("index < 0"); if (Count <= index) throw new ArgumentOutOfRangeException("count <= index"); if (ascendant) { return orderedData![left + index]; } else { return orderedData![right - index]; } } } public RangeView(T[]? orderedData, int left, int right, bool ascendant) { this.hasValue = (orderedData != null) && (orderedData.Length != 0) && (left <= right); // same index is length = 1 this.orderedData = orderedData; this.orderedData = orderedData; this.left = left; this.right = right; this.ascendant = ascendant; } public IEnumerator GetEnumerator() { var count = Count; for (int i = 0; i < count; i++) { yield return this[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } public bool Any() { return Count != 0; } public int IndexOf(T item) { var i = 0; foreach (var v in this) { if (EqualityComparer.Default.Equals(v, item)) { return i; } i++; } return -1; } public bool Contains(T item) { var count = Count; for (int i = 0; i < count; i++) { var v = this[i]; if (EqualityComparer.Default.Equals(v, item)) { return true; } } return false; } public void CopyTo(T[] array, int arrayIndex) { var count = Count; Array.Copy(orderedData, left, array, arrayIndex, count); if (!ascendant) { Array.Reverse(array, arrayIndex, count); } } T IList.this[int index] { get { return this[index]; } set { throw new NotImplementedException(); } } void IList.Insert(int index, T item) { throw new NotSupportedException(); } void IList.RemoveAt(int index) { throw new NotSupportedException(); } void ICollection.Add(T item) { throw new NotSupportedException(); } void ICollection.Clear() { throw new NotSupportedException(); } bool ICollection.Remove(T item) { throw new NotSupportedException(); } } } ================================================ FILE: src/MasterMemory/TableBase.cs ================================================ using MasterMemory.Internal; using MasterMemory.Validation; using System; using System.Collections.Generic; using System.Diagnostics; namespace MasterMemory { public abstract class TableBase { protected readonly TElement[] data; // Common Properties public int Count => data.Length; public RangeView All => new RangeView(data, 0, data.Length - 1, true); public RangeView AllReverse => new RangeView(data, 0, data.Length - 1, false); public TElement[] GetRawDataUnsafe() => data; public TableBase(TElement[] sortedData) { this.data = sortedData; } // Validate static protected void ValidateUniqueCore(TElement[] indexArray, Func keySelector, string message, ValidateResult resultSet) { var set = new HashSet(); foreach (var item in indexArray) { var v = keySelector(item); if (!set.Add(v)) { resultSet.AddFail(typeof(TElement), "Unique failed: " + message + ", value = " + v, item!); } } } // Util protected TElement[] CloneAndSortBy(Func indexSelector, IComparer comparer) { var array = new TElement[data.Length]; var sortSource = new TKey[data.Length]; for (int i = 0; i < data.Length; i++) { array[i] = data[i]; sortSource[i] = indexSelector(data[i]); } Array.Sort(sortSource, array, 0, array.Length, comparer); return array; } static protected TElement ThrowKeyNotFound(TKey key) { throw new KeyNotFoundException("DataType: " + typeof(TElement).FullName + ", Key: " + key?.ToString()); } // Unique static protected TElement FindUniqueCore(TElement[] indexArray, Func keySelector, IComparer comparer, TKey key, bool throwIfNotFound = true) { var index = BinarySearch.FindFirst(indexArray, key, keySelector, comparer); if (index != -1) { return indexArray[index]; } else { if (throwIfNotFound) { ThrowKeyNotFound(key); } return default!; } } // Optimize for IntKey static protected TElement FindUniqueCoreInt(TElement[] indexArray, Func keySelector, IComparer _, int key, bool throwIfNotFound = true) { var index = BinarySearch.FindFirstIntKey(indexArray, key, keySelector); if (index != -1) { return indexArray[index]; } else { if (throwIfNotFound) { ThrowKeyNotFound(key); } return default!; } } static protected bool TryFindUniqueCore(TElement[] indexArray, Func keySelector, IComparer comparer, TKey key, out TElement result) { var index = BinarySearch.FindFirst(indexArray, key, keySelector, comparer); if (index != -1) { result = indexArray[index]; return true; } else { result = default!; return false; } } static protected bool TryFindUniqueCoreInt(TElement[] indexArray, Func keySelector, IComparer _, int key, out TElement result) { var index = BinarySearch.FindFirstIntKey(indexArray, key, keySelector); if (index != -1) { result = indexArray[index]; return true; } else { result = default!; return false; } } static protected TElement? FindUniqueClosestCore(TElement[] indexArray, Func keySelector, IComparer comparer, TKey key, bool selectLower) { var index = BinarySearch.FindClosest(indexArray, 0, indexArray.Length, key, keySelector, comparer, selectLower); return (index != -1) ? indexArray[index] : default(TElement); } static protected RangeView FindUniqueRangeCore(TElement[] indexArray, Func keySelector, IComparer comparer, TKey min, TKey max, bool ascendant) { var lo = BinarySearch.FindClosest(indexArray, 0, indexArray.Length, min, keySelector, comparer, false); var hi = BinarySearch.FindClosest(indexArray, 0, indexArray.Length, max, keySelector, comparer, true); if ( lo == -1 ) lo = 0; if ( hi == indexArray.Length ) hi -= 1; return new RangeView(indexArray, lo, hi, ascendant); } // Many static protected RangeView FindManyCore(TElement[] indexKeys, Func keySelector, IComparer comparer, TKey key) { var lo = BinarySearch.LowerBound(indexKeys, 0, indexKeys.Length, key, keySelector, comparer); if (lo == -1) return RangeView.Empty; var hi = BinarySearch.UpperBound(indexKeys, 0, indexKeys.Length, key, keySelector, comparer); if (hi == -1) return RangeView.Empty; return new RangeView(indexKeys, lo, hi, true); } static protected RangeView FindManyClosestCore(TElement[] indexArray, Func keySelector, IComparer comparer, TKey key, bool selectLower) { var closest = BinarySearch.FindClosest(indexArray, 0, indexArray.Length, key, keySelector, comparer, selectLower); if ((closest == -1) || ( closest >= indexArray.Length )) return RangeView.Empty; return FindManyCore(indexArray, keySelector, comparer, keySelector(indexArray[closest])); } static protected RangeView FindManyRangeCore(TElement[] indexArray, Func keySelector, IComparer comparer, TKey min, TKey max, bool ascendant) { //... Empty set when min > max //... Alternatively, could treat this as between and swap min and max. if ( Comparer.Default.Compare( min, max ) > 0 ) return RangeView.Empty; //... want lo to be the lowest index of the values >= than min. //... lo should be in the range [0,arraylength] var lo = BinarySearch.LowerBoundClosest(indexArray, 0, indexArray.Length, min, keySelector, comparer ); //... want hi to be the highest index of the values <= than max //... hi should be in the range [-1,arraylength-1] var hi = BinarySearch.UpperBoundClosest(indexArray, 0, indexArray.Length, max, keySelector, comparer ); Debug.Assert( lo >= 0 ); Debug.Assert( hi < indexArray.Length ); if ( hi < lo ) return RangeView.Empty; return new RangeView(indexArray, lo, hi, ascendant); } } } ================================================ FILE: src/MasterMemory/Validation/ExpressionDumper.cs ================================================ using System; using System.Linq; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace MasterMemory.Validation { internal class ExpressionDumper : ExpressionVisitor { ParameterExpression param; T target; public Dictionary Members { get; private set; } public ExpressionDumper(T target, ParameterExpression param) { this.target = target; this.param = param; this.Members = new Dictionary(); } protected override System.Linq.Expressions.Expression VisitMember(MemberExpression node) { if (node.Expression == param && !Members.ContainsKey(node.Member.Name)) { var accessor = new ReflectAccessor(target, node.Member.Name); Members.Add(node.Member.Name, accessor.GetValue()); } return base.VisitMember(node); } public static string DumpMemberValues(T item, Expression> predicate) { var dumper = new ExpressionDumper(item, predicate.Parameters.Single()); return dumper.VisitAndFormat(predicate); } public string VisitAndFormat(Expression expression) { Visit(expression); return string.Join(", ", Members.Select(kvp => kvp.Key + " = " + kvp.Value)); } private class ReflectAccessor { public Func GetValue { get; private set; } public Action SetValue { get; private set; } public ReflectAccessor(T target, string name) { var field = typeof(T).GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { GetValue = () => field.GetValue(target); SetValue = value => field.SetValue(target, value); return; } var prop = typeof(T).GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (prop != null) { GetValue = () => prop.GetValue(target, null); SetValue = value => prop.SetValue(target, value, null); return; } throw new ArgumentException(string.Format("\"{0}\" not found : Type <{1}>", name, typeof(T).Name)); } } } } ================================================ FILE: src/MasterMemory/Validation/ExpressionParameterNameModifier.cs ================================================ using System; using System.Linq.Expressions; namespace MasterMemory.Validation { public class ExpressionParameterNameModifier : ExpressionVisitor { readonly ParameterExpression modifyTarget; readonly ParameterExpression replaceExpression; public ExpressionParameterNameModifier(ParameterExpression modifyTarget, ParameterExpression replaceExpression) { this.modifyTarget = modifyTarget; this.replaceExpression = replaceExpression; } protected override Expression VisitParameter(ParameterExpression node) { if (node == modifyTarget) { return replaceExpression; } return base.VisitParameter(node); } } public static class ExpressionParameterNameModifyExtensions { public static string ToThisBodyString(this Expression> predicate) { var newNameParameter = Expression.Parameter(typeof(T), "this"); var newExpression = new ExpressionParameterNameModifier(predicate.Parameters[0], newNameParameter).Visit(predicate); return (newExpression as Expression>)!.Body.ToString(); } public static string ToSpaceBodyString(this Expression> selector) { var newNameParameter = Expression.Parameter(typeof(T), " "); var newExpression = new ExpressionParameterNameModifier(selector.Parameters[0], newNameParameter).Visit(selector); return (newExpression as Expression>)!.Body.ToString(); } public static string ToNameBodyString(this Expression> selector, string newName) { var newNameParameter = Expression.Parameter(typeof(T), newName); var newExpression = new ExpressionParameterNameModifier(selector.Parameters[0], newNameParameter).Visit(selector); return (newExpression as Expression>)!.Body.ToString(); } } } ================================================ FILE: src/MasterMemory/Validation/ITableUniqueValidate.cs ================================================ namespace MasterMemory.Validation { public interface ITableUniqueValidate { void ValidateUnique(ValidateResult resultSet); } } ================================================ FILE: src/MasterMemory/Validation/ReferenceSet.cs ================================================ using System; using System.Linq; using System.Collections.Generic; using System.Linq.Expressions; namespace MasterMemory.Validation { public class ReferenceSet { readonly TElement item; readonly IReadOnlyList referenceTable; readonly ValidateResult resultSet; readonly string pkName; readonly Delegate pkSelector; public IReadOnlyList TableData => referenceTable; public ReferenceSet(TElement item, IReadOnlyList referenceTable, ValidateResult resultSet, string pkName, Delegate pkSelector) { this.item = item; this.referenceTable = referenceTable; this.resultSet = resultSet; this.pkName = pkName; this.pkSelector = pkSelector; } public void Exists(Expression> elementSelector, Expression> referenceElementSelector) { Exists(elementSelector, referenceElementSelector, EqualityComparer.Default); } public void Exists(Expression> elementSelector, Expression> referenceElementSelector, EqualityComparer equalityComparer) { var f1 = elementSelector.Compile(true); var f2 = referenceElementSelector.Compile(true); var compareBase = f1(item); foreach (var refItem in referenceTable) { if (equalityComparer.Equals(compareBase, f2(refItem))) { return; } } // not found, assert. var from = elementSelector.ToNameBodyString(typeof(TElement).Name); var to = referenceElementSelector.ToNameBodyString(typeof(TReference).Name); resultSet.AddFail(typeof(TElement), "Exists failed: " + from + " -> " + to + ", value = " + compareBase + ", " + BuildPkMessage(), item!); } string BuildPkMessage() { var pk = pkSelector.DynamicInvoke(item).ToString(); return $"PK({pkName}) = {pk}"; } } } ================================================ FILE: src/MasterMemory/Validation/ValidatableSet.Sequential.cs ================================================  using System; using System.Linq; using System.Linq.Expressions; namespace MasterMemory.Validation { public partial class ValidatableSet { public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } public void Sequential(Expression> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]!); } prev = curr; } } } } ================================================ FILE: src/MasterMemory/Validation/ValidatableSet.Sequential.tt ================================================ <#@ template debug="true" hostSpecific="false" #> <#@ output extension=".cs" #> <#@ Assembly Name="System.Core" #> <#@ import namespace="System" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Diagnostics" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Collections" #> <#@ import namespace="System.Collections.Generic" #> <# var targetTypes = new[] { typeof(sbyte), typeof(short), typeof(int), typeof(long), typeof(byte), typeof(ushort), typeof(uint), typeof(ulong), }; #> using System; using System.Linq; using System.Linq.Expressions; namespace MasterMemory.Validation { public partial class ValidatableSet { <# foreach(var t in targetTypes) { #> public void Sequential(Expression>> selector, bool distinct = false) { var f = selector.Compile(true); SequentialCore(f, () => selector.ToSpaceBodyString(), distinct); } public void Sequential(Func> selector, string message, bool distinct = false) { SequentialCore(selector, () => " " + message, distinct); } void SequentialCore(Func> selector, Func message, bool distinct) { if (tableData.Count == 0) return; var data = tableData.OrderBy(selector).ToArray(); var prev = selector(data[0]); for (int i = 1; i < data.Length; i++) { var curr = selector(data[i]); if (distinct) { if (prev == curr) continue; } if ((prev + 1) != curr) { resultSet.AddFail(typeof(TElement), "Sequential failed:" + message() + ", value = " + (prev, curr) + ", " + BuildPkMessage(data[i]), data[i]); } prev = curr; } } <# } #> } } ================================================ FILE: src/MasterMemory/Validation/ValidatableSet.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace MasterMemory.Validation { public partial class ValidatableSet { readonly IReadOnlyList tableData; readonly ValidateResult resultSet; readonly string pkName; readonly Delegate pkSelector; public ValidatableSet(IReadOnlyList tableData, ValidateResult resultSet, string pkName, Delegate pkSelector) { this.tableData = tableData; this.resultSet = resultSet; this.pkName = pkName; this.pkSelector = pkSelector; } public IReadOnlyList TableData => tableData; public void Unique(Expression> selector) { Unique(selector, EqualityComparer.Default); } public void Unique(Expression> selector, IEqualityComparer equalityComparer) { var f = selector.Compile(true); var set = new HashSet(equalityComparer); foreach (var item in tableData) { var v = f(item); if (!set.Add(v)) { resultSet.AddFail(typeof(TElement), "Unique failed:" + selector.ToSpaceBodyString() + ", value = " + v + ", " + BuildPkMessage(item), item!); } } } public void Unique(Func selector, string message) { Unique(selector, EqualityComparer.Default, message); } public void Unique(Func selector, IEqualityComparer equalityComparer, string message) { var set = new HashSet(equalityComparer); foreach (var item in tableData) { var v = selector(item); if (!set.Add(v)) { resultSet.AddFail(typeof(TElement), "Unique failed: " + message + ", value = " + v + ", " + BuildPkMessage(item), item!); } } } public ValidatableSet Where(Func predicate) { return new ValidatableSet(tableData.Where(predicate).ToArray(), resultSet, pkName, pkSelector); } string BuildPkMessage(TElement item) { var pk = pkSelector.DynamicInvoke(item).ToString(); return $"PK({pkName}) = {pk}"; } } } ================================================ FILE: src/MasterMemory/Validation/ValidateResult.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace MasterMemory.Validation { public class ValidateResult { List result = new List(); public bool IsValidationFailed => result.Count != 0; public IReadOnlyList FailedResults => result; public string FormatFailedResults() { var sb = new StringBuilder(); foreach (var item in result) { sb.AppendLine(item.Type.FullName + " - " + item.Message); } return sb.ToString(); } internal void AddFail(Type type, string message, object data) { result.Add(new FaildItem(type, message, data)); } } public readonly struct FaildItem { public FaildItem(Type type, string message, object data) { Type = type; Message = message; Data = data; } public Type Type { get; } public string Message { get; } public object Data { get; } } } ================================================ FILE: src/MasterMemory/Validation/ValidationDatabase.cs ================================================ using System; using System.Collections.Generic; namespace MasterMemory.Validation { public class ValidationDatabase { // {Type, IReadOnlyList } readonly Dictionary dataTables = new Dictionary(); public ValidationDatabase(IEnumerable tables) { foreach (var table in tables) { // TableBase var baseType = table.GetType().BaseType; // RangeView var rangeViewAll = baseType.GetProperty("All").GetGetMethod().Invoke(table, null); var elementType = baseType.GetGenericArguments()[0]; dataTables.Add(elementType, rangeViewAll); } } internal IReadOnlyList GetTable() { if (!dataTables.TryGetValue(typeof(T), out var table)) { throw new InvalidOperationException("Can not create validator in " + typeof(T).FullName); } var data = table as IReadOnlyList; return data!; } } } ================================================ FILE: src/MasterMemory/Validation/Validator.cs ================================================ using System; using System.Linq.Expressions; using System.Runtime.CompilerServices; namespace MasterMemory.Validation { internal class Validator : IValidator { readonly ValidationDatabase database; readonly T item; readonly ValidateResult resultSet; readonly StrongBox onceCalled; readonly string pkName; readonly Delegate pkSelector; public Validator(ValidationDatabase database, T item, ValidateResult resultSet, StrongBox onceCalled, string pkName, Delegate pkSelector) { this.database = database; this.item = item; this.resultSet = resultSet; this.onceCalled = onceCalled; this.pkName = pkName; this.pkSelector = pkSelector; } public bool CallOnce() { if (!onceCalled.Value) { onceCalled.Value = true; return true; } return false; } public ValidatableSet GetTableSet() { return new ValidatableSet(database.GetTable(), resultSet, pkName, pkSelector); } public ReferenceSet GetReferenceSet() { var table = database.GetTable(); return new ReferenceSet(item, table, resultSet, pkName, pkSelector); } public void Validate(Expression> predicate) { if (!predicate.Compile(true).Invoke(item)) { var memberValues = ExpressionDumper.DumpMemberValues(item, predicate); var message = string.Format($"{predicate.ToThisBodyString()}, {memberValues}, {BuildPkMessage()}"); resultSet.AddFail(typeof(T), "Validate failed: " + message, item!); } } public void Validate(Func predicate, string message) { if (!predicate(item)) { resultSet.AddFail(typeof(T), "Validate failed: " + message + ", " + BuildPkMessage(), item!); } } public void ValidateAction(Expression> predicate) { if (!predicate.Compile(true).Invoke()) { var expr = predicate.Body.ToString(); resultSet.AddFail(typeof(T), "ValidateAction failed: " + expr + ", " + BuildPkMessage(), item!); } } public void ValidateAction(Func predicate, string message) { if (!predicate()) { resultSet.AddFail(typeof(T), "ValidateAction failed: " + message + ", " + BuildPkMessage(), item!); } } public void Fail(string message) { resultSet.AddFail(typeof(T), message + ", " + BuildPkMessage(), item!); } string BuildPkMessage() { var pk = pkSelector.DynamicInvoke(item).ToString(); return $"PK({pkName}) = {pk}"; } } } ================================================ FILE: src/MasterMemory/_InternalVisibleTo.cs ================================================ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("MasterMemory.Tests")] ================================================ FILE: src/MasterMemory/_MessagePackResolver.cs ================================================ using System; using System.Collections.Generic; using System.Text; namespace MasterMemory; [MessagePack.GeneratedMessagePackResolver] internal partial class _MessagePackResolver { } ================================================ FILE: src/MasterMemory.Annotations/Attributes.cs ================================================ using System; namespace MasterMemory { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] public class MemoryTableAttribute : Attribute { public string TableName { get; } public MemoryTableAttribute(string tableName) { this.TableName = tableName; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class PrimaryKeyAttribute : Attribute { public int KeyOrder { get; } public PrimaryKeyAttribute(int keyOrder = 0) { this.KeyOrder = keyOrder; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class SecondaryKeyAttribute : Attribute { public int IndexNo { get; } public int KeyOrder { get; } public SecondaryKeyAttribute(int indexNo, int keyOrder = 0) { this.IndexNo = indexNo; this.KeyOrder = keyOrder; } } [AttributeUsage(AttributeTargets.Property, AllowMultiple = true)] public class NonUniqueAttribute : Attribute { } [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class StringComparisonOptionAttribute : Attribute { public StringComparison StringComparison { get; } public StringComparisonOptionAttribute(StringComparison stringComparison) { this.StringComparison = stringComparison; } } } ================================================ FILE: src/MasterMemory.Annotations/MasterMemory.Annotations.csproj ================================================  netstandard2.0 13 Library enable False Cysharp true 1701;1702;1705;1591 MasterMemory MasterMemory.Annotations Attributes of MasterMemory. true ================================================ FILE: src/MasterMemory.SourceGenerator/DiagnosticDescriptors.cs ================================================ using Microsoft.CodeAnalysis; using System; using System.Collections.Generic; using System.Text; namespace MasterMemory; internal sealed class DiagnosticReporter : IEquatable { List? diagnostics; public bool HasDiagnostics => diagnostics != null && diagnostics.Count != 0; public void ReportDiagnostic(DiagnosticDescriptor diagnosticDescriptor, Location location, params object?[]? messageArgs) { var diagnostic = Diagnostic.Create(diagnosticDescriptor, location, messageArgs); if (diagnostics == null) { diagnostics = new(); } diagnostics.Add(diagnostic); } public void ReportToContext(SourceProductionContext context) { if (diagnostics != null) { foreach (var item in diagnostics) { context.ReportDiagnostic(item); } } } public bool Equals(DiagnosticReporter other) { // if error, always false and otherwise ignore if (diagnostics == null && other.diagnostics == null) { return true; } return false; } } internal static class DiagnosticDescriptors { const string Category = "GenerateMasterMemory"; public static void ReportDiagnostic(this SourceProductionContext context, DiagnosticDescriptor diagnosticDescriptor, Location location, params object?[]? messageArgs) { var diagnostic = Diagnostic.Create(diagnosticDescriptor, location, messageArgs); context.ReportDiagnostic(diagnostic); } public static DiagnosticDescriptor Create(int id, string message) { return Create(id, message, message); } public static DiagnosticDescriptor Create(int id, string title, string messageFormat) { return new DiagnosticDescriptor( id: "MAM" + id.ToString("000"), title: title, messageFormat: messageFormat, category: Category, defaultSeverity: DiagnosticSeverity.Error, isEnabledByDefault: true); } public static DiagnosticDescriptor RequirePrimaryKey { get; } = Create( 1, "MemoryTable does not found PrimaryKey property, Type:{0}."); public static DiagnosticDescriptor DuplicatePrimaryKey { get; } = Create( 2, "Duplicate PrimaryKey:{0}.{1}"); public static DiagnosticDescriptor DuplicateSecondaryKey { get; } = Create( 3, "Duplicate SecondaryKey, doesn't allow to add multiple attribute in same attribute list:{0}.{1}"); } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/CodeGenerator.cs ================================================ #nullable disable using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace MasterMemory.GeneratorCore { internal static class CodeGenerator { // return GenerationContext? public static GenerationContext CreateGenerationContext(TypeDeclarationSyntax classDecl, AttributeData memoryTableAttribute, DiagnosticReporter reporter) { var context = new GenerationContext(); context.ClassName = classDecl.Identifier.ToFullString().Trim(); context.MemoryTableName = memoryTableAttribute.ConstructorArguments[0].Value as string ?? context.ClassName; var hasError = false; var members = classDecl.Members.OfType() .Select(x => { var prop = ExtractPropertyAttribute(x, reporter); if (prop == null) { hasError = true; return default!; } return prop.Value; }) .ToArray(); if (hasError) return null; var primaryKey = AggregatePrimaryKey(members.Where(x => x.Item1 != null).Select(x => x.Item1)); if (primaryKey.Properties.Length == 0) { reporter.ReportDiagnostic(DiagnosticDescriptors.RequirePrimaryKey, classDecl.Identifier.GetLocation(), context.ClassName); return null; } var secondaryKeys = members.SelectMany(x => x.Item2).GroupBy(x => x.IndexNo).Select(x => AggregateSecondaryKey(x)).ToArray(); var properties = members.Where(x => x.Item3 != null).Select(x => new Property { Type = x.Item3.Type.ToFullStringTrim(), Name = x.Item3.Identifier.Text, }).ToArray(); var root = classDecl.SyntaxTree.GetRoot(); var ns = root.DescendantNodes().OfType() .Select(x => "using " + x.Name.ToFullStringTrim() + ";") .ToArray(); var usingStrings = root.DescendantNodes() .OfType() .Select(x => x.ToFullString().Trim()) .Concat(new[] { "using MasterMemory", "using MasterMemory.Validation", "using System", "using System.Collections.Generic" }) .Concat(ns) .Select(x => x.Trim(';') + ";") .Distinct() .OrderBy(x => x, StringComparer.Ordinal) .ToArray(); context.PrimaryKey = primaryKey; context.SecondaryKeys = secondaryKeys; context.Properties = properties; context.UsingStrings = usingStrings; context.OriginalClassDeclaration = classDecl; return context; } static (PrimaryKey, List, PropertyDeclarationSyntax)? ExtractPropertyAttribute(PropertyDeclarationSyntax property, DiagnosticReporter reporter) { // Attribute Parterns: // Primarykey(keyOrder = 0) // SecondaryKey(indexNo, keyOrder = 0) // NonUnique // StringComparisonOption PrimaryKey resultPrimaryKey = default; List secondaryKeys = new List(); bool isSerializableProperty = true; foreach (var attrList in property.AttributeLists) { var hasNonUnique = false; PrimaryKey primaryKey = default; SecondaryKey secondaryKey = default; foreach (var attr in attrList.Attributes) { var attrName = attr.Name.ToFullString().Trim(); if (attrName == "PrimaryKey" || attrName == "MasterMemory.PrimaryKey") { if (resultPrimaryKey != null) { // PrimaryKey is AllowMultiple:false so this code is dead reporter.ReportDiagnostic(DiagnosticDescriptors.DuplicatePrimaryKey, property.Identifier.GetLocation(), property.Type.ToFullString(), property.Identifier.ToFullString()); return null; } primaryKey = new PrimaryKey(); var keyProperty = new KeyProperty() { Name = property.Identifier.ToFullStringTrim(), TypeName = property.Type.ToFullStringTrim() }; foreach (var arg in attr.ArgumentList?.Arguments ?? default) { keyProperty.KeyOrder = (int)((arg.Expression as LiteralExpressionSyntax).Token.Value); } primaryKey.Properties = new[] { keyProperty }; } else if (attrName == "SecondaryKey" || attrName == "MasterMemory.SecondaryKey") { if (secondaryKey != null) { reporter.ReportDiagnostic(DiagnosticDescriptors.DuplicateSecondaryKey, property.Identifier.GetLocation(), property.Type.ToFullString(), property.Identifier.ToFullString()); return null; } secondaryKey = new SecondaryKey(); var keyProperty = new KeyProperty() { Name = property.Identifier.ToFullStringTrim(), TypeName = property.Type.ToFullStringTrim() }; var args = attr.ArgumentList.Arguments; secondaryKey.IndexNo = (int)((args[0].Expression as LiteralExpressionSyntax).Token.Value); if (args.Count == 2) { keyProperty.KeyOrder = (int)((args[1].Expression as LiteralExpressionSyntax).Token.Value); } secondaryKey.Properties = new[] { keyProperty }; } else if (attrName == "NonUnique" || attrName == "MasterMemory.NonUnique") { hasNonUnique = true; } else if (attrName == "StringComparisonOption" || attrName == "MasterMemory.StringComparisonOption") { var option = (attr.ArgumentList.Arguments[0].Expression as MemberAccessExpressionSyntax).ToFullStringTrim(); if (primaryKey != null) { primaryKey.StringComparisonOption = option; } if (secondaryKey != null) { secondaryKey.StringComparisonOption = option; } } else if (!property.Modifiers.Any(SyntaxKind.PublicKeyword) || attrName == "IgnoreMember" || attrName == "MessagePack.IgnoreMember" || attrName == "IgnoreDataMember" || attrName == "System.Runtime.Serialization.IgnoreDataMember") { isSerializableProperty = false; } } if (hasNonUnique) { if (primaryKey != null) { primaryKey.IsNonUnique = true; } if (secondaryKey != null) { secondaryKey.IsNonUnique = true; } } if (primaryKey != null) { resultPrimaryKey = primaryKey; } if (secondaryKey != null) { secondaryKeys.Add(secondaryKey); } } return (resultPrimaryKey, secondaryKeys, isSerializableProperty ? property : null); } static PrimaryKey AggregatePrimaryKey(IEnumerable primaryKeys) { var primarykey = new PrimaryKey(); var list = new List(); foreach (var item in primaryKeys) { if (item.IsNonUnique) primarykey.IsNonUnique = true; if (item.StringComparisonOption != null) primarykey.StringComparisonOption = item.StringComparisonOption; list.AddRange(item.Properties); } primarykey.Properties = list.OrderBy(x => x.KeyOrder).ToArray(); return primarykey; } // grouped by IndexNo. static SecondaryKey AggregateSecondaryKey(IGrouping secondaryKeys) { var secondaryKey = new SecondaryKey(); secondaryKey.IndexNo = secondaryKeys.Key; var list = new List(); foreach (var item in secondaryKeys) { if (item.IsNonUnique) secondaryKey.IsNonUnique = true; if (item.StringComparisonOption != null) secondaryKey.StringComparisonOption = item.StringComparisonOption; list.AddRange(item.Properties); } secondaryKey.Properties = list.OrderBy(x => x.KeyOrder).ToArray(); return secondaryKey; } } internal static class Extensions { public static string ToFullStringTrim(this SyntaxNode node) { return node.ToFullString().Trim(); } public static string ToFullStringTrim(this SyntaxToken token) { return token.ToFullString().Trim(); } } } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/DatabaseBuilderTemplate.cs ================================================ // ------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version: 17.0.0.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // // ------------------------------------------------------------------------------ namespace MasterMemory.GeneratorCore { using System.Linq; using System.Text; using System.Collections.Generic; using System; /// /// Class to produce the template output /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public partial class DatabaseBuilderTemplate : DatabaseBuilderTemplateBase { /// /// Create the template output /// public virtual string TransformText() { this.Write("// \r\n#pragma warning disable\r\n#nullable enable\r\n\r\n"); this.Write(this.ToStringHelper.ToStringWithCulture(Using)); this.Write("\r\n\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write("\r\n{\r\n public sealed class "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(" : DatabaseBuilderBase\r\n {\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write("() : this(null) { }\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write("(MessagePack.IFormatterResolver? resolver) : base(resolver) { }\r\n\r\n"); foreach(var item in GenerationContexts) { this.Write(" public "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(" Append(System.Collections.Generic.IEnumerable<"); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("> dataSource)\r\n {\r\n AppendCore(dataSource, x => "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildKeyAccessor("x"))); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildComparer())); this.Write(");\r\n return this;\r\n }\r\n\r\n"); } this.Write(" }\r\n}"); return this.GenerationEnvironment.ToString(); } } #region Base class /// /// Base class for this transformation /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public class DatabaseBuilderTemplateBase { #region Fields private global::System.Text.StringBuilder generationEnvironmentField; private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; private global::System.Collections.Generic.List indentLengthsField; private string currentIndentField = ""; private bool endsWithNewline; private global::System.Collections.Generic.IDictionary sessionField; #endregion #region Properties /// /// The string builder that generation-time code is using to assemble generated output /// public System.Text.StringBuilder GenerationEnvironment { get { if ((this.generationEnvironmentField == null)) { this.generationEnvironmentField = new global::System.Text.StringBuilder(); } return this.generationEnvironmentField; } set { this.generationEnvironmentField = value; } } /// /// The error collection for the generation process /// public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { if ((this.errorsField == null)) { this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); } return this.errorsField; } } /// /// A list of the lengths of each indent that was added with PushIndent /// private System.Collections.Generic.List indentLengths { get { if ((this.indentLengthsField == null)) { this.indentLengthsField = new global::System.Collections.Generic.List(); } return this.indentLengthsField; } } /// /// Gets the current indent we use when adding lines to the output /// public string CurrentIndent { get { return this.currentIndentField; } } /// /// Current transformation session /// public virtual global::System.Collections.Generic.IDictionary Session { get { return this.sessionField; } set { this.sessionField = value; } } #endregion #region Transform-time helpers /// /// Write text directly into the generated output /// public void Write(string textToAppend) { if (string.IsNullOrEmpty(textToAppend)) { return; } // If we're starting off, or if the previous text ended with a newline, // we have to append the current indent first. if (((this.GenerationEnvironment.Length == 0) || this.endsWithNewline)) { this.GenerationEnvironment.Append(this.currentIndentField); this.endsWithNewline = false; } // Check if the current text ends with a newline if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) { this.endsWithNewline = true; } // This is an optimization. If the current indent is "", then we don't have to do any // of the more complex stuff further down. if ((this.currentIndentField.Length == 0)) { this.GenerationEnvironment.Append(textToAppend); return; } // Everywhere there is a newline in the text, add an indent after it textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); // If the text ends with a newline, then we should strip off the indent added at the very end // because the appropriate indent will be added when the next time Write() is called if (this.endsWithNewline) { this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); } else { this.GenerationEnvironment.Append(textToAppend); } } /// /// Write text directly into the generated output /// public void WriteLine(string textToAppend) { this.Write(textToAppend); this.GenerationEnvironment.AppendLine(); this.endsWithNewline = true; } /// /// Write formatted text directly into the generated output /// public void Write(string format, params object[] args) { this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Write formatted text directly into the generated output /// public void WriteLine(string format, params object[] args) { this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Raise an error /// public void Error(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; this.Errors.Add(error); } /// /// Raise a warning /// public void Warning(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; error.IsWarning = true; this.Errors.Add(error); } /// /// Increase the indent /// public void PushIndent(string indent) { if ((indent == null)) { throw new global::System.ArgumentNullException("indent"); } this.currentIndentField = (this.currentIndentField + indent); this.indentLengths.Add(indent.Length); } /// /// Remove the last indent that was added with PushIndent /// public string PopIndent() { string returnValue = ""; if ((this.indentLengths.Count > 0)) { int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); if ((indentLength > 0)) { returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); } } return returnValue; } /// /// Remove any indentation /// public void ClearIndent() { this.indentLengths.Clear(); this.currentIndentField = ""; } #endregion #region ToString Helpers /// /// Utility class to produce culture-oriented representation of an object as a string. /// public class ToStringInstanceHelper { private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; /// /// Gets or sets format provider to be used by ToStringWithCulture method. /// public System.IFormatProvider FormatProvider { get { return this.formatProviderField ; } set { if ((value != null)) { this.formatProviderField = value; } } } /// /// This is called from the compile/run appdomain to convert objects within an expression block to a string /// public string ToStringWithCulture(object objectToConvert) { if ((objectToConvert == null)) { throw new global::System.ArgumentNullException("objectToConvert"); } System.Type t = objectToConvert.GetType(); System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { typeof(System.IFormatProvider)}); if ((method == null)) { return objectToConvert.ToString(); } else { return ((string)(method.Invoke(objectToConvert, new object[] { this.formatProviderField }))); } } } private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); /// /// Helper to produce culture-oriented representation of an object as a string /// public ToStringInstanceHelper ToStringHelper { get { return this.toStringHelperField; } } #endregion } #endregion } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/DatabaseBuilderTemplate.tt ================================================ <#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> // #pragma warning disable #nullable enable <#= Using #> namespace <#= Namespace #> { public sealed class <#= ClassName #> : DatabaseBuilderBase { public <#= ClassName #>() : this(null) { } public <#= ClassName #>(MessagePack.IFormatterResolver? resolver) : base(resolver) { } <# foreach(var item in GenerationContexts) { #> public <#= ClassName #> Append(System.Collections.Generic.IEnumerable<<#= item.ClassName #>> dataSource) { AppendCore(dataSource, x => <#= item.PrimaryKey.BuildKeyAccessor("x") #>, <#= item.PrimaryKey.BuildComparer() #>); return this; } <# } #> } } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/GenerationContext.cs ================================================ #nullable disable using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Linq; namespace MasterMemory.GeneratorCore { public record GenerationContext { public string ClassName { get; set; } public string MemoryTableName { get; set; } public EquatableArray UsingStrings { get; set; } public PrimaryKey PrimaryKey { get; set; } public EquatableArray SecondaryKeys { get; set; } // public string InputFilePath { get; set; } public IgnoreEquality OriginalClassDeclaration { get; set; } public EquatableArray Properties { get; set; } public EquatableArray Keys => new KeyBase[] { PrimaryKey }.Concat(SecondaryKeys).ToArray(); } public record Property { public string Type { get; set; } public string Name { get; set; } } public abstract record KeyBase { public bool IsNonUnique { get; set; } public string StringComparisonOption { get; set; } public EquatableArray Properties { get; set; } public abstract string SelectorName { get; } public abstract string TableName { get; } public abstract bool IsPrimary { get; } public string BuildKeyAccessor(string lambdaArgument) { if (Properties.Length == 1) { return lambdaArgument + "." + Properties[0].Name; } else { return "(" + string.Join(", ", Properties.Select(x => lambdaArgument + "." + x.Name)) + ")"; } } public string BuildTypeName() { if (Properties.Length == 1) { return Properties[0].TypeName; } else { return "(" + string.Join(", ", Properties.Select(x => x.TypeName + " " + x.Name)) + ")"; } } public string BuildMethodName() { if (Properties.Length == 1) { return Properties[0].Name; } else { return string.Join("And", Properties.Select(x => x.Name)); } } public string BuildPropertyTupleName() { if (Properties.Length == 1) { return Properties[0].Name; } else { return "(" + string.Join(", ", Properties.Select(x => x.Name)) + ")"; } } public string BuildFindPrefix() { return IsNonUnique ? "FindMany" : "FindUnique"; } public string BuildReturnTypeName(string elementName) { return IsNonUnique ? "RangeView<" + elementName + ">" : elementName; } public string BuildReturnTypeNameForClosest(string elementName) { return IsNonUnique ? "RangeView<" + elementName + ">" : elementName + "?"; } public string BuildComparer() { if (!IsStringType) { return $"System.Collections.Generic.Comparer<{BuildTypeName()}>.Default"; } else { if (StringComparisonOption != null) { return "System.StringComparer." + StringComparisonOption.Split('.').Last(); } else { return "System.StringComparer.Ordinal"; } } } public bool IsIntType { get { if (Properties.Length == 1) { var typeName = Properties[0].TypeName; if (typeName == "int" || typeName == "Int32" || typeName == "System.Int32") { return true; } else { return false; } } else { return false; } } } public bool IsStringType { get { if (Properties.Length == 1) { var typeName = Properties[0].TypeName; if (typeName == "string" || typeName == "String" || typeName == "System.String") { return true; } else { return false; } } else { return false; } } } public bool IsComparableNumberType { get { if (Properties.Length == 1) { var typeName = Properties[0].TypeName; if (typeName == "int" || typeName == "Int32" || typeName == "System.Int32" || typeName == "long" || typeName == "Int64" || typeName == "System.Int64" || typeName == "uint" || typeName == "UInt32" || typeName == "System.UInt32" || typeName == "ulong" || typeName == "UInt64" || typeName == "System.UInt64" || typeName == "byte" || typeName == "Byte" || typeName == "System.Byte" || typeName == "sbyte" || typeName == "SByte" || typeName == "System.SByte" ) { return true; } else { return false; } } else { return false; } } } public bool CanInlineBinarySearch { get { return (this is PrimaryKey) && (IsComparableNumberType) && !IsNonUnique; } } } public record PrimaryKey : KeyBase { public override string SelectorName => "primaryIndexSelector"; public override string TableName => "data"; public override bool IsPrimary => true; } public record SecondaryKey : KeyBase { public int IndexNo { get; set; } public override string SelectorName => $"secondaryIndex{IndexNo}Selector"; public override string TableName => $"secondaryIndex{IndexNo}"; public override bool IsPrimary => false; } public record KeyProperty { public int KeyOrder { get; set; } public string Name { get; set; } public string TypeName { get; set; } public override string ToString() { return $"{TypeName} {Name} : {KeyOrder}"; } } } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/ImmutableBuilderTemplate.cs ================================================ // ------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version: 17.0.0.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // // ------------------------------------------------------------------------------ namespace MasterMemory.GeneratorCore { using System.Linq; using System.Text; using System.Collections.Generic; using System; /// /// Class to produce the template output /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public partial class ImmutableBuilderTemplate : ImmutableBuilderTemplateBase { /// /// Create the template output /// public virtual string TransformText() { this.Write("// \r\n#pragma warning disable\r\n#nullable enable\r\n\r\n"); this.Write(this.ToStringHelper.ToStringWithCulture(Using)); this.Write("\r\n\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write("\r\n{\r\n public sealed class "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(" : ImmutableBuilderBase\r\n {\r\n "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("MemoryDatabase memory;\r\n\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("MemoryDatabase memory)\r\n {\r\n this.memory = memory;\r\n }\r\n" + "\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("MemoryDatabase Build()\r\n {\r\n return memory;\r\n }\r\n\r\n"); for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; this.Write(" public void ReplaceAll(System.Collections.Generic.IList<"); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("> data)\r\n {\r\n var newData = CloneAndSortBy(data, x => "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildKeyAccessor("x"))); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildComparer())); this.Write(");\r\n var table = new "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table(newData);\r\n memory = new "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("MemoryDatabase(\r\n"); for(var j = 0; j < GenerationContexts.Length; j++) { var item2 = GenerationContexts[j]; this.Write(" "); this.Write(this.ToStringHelper.ToStringWithCulture((i == j) ? "table" : "memory." + item2.ClassName + "Table")); this.Write(this.ToStringHelper.ToStringWithCulture((j == GenerationContexts.Length - 1) ? "" : ",")); this.Write("\r\n"); } this.Write(" \r\n );\r\n }\r\n\r\n"); if(!item.PrimaryKey.IsNonUnique) { this.Write(" public void Remove"); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildTypeName())); this.Write("[] keys)\r\n {\r\n var data = RemoveCore(memory."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table.GetRawDataUnsafe(), keys, x => "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildKeyAccessor("x"))); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildComparer())); this.Write(");\r\n var newData = CloneAndSortBy(data, x => "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildKeyAccessor("x"))); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildComparer())); this.Write(");\r\n var table = new "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table(newData);\r\n memory = new "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("MemoryDatabase(\r\n"); for(var j = 0; j < GenerationContexts.Length; j++) { var item2 = GenerationContexts[j]; this.Write(" "); this.Write(this.ToStringHelper.ToStringWithCulture((i == j) ? "table" : "memory." + item2.ClassName + "Table")); this.Write(this.ToStringHelper.ToStringWithCulture((j == GenerationContexts.Length - 1) ? "" : ",")); this.Write("\r\n"); } this.Write(" \r\n );\r\n }\r\n\r\n public void Diff("); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("[] addOrReplaceData)\r\n {\r\n var data = DiffCore(memory."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table.GetRawDataUnsafe(), addOrReplaceData, x => "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildKeyAccessor("x"))); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildComparer())); this.Write(");\r\n var newData = CloneAndSortBy(data, x => "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildKeyAccessor("x"))); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildComparer())); this.Write(");\r\n var table = new "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table(newData);\r\n memory = new "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("MemoryDatabase(\r\n"); for(var j = 0; j < GenerationContexts.Length; j++) { var item2 = GenerationContexts[j]; this.Write(" "); this.Write(this.ToStringHelper.ToStringWithCulture((i == j) ? "table" : "memory." + item2.ClassName + "Table")); this.Write(this.ToStringHelper.ToStringWithCulture((j == GenerationContexts.Length - 1) ? "" : ",")); this.Write("\r\n"); } this.Write(" \r\n );\r\n }\r\n"); } this.Write("\r\n"); } this.Write(" }\r\n}"); return this.GenerationEnvironment.ToString(); } } #region Base class /// /// Base class for this transformation /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public class ImmutableBuilderTemplateBase { #region Fields private global::System.Text.StringBuilder generationEnvironmentField; private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; private global::System.Collections.Generic.List indentLengthsField; private string currentIndentField = ""; private bool endsWithNewline; private global::System.Collections.Generic.IDictionary sessionField; #endregion #region Properties /// /// The string builder that generation-time code is using to assemble generated output /// public System.Text.StringBuilder GenerationEnvironment { get { if ((this.generationEnvironmentField == null)) { this.generationEnvironmentField = new global::System.Text.StringBuilder(); } return this.generationEnvironmentField; } set { this.generationEnvironmentField = value; } } /// /// The error collection for the generation process /// public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { if ((this.errorsField == null)) { this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); } return this.errorsField; } } /// /// A list of the lengths of each indent that was added with PushIndent /// private System.Collections.Generic.List indentLengths { get { if ((this.indentLengthsField == null)) { this.indentLengthsField = new global::System.Collections.Generic.List(); } return this.indentLengthsField; } } /// /// Gets the current indent we use when adding lines to the output /// public string CurrentIndent { get { return this.currentIndentField; } } /// /// Current transformation session /// public virtual global::System.Collections.Generic.IDictionary Session { get { return this.sessionField; } set { this.sessionField = value; } } #endregion #region Transform-time helpers /// /// Write text directly into the generated output /// public void Write(string textToAppend) { if (string.IsNullOrEmpty(textToAppend)) { return; } // If we're starting off, or if the previous text ended with a newline, // we have to append the current indent first. if (((this.GenerationEnvironment.Length == 0) || this.endsWithNewline)) { this.GenerationEnvironment.Append(this.currentIndentField); this.endsWithNewline = false; } // Check if the current text ends with a newline if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) { this.endsWithNewline = true; } // This is an optimization. If the current indent is "", then we don't have to do any // of the more complex stuff further down. if ((this.currentIndentField.Length == 0)) { this.GenerationEnvironment.Append(textToAppend); return; } // Everywhere there is a newline in the text, add an indent after it textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); // If the text ends with a newline, then we should strip off the indent added at the very end // because the appropriate indent will be added when the next time Write() is called if (this.endsWithNewline) { this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); } else { this.GenerationEnvironment.Append(textToAppend); } } /// /// Write text directly into the generated output /// public void WriteLine(string textToAppend) { this.Write(textToAppend); this.GenerationEnvironment.AppendLine(); this.endsWithNewline = true; } /// /// Write formatted text directly into the generated output /// public void Write(string format, params object[] args) { this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Write formatted text directly into the generated output /// public void WriteLine(string format, params object[] args) { this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Raise an error /// public void Error(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; this.Errors.Add(error); } /// /// Raise a warning /// public void Warning(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; error.IsWarning = true; this.Errors.Add(error); } /// /// Increase the indent /// public void PushIndent(string indent) { if ((indent == null)) { throw new global::System.ArgumentNullException("indent"); } this.currentIndentField = (this.currentIndentField + indent); this.indentLengths.Add(indent.Length); } /// /// Remove the last indent that was added with PushIndent /// public string PopIndent() { string returnValue = ""; if ((this.indentLengths.Count > 0)) { int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); if ((indentLength > 0)) { returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); } } return returnValue; } /// /// Remove any indentation /// public void ClearIndent() { this.indentLengths.Clear(); this.currentIndentField = ""; } #endregion #region ToString Helpers /// /// Utility class to produce culture-oriented representation of an object as a string. /// public class ToStringInstanceHelper { private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; /// /// Gets or sets format provider to be used by ToStringWithCulture method. /// public System.IFormatProvider FormatProvider { get { return this.formatProviderField ; } set { if ((value != null)) { this.formatProviderField = value; } } } /// /// This is called from the compile/run appdomain to convert objects within an expression block to a string /// public string ToStringWithCulture(object objectToConvert) { if ((objectToConvert == null)) { throw new global::System.ArgumentNullException("objectToConvert"); } System.Type t = objectToConvert.GetType(); System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { typeof(System.IFormatProvider)}); if ((method == null)) { return objectToConvert.ToString(); } else { return ((string)(method.Invoke(objectToConvert, new object[] { this.formatProviderField }))); } } } private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); /// /// Helper to produce culture-oriented representation of an object as a string /// public ToStringInstanceHelper ToStringHelper { get { return this.toStringHelperField; } } #endregion } #endregion } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/ImmutableBuilderTemplate.tt ================================================ <#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> // #pragma warning disable #nullable enable <#= Using #> namespace <#= Namespace #> { public sealed class <#= ClassName #> : ImmutableBuilderBase { <#= PrefixClassName #>MemoryDatabase memory; public <#= ClassName #>(<#= PrefixClassName #>MemoryDatabase memory) { this.memory = memory; } public <#= PrefixClassName #>MemoryDatabase Build() { return memory; } <# for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; #> public void ReplaceAll(System.Collections.Generic.IList<<#= item.ClassName #>> data) { var newData = CloneAndSortBy(data, x => <#= item.PrimaryKey.BuildKeyAccessor("x") #>, <#= item.PrimaryKey.BuildComparer() #>); var table = new <#= item.ClassName #>Table(newData); memory = new <#= PrefixClassName #>MemoryDatabase( <# for(var j = 0; j < GenerationContexts.Length; j++) { var item2 = GenerationContexts[j]; #> <#= (i == j) ? "table" : "memory." + item2.ClassName + "Table" #><#= (j == GenerationContexts.Length - 1) ? "" : "," #> <# } #> ); } <# if(!item.PrimaryKey.IsNonUnique) { #> public void Remove<#= item.ClassName #>(<#= item.PrimaryKey.BuildTypeName() #>[] keys) { var data = RemoveCore(memory.<#= item.ClassName #>Table.GetRawDataUnsafe(), keys, x => <#= item.PrimaryKey.BuildKeyAccessor("x") #>, <#= item.PrimaryKey.BuildComparer() #>); var newData = CloneAndSortBy(data, x => <#= item.PrimaryKey.BuildKeyAccessor("x") #>, <#= item.PrimaryKey.BuildComparer() #>); var table = new <#= item.ClassName #>Table(newData); memory = new <#= PrefixClassName #>MemoryDatabase( <# for(var j = 0; j < GenerationContexts.Length; j++) { var item2 = GenerationContexts[j]; #> <#= (i == j) ? "table" : "memory." + item2.ClassName + "Table" #><#= (j == GenerationContexts.Length - 1) ? "" : "," #> <# } #> ); } public void Diff(<#= item.ClassName #>[] addOrReplaceData) { var data = DiffCore(memory.<#= item.ClassName #>Table.GetRawDataUnsafe(), addOrReplaceData, x => <#= item.PrimaryKey.BuildKeyAccessor("x") #>, <#= item.PrimaryKey.BuildComparer() #>); var newData = CloneAndSortBy(data, x => <#= item.PrimaryKey.BuildKeyAccessor("x") #>, <#= item.PrimaryKey.BuildComparer() #>); var table = new <#= item.ClassName #>Table(newData); memory = new <#= PrefixClassName #>MemoryDatabase( <# for(var j = 0; j < GenerationContexts.Length; j++) { var item2 = GenerationContexts[j]; #> <#= (i == j) ? "table" : "memory." + item2.ClassName + "Table" #><#= (j == GenerationContexts.Length - 1) ? "" : "," #> <# } #> ); } <# } #> <# } #> } } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/MemoryDatabaseTemplate.cs ================================================ // ------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version: 17.0.0.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // // ------------------------------------------------------------------------------ namespace MasterMemory.GeneratorCore { using System.Linq; using System.Text; using System.Collections.Generic; using System; /// /// Class to produce the template output /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public partial class MemoryDatabaseTemplate : MemoryDatabaseTemplateBase { /// /// Create the template output /// public virtual string TransformText() { this.Write("// \r\n#pragma warning disable\r\n#nullable enable\r\n\r\n"); this.Write(this.ToStringHelper.ToStringWithCulture(Using)); this.Write("\r\n\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write("\r\n{\r\n public sealed class "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(" : MemoryDatabaseBase\r\n {\r\n"); foreach(var item in GenerationContexts) { this.Write(" public "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table { get; private set; } = default!;\r\n"); } this.Write("\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write("(\r\n"); for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; this.Write(" "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table"); this.Write(this.ToStringHelper.ToStringWithCulture((i == GenerationContexts.Length - 1) ? "" : ",")); this.Write("\r\n"); } this.Write(" )\r\n {\r\n"); for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; this.Write(" this."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table = "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table;\r\n"); } this.Write(" }\r\n\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(@"(byte[] databaseBinary, bool internString = true, MessagePack.IFormatterResolver? formatterResolver = null, int maxDegreeOfParallelism = 1) : base(databaseBinary, internString, formatterResolver, maxDegreeOfParallelism) { } protected override void Init(Dictionary header, System.ReadOnlyMemory databaseBinary, MessagePack.MessagePackSerializerOptions options, int maxDegreeOfParallelism) { if (maxDegreeOfParallelism == 1) { InitSequential(header, databaseBinary, options, maxDegreeOfParallelism); } else { InitParallel(header, databaseBinary, options, maxDegreeOfParallelism); } } void InitSequential(Dictionary header, System.ReadOnlyMemory databaseBinary, MessagePack.MessagePackSerializerOptions options, int maxDegreeOfParallelism) { "); foreach(var item in GenerationContexts) { this.Write(" this."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table = ExtractTableData<"); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table>(header, databaseBinary, options, xs => new "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table(xs));\r\n"); } this.Write(@" } void InitParallel(Dictionary header, System.ReadOnlyMemory databaseBinary, MessagePack.MessagePackSerializerOptions options, int maxDegreeOfParallelism) { var extracts = new Action[] { "); foreach(var item in GenerationContexts) { this.Write(" () => this."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table = ExtractTableData<"); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table>(header, databaseBinary, options, xs => new "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table(xs)),\r\n"); } this.Write(@" }; System.Threading.Tasks.Parallel.Invoke(new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, extracts); } public "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("ImmutableBuilder ToImmutableBuilder()\r\n {\r\n return new "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("ImmutableBuilder(this);\r\n }\r\n\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("DatabaseBuilder ToDatabaseBuilder()\r\n {\r\n var builder = new "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("DatabaseBuilder();\r\n"); foreach(var item in GenerationContexts) { this.Write(" builder.Append(this."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table.GetRawDataUnsafe());\r\n"); } this.Write(" return builder;\r\n }\r\n\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("DatabaseBuilder ToDatabaseBuilder(MessagePack.IFormatterResolver resolver)\r\n " + " {\r\n var builder = new "); this.Write(this.ToStringHelper.ToStringWithCulture(PrefixClassName)); this.Write("DatabaseBuilder(resolver);\r\n"); foreach(var item in GenerationContexts) { this.Write(" builder.Append(this."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table.GetRawDataUnsafe());\r\n"); } this.Write(@" return builder; } #if !DISABLE_MASTERMEMORY_VALIDATOR public ValidateResult Validate() { var result = new ValidateResult(); var database = new ValidationDatabase(new object[] { "); foreach(var item in GenerationContexts) { this.Write(" "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table,\r\n"); } this.Write(" });\r\n\r\n"); foreach(var item in GenerationContexts) { this.Write(" ((ITableUniqueValidate)"); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table).ValidateUnique(result);\r\n ValidateTable("); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table.All, database, \""); this.Write(this.ToStringHelper.ToStringWithCulture(item.PrimaryKey.BuildPropertyTupleName())); this.Write("\", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table.PrimaryKeySelector, result);\r\n"); } this.Write("\r\n return result;\r\n }\r\n\r\n#endif\r\n\r\n static MasterMemory." + "Meta.MetaDatabase? metaTable;\r\n\r\n public static object? GetTable("); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(" db, string tableName)\r\n {\r\n switch (tableName)\r\n {\r" + "\n"); foreach(var item in GenerationContexts) { this.Write(" case \""); this.Write(this.ToStringHelper.ToStringWithCulture(item.MemoryTableName)); this.Write("\":\r\n return db."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table;\r\n"); } this.Write(@" default: return null; } } #if !DISABLE_MASTERMEMORY_METADATABASE public static MasterMemory.Meta.MetaDatabase GetMetaDatabase() { if (metaTable != null) return metaTable; var dict = new Dictionary(); "); foreach(var item in GenerationContexts) { this.Write(" dict.Add(\""); this.Write(this.ToStringHelper.ToStringWithCulture(item.MemoryTableName)); this.Write("\", "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write(".Tables."); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("Table.CreateMetaTable());\r\n"); } this.Write("\r\n metaTable = new MasterMemory.Meta.MetaDatabase(dict);\r\n " + "return metaTable;\r\n }\r\n\r\n#endif\r\n }\r\n}"); return this.GenerationEnvironment.ToString(); } } #region Base class /// /// Base class for this transformation /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public class MemoryDatabaseTemplateBase { #region Fields private global::System.Text.StringBuilder generationEnvironmentField; private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; private global::System.Collections.Generic.List indentLengthsField; private string currentIndentField = ""; private bool endsWithNewline; private global::System.Collections.Generic.IDictionary sessionField; #endregion #region Properties /// /// The string builder that generation-time code is using to assemble generated output /// public System.Text.StringBuilder GenerationEnvironment { get { if ((this.generationEnvironmentField == null)) { this.generationEnvironmentField = new global::System.Text.StringBuilder(); } return this.generationEnvironmentField; } set { this.generationEnvironmentField = value; } } /// /// The error collection for the generation process /// public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { if ((this.errorsField == null)) { this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); } return this.errorsField; } } /// /// A list of the lengths of each indent that was added with PushIndent /// private System.Collections.Generic.List indentLengths { get { if ((this.indentLengthsField == null)) { this.indentLengthsField = new global::System.Collections.Generic.List(); } return this.indentLengthsField; } } /// /// Gets the current indent we use when adding lines to the output /// public string CurrentIndent { get { return this.currentIndentField; } } /// /// Current transformation session /// public virtual global::System.Collections.Generic.IDictionary Session { get { return this.sessionField; } set { this.sessionField = value; } } #endregion #region Transform-time helpers /// /// Write text directly into the generated output /// public void Write(string textToAppend) { if (string.IsNullOrEmpty(textToAppend)) { return; } // If we're starting off, or if the previous text ended with a newline, // we have to append the current indent first. if (((this.GenerationEnvironment.Length == 0) || this.endsWithNewline)) { this.GenerationEnvironment.Append(this.currentIndentField); this.endsWithNewline = false; } // Check if the current text ends with a newline if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) { this.endsWithNewline = true; } // This is an optimization. If the current indent is "", then we don't have to do any // of the more complex stuff further down. if ((this.currentIndentField.Length == 0)) { this.GenerationEnvironment.Append(textToAppend); return; } // Everywhere there is a newline in the text, add an indent after it textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); // If the text ends with a newline, then we should strip off the indent added at the very end // because the appropriate indent will be added when the next time Write() is called if (this.endsWithNewline) { this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); } else { this.GenerationEnvironment.Append(textToAppend); } } /// /// Write text directly into the generated output /// public void WriteLine(string textToAppend) { this.Write(textToAppend); this.GenerationEnvironment.AppendLine(); this.endsWithNewline = true; } /// /// Write formatted text directly into the generated output /// public void Write(string format, params object[] args) { this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Write formatted text directly into the generated output /// public void WriteLine(string format, params object[] args) { this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Raise an error /// public void Error(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; this.Errors.Add(error); } /// /// Raise a warning /// public void Warning(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; error.IsWarning = true; this.Errors.Add(error); } /// /// Increase the indent /// public void PushIndent(string indent) { if ((indent == null)) { throw new global::System.ArgumentNullException("indent"); } this.currentIndentField = (this.currentIndentField + indent); this.indentLengths.Add(indent.Length); } /// /// Remove the last indent that was added with PushIndent /// public string PopIndent() { string returnValue = ""; if ((this.indentLengths.Count > 0)) { int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); if ((indentLength > 0)) { returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); } } return returnValue; } /// /// Remove any indentation /// public void ClearIndent() { this.indentLengths.Clear(); this.currentIndentField = ""; } #endregion #region ToString Helpers /// /// Utility class to produce culture-oriented representation of an object as a string. /// public class ToStringInstanceHelper { private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; /// /// Gets or sets format provider to be used by ToStringWithCulture method. /// public System.IFormatProvider FormatProvider { get { return this.formatProviderField ; } set { if ((value != null)) { this.formatProviderField = value; } } } /// /// This is called from the compile/run appdomain to convert objects within an expression block to a string /// public string ToStringWithCulture(object objectToConvert) { if ((objectToConvert == null)) { throw new global::System.ArgumentNullException("objectToConvert"); } System.Type t = objectToConvert.GetType(); System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { typeof(System.IFormatProvider)}); if ((method == null)) { return objectToConvert.ToString(); } else { return ((string)(method.Invoke(objectToConvert, new object[] { this.formatProviderField }))); } } } private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); /// /// Helper to produce culture-oriented representation of an object as a string /// public ToStringInstanceHelper ToStringHelper { get { return this.toStringHelperField; } } #endregion } #endregion } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/MemoryDatabaseTemplate.tt ================================================ <#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> // #pragma warning disable #nullable enable <#= Using #> namespace <#= Namespace #> { public sealed class <#= ClassName #> : MemoryDatabaseBase { <# foreach(var item in GenerationContexts) { #> public <#= item.ClassName #>Table <#= item.ClassName #>Table { get; private set; } = default!; <# } #> public <#= ClassName #>( <# for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; #> <#= item.ClassName #>Table <#= item.ClassName #>Table<#= (i == GenerationContexts.Length - 1) ? "" : "," #> <# } #> ) { <# for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; #> this.<#= item.ClassName #>Table = <#= item.ClassName #>Table; <# } #> } public <#= ClassName #>(byte[] databaseBinary, bool internString = true, MessagePack.IFormatterResolver? formatterResolver = null, int maxDegreeOfParallelism = 1) : base(databaseBinary, internString, formatterResolver, maxDegreeOfParallelism) { } protected override void Init(Dictionary header, System.ReadOnlyMemory databaseBinary, MessagePack.MessagePackSerializerOptions options, int maxDegreeOfParallelism) { if (maxDegreeOfParallelism == 1) { InitSequential(header, databaseBinary, options, maxDegreeOfParallelism); } else { InitParallel(header, databaseBinary, options, maxDegreeOfParallelism); } } void InitSequential(Dictionary header, System.ReadOnlyMemory databaseBinary, MessagePack.MessagePackSerializerOptions options, int maxDegreeOfParallelism) { <# foreach(var item in GenerationContexts) { #> this.<#= item.ClassName #>Table = ExtractTableData<<#= item.ClassName #>, <#= item.ClassName #>Table>(header, databaseBinary, options, xs => new <#= item.ClassName #>Table(xs)); <# } #> } void InitParallel(Dictionary header, System.ReadOnlyMemory databaseBinary, MessagePack.MessagePackSerializerOptions options, int maxDegreeOfParallelism) { var extracts = new Action[] { <# foreach(var item in GenerationContexts) { #> () => this.<#= item.ClassName #>Table = ExtractTableData<<#= item.ClassName #>, <#= item.ClassName #>Table>(header, databaseBinary, options, xs => new <#= item.ClassName #>Table(xs)), <# } #> }; System.Threading.Tasks.Parallel.Invoke(new System.Threading.Tasks.ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }, extracts); } public <#= PrefixClassName #>ImmutableBuilder ToImmutableBuilder() { return new <#= PrefixClassName #>ImmutableBuilder(this); } public <#= PrefixClassName #>DatabaseBuilder ToDatabaseBuilder() { var builder = new <#= PrefixClassName #>DatabaseBuilder(); <# foreach(var item in GenerationContexts) { #> builder.Append(this.<#= item.ClassName #>Table.GetRawDataUnsafe()); <# } #> return builder; } public <#= PrefixClassName #>DatabaseBuilder ToDatabaseBuilder(MessagePack.IFormatterResolver resolver) { var builder = new <#= PrefixClassName #>DatabaseBuilder(resolver); <# foreach(var item in GenerationContexts) { #> builder.Append(this.<#= item.ClassName #>Table.GetRawDataUnsafe()); <# } #> return builder; } #if !DISABLE_MASTERMEMORY_VALIDATOR public ValidateResult Validate() { var result = new ValidateResult(); var database = new ValidationDatabase(new object[] { <# foreach(var item in GenerationContexts) { #> <#= item.ClassName #>Table, <# } #> }); <# foreach(var item in GenerationContexts) { #> ((ITableUniqueValidate)<#= item.ClassName #>Table).ValidateUnique(result); ValidateTable(<#= item.ClassName #>Table.All, database, "<#= item.PrimaryKey.BuildPropertyTupleName() #>", <#= item.ClassName #>Table.PrimaryKeySelector, result); <# } #> return result; } #endif static MasterMemory.Meta.MetaDatabase? metaTable; public static object? GetTable(<#= ClassName #> db, string tableName) { switch (tableName) { <# foreach(var item in GenerationContexts) { #> case "<#= item.MemoryTableName #>": return db.<#= item.ClassName #>Table; <# } #> default: return null; } } #if !DISABLE_MASTERMEMORY_METADATABASE public static MasterMemory.Meta.MetaDatabase GetMetaDatabase() { if (metaTable != null) return metaTable; var dict = new Dictionary(); <# foreach(var item in GenerationContexts) { #> dict.Add("<#= item.MemoryTableName #>", <#= Namespace #>.Tables.<#= item.ClassName #>Table.CreateMetaTable()); <# } #> metaTable = new MasterMemory.Meta.MetaDatabase(dict); return metaTable; } #endif } } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/MessagePackResolverTemplate.cs ================================================ // ------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version: 17.0.0.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // // ------------------------------------------------------------------------------ namespace MasterMemory.GeneratorCore { using System.Linq; using System.Text; using System.Collections.Generic; using System; /// /// Class to produce the template output /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public partial class MessagePackResolverTemplate : MessagePackResolverTemplateBase { /// /// Create the template output /// public virtual string TransformText() { this.Write("// \r\n#pragma warning disable\r\n#nullable enable\r\n\r\n"); this.Write(this.ToStringHelper.ToStringWithCulture(Using)); this.Write("\r\n\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write("\r\n{\r\n public class "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(" : global::MessagePack.IFormatterResolver\r\n {\r\n public static readonly " + "global::MessagePack.IFormatterResolver Instance = new "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write("();\r\n\r\n "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(@"() { } public global::MessagePack.Formatters.IMessagePackFormatter? GetFormatter() { return FormatterCache.formatter; } static class FormatterCache { public static readonly global::MessagePack.Formatters.IMessagePackFormatter? formatter; static FormatterCache() { var f = "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write(@"GetFormatterHelper.GetFormatter(typeof(T)); if (f != null) { formatter = (global::MessagePack.Formatters.IMessagePackFormatter)f; } } } } internal static class "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write("GetFormatterHelper\r\n {\r\n static readonly global::System.Collections.Gen" + "eric.Dictionary lookup;\r\n\r\n static "); this.Write(this.ToStringHelper.ToStringWithCulture(ClassName)); this.Write("GetFormatterHelper()\r\n {\r\n lookup = new global::System.Collecti" + "ons.Generic.Dictionary("); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContexts.Length)); this.Write(")\r\n {\r\n"); for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; this.Write(" {typeof("); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write("[]), "); this.Write(this.ToStringHelper.ToStringWithCulture(i)); this.Write(" },\r\n"); } this.Write(" };\r\n }\r\n\r\n internal static object? GetFormatter(Type t)" + "\r\n {\r\n int key;\r\n if (!lookup.TryGetValue(t, out ke" + "y)) return null;\r\n\r\n switch (key)\r\n {\r\n"); for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; this.Write(" case "); this.Write(this.ToStringHelper.ToStringWithCulture(i)); this.Write(": return new MessagePack.Formatters.ArrayFormatter<"); this.Write(this.ToStringHelper.ToStringWithCulture(item.ClassName)); this.Write(">();\r\n"); } this.Write(" default: return null;\r\n }\r\n }\r\n }\r\n}"); return this.GenerationEnvironment.ToString(); } } #region Base class /// /// Base class for this transformation /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public class MessagePackResolverTemplateBase { #region Fields private global::System.Text.StringBuilder generationEnvironmentField; private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; private global::System.Collections.Generic.List indentLengthsField; private string currentIndentField = ""; private bool endsWithNewline; private global::System.Collections.Generic.IDictionary sessionField; #endregion #region Properties /// /// The string builder that generation-time code is using to assemble generated output /// public System.Text.StringBuilder GenerationEnvironment { get { if ((this.generationEnvironmentField == null)) { this.generationEnvironmentField = new global::System.Text.StringBuilder(); } return this.generationEnvironmentField; } set { this.generationEnvironmentField = value; } } /// /// The error collection for the generation process /// public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { if ((this.errorsField == null)) { this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); } return this.errorsField; } } /// /// A list of the lengths of each indent that was added with PushIndent /// private System.Collections.Generic.List indentLengths { get { if ((this.indentLengthsField == null)) { this.indentLengthsField = new global::System.Collections.Generic.List(); } return this.indentLengthsField; } } /// /// Gets the current indent we use when adding lines to the output /// public string CurrentIndent { get { return this.currentIndentField; } } /// /// Current transformation session /// public virtual global::System.Collections.Generic.IDictionary Session { get { return this.sessionField; } set { this.sessionField = value; } } #endregion #region Transform-time helpers /// /// Write text directly into the generated output /// public void Write(string textToAppend) { if (string.IsNullOrEmpty(textToAppend)) { return; } // If we're starting off, or if the previous text ended with a newline, // we have to append the current indent first. if (((this.GenerationEnvironment.Length == 0) || this.endsWithNewline)) { this.GenerationEnvironment.Append(this.currentIndentField); this.endsWithNewline = false; } // Check if the current text ends with a newline if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) { this.endsWithNewline = true; } // This is an optimization. If the current indent is "", then we don't have to do any // of the more complex stuff further down. if ((this.currentIndentField.Length == 0)) { this.GenerationEnvironment.Append(textToAppend); return; } // Everywhere there is a newline in the text, add an indent after it textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); // If the text ends with a newline, then we should strip off the indent added at the very end // because the appropriate indent will be added when the next time Write() is called if (this.endsWithNewline) { this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); } else { this.GenerationEnvironment.Append(textToAppend); } } /// /// Write text directly into the generated output /// public void WriteLine(string textToAppend) { this.Write(textToAppend); this.GenerationEnvironment.AppendLine(); this.endsWithNewline = true; } /// /// Write formatted text directly into the generated output /// public void Write(string format, params object[] args) { this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Write formatted text directly into the generated output /// public void WriteLine(string format, params object[] args) { this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Raise an error /// public void Error(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; this.Errors.Add(error); } /// /// Raise a warning /// public void Warning(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; error.IsWarning = true; this.Errors.Add(error); } /// /// Increase the indent /// public void PushIndent(string indent) { if ((indent == null)) { throw new global::System.ArgumentNullException("indent"); } this.currentIndentField = (this.currentIndentField + indent); this.indentLengths.Add(indent.Length); } /// /// Remove the last indent that was added with PushIndent /// public string PopIndent() { string returnValue = ""; if ((this.indentLengths.Count > 0)) { int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); if ((indentLength > 0)) { returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); } } return returnValue; } /// /// Remove any indentation /// public void ClearIndent() { this.indentLengths.Clear(); this.currentIndentField = ""; } #endregion #region ToString Helpers /// /// Utility class to produce culture-oriented representation of an object as a string. /// public class ToStringInstanceHelper { private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; /// /// Gets or sets format provider to be used by ToStringWithCulture method. /// public System.IFormatProvider FormatProvider { get { return this.formatProviderField ; } set { if ((value != null)) { this.formatProviderField = value; } } } /// /// This is called from the compile/run appdomain to convert objects within an expression block to a string /// public string ToStringWithCulture(object objectToConvert) { if ((objectToConvert == null)) { throw new global::System.ArgumentNullException("objectToConvert"); } System.Type t = objectToConvert.GetType(); System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { typeof(System.IFormatProvider)}); if ((method == null)) { return objectToConvert.ToString(); } else { return ((string)(method.Invoke(objectToConvert, new object[] { this.formatProviderField }))); } } } private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); /// /// Helper to produce culture-oriented representation of an object as a string /// public ToStringInstanceHelper ToStringHelper { get { return this.toStringHelperField; } } #endregion } #endregion } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/MessagePackResolverTemplate.tt ================================================ <#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> // #pragma warning disable #nullable enable <#= Using #> namespace <#= Namespace #> { public class <#= ClassName #> : global::MessagePack.IFormatterResolver { public static readonly global::MessagePack.IFormatterResolver Instance = new <#= ClassName #>(); <#= ClassName #>() { } public global::MessagePack.Formatters.IMessagePackFormatter? GetFormatter() { return FormatterCache.formatter; } static class FormatterCache { public static readonly global::MessagePack.Formatters.IMessagePackFormatter? formatter; static FormatterCache() { var f = <#= ClassName #>GetFormatterHelper.GetFormatter(typeof(T)); if (f != null) { formatter = (global::MessagePack.Formatters.IMessagePackFormatter)f; } } } } internal static class <#= ClassName #>GetFormatterHelper { static readonly global::System.Collections.Generic.Dictionary lookup; static <#= ClassName #>GetFormatterHelper() { lookup = new global::System.Collections.Generic.Dictionary(<#= GenerationContexts.Length #>) { <# for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; #> {typeof(<#= item.ClassName #>[]), <#= i #> }, <# } #> }; } internal static object? GetFormatter(Type t) { int key; if (!lookup.TryGetValue(t, out key)) return null; switch (key) { <# for(var i = 0; i < GenerationContexts.Length; i++) { var item = GenerationContexts[i]; #> case <#= i #>: return new MessagePack.Formatters.ArrayFormatter<<#= item.ClassName #>>(); <# } #> default: return null; } } } } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/TableTemplate.cs ================================================ // ------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version: 17.0.0.0 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // // ------------------------------------------------------------------------------ namespace MasterMemory.GeneratorCore { using System.Linq; using System.Text; using System.Collections.Generic; using System; /// /// Class to produce the template output /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public partial class TableTemplate : TableTemplateBase { /// /// Create the template output /// public virtual string TransformText() { this.Write("// \r\n#pragma warning disable\r\n#nullable enable\r\n\r\n"); this.Write(this.ToStringHelper.ToStringWithCulture(Using)); this.Write("\r\n\r\nnamespace "); this.Write(this.ToStringHelper.ToStringWithCulture(Namespace)); this.Write(".Tables\r\n{\r\n public sealed partial class "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("Table : TableBase<"); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write(">, ITableUniqueValidate\r\n {\r\n public Func<"); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.PrimaryKey.BuildTypeName())); this.Write("> PrimaryKeySelector => "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.PrimaryKey.SelectorName)); this.Write(";\r\n readonly Func<"); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.PrimaryKey.BuildTypeName())); this.Write("> "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.PrimaryKey.SelectorName)); this.Write(";\r\n\r\n"); for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var item = GenerationContext.SecondaryKeys[i]; this.Write(" readonly "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("[] "); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(";\r\n readonly Func<"); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write("> "); this.Write(this.ToStringHelper.ToStringWithCulture(item.SelectorName)); this.Write(";\r\n"); } this.Write("\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("Table("); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("[] sortedData)\r\n : base(sortedData)\r\n {\r\n this."); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.PrimaryKey.SelectorName)); this.Write(" = x => "); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.PrimaryKey.BuildKeyAccessor("x"))); this.Write(";\r\n"); for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var item = GenerationContext.SecondaryKeys[i]; this.Write(" this."); this.Write(this.ToStringHelper.ToStringWithCulture(item.SelectorName)); this.Write(" = x => "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildKeyAccessor("x"))); this.Write(";\r\n this."); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(" = CloneAndSortBy(this.secondaryIndex"); this.Write(this.ToStringHelper.ToStringWithCulture(item.IndexNo)); this.Write("Selector, "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildComparer())); this.Write(");\r\n"); } this.Write(" OnAfterConstruct();\r\n }\r\n\r\n partial void OnAfterConstru" + "ct();\r\n\r\n"); for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var item = GenerationContext.SecondaryKeys[i]; this.Write(" public RangeView<"); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("> SortBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write(" => new RangeView<"); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write(">("); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(", 0, "); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(".Length - 1, true);\r\n"); } this.Write("\r\n"); foreach(var item in new KeyBase[] { GenerationContext.PrimaryKey }.Concat(GenerationContext.SecondaryKeys)) { if(item.CanInlineBinarySearch) { this.Write(" [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServic" + "es.MethodImplOptions.AggressiveInlining)]\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildReturnTypeName(GenerationContext.ClassName) + (ThrowKeyIfNotFound ? "" : "?"))); this.Write(" FindBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" key)\r\n {\r\n var lo = 0;\r\n var hi = data.Length - 1;\r" + "\n while (lo <= hi)\r\n {\r\n var mid = (int)(((" + "uint)hi + (uint)lo) >> 1);\r\n var selected = data[mid]."); this.Write(this.ToStringHelper.ToStringWithCulture(item.Properties[0].Name)); this.Write(";\r\n var found = (selected < key) ? -1 : (selected > key) ? 1 : 0;\r" + "\n if (found == 0) { return data[mid]; }\r\n if (foun" + "d < 0) { lo = mid + 1; }\r\n else { hi = mid - 1; }\r\n }\r" + "\n"); if(ThrowKeyIfNotFound) { this.Write(" return ThrowKeyNotFound(key);\r\n"); } else { this.Write(" return default;\r\n"); } this.Write(" }\r\n\r\n [System.Runtime.CompilerServices.MethodImpl(System.Runtime.C" + "ompilerServices.MethodImplOptions.AggressiveInlining)]\r\n public bool TryF" + "indBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" key, out "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildReturnTypeName(GenerationContext.ClassName))); this.Write(" result)\r\n {\r\n var lo = 0;\r\n var hi = data.Length - " + "1;\r\n while (lo <= hi)\r\n {\r\n var mid = (int)" + "(((uint)hi + (uint)lo) >> 1);\r\n var selected = data[mid]."); this.Write(this.ToStringHelper.ToStringWithCulture(item.Properties[0].Name)); this.Write(@"; var found = (selected < key) ? -1 : (selected > key) ? 1 : 0; if (found == 0) { result = data[mid]; return true; } if (found < 0) { lo = mid + 1; } else { hi = mid - 1; } } result = default!; return false; } "); } else { if (!item.IsNonUnique) { this.Write(" public "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildReturnTypeName(GenerationContext.ClassName))); this.Write(" FindBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" key)\r\n {\r\n return "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildFindPrefix())); this.Write("Core"); this.Write(this.ToStringHelper.ToStringWithCulture(!item.IsNonUnique && item.IsIntType ? "Int" : "")); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.SelectorName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildComparer())); this.Write(", key, "); this.Write(this.ToStringHelper.ToStringWithCulture(ThrowKeyIfNotFound.ToString().ToLower())); this.Write(");\r\n }\r\n \r\n public bool TryFindBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" key, out "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildReturnTypeName(GenerationContext.ClassName))); this.Write(" result)\r\n {\r\n return Try"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildFindPrefix())); this.Write("Core"); this.Write(this.ToStringHelper.ToStringWithCulture(!item.IsNonUnique && item.IsIntType ? "Int" : "")); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.SelectorName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildComparer())); this.Write(", key, out result);\r\n }\r\n"); } else { this.Write(" public "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildReturnTypeName(GenerationContext.ClassName))); this.Write(" FindBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" key)\r\n {\r\n return "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildFindPrefix())); this.Write("Core"); this.Write(this.ToStringHelper.ToStringWithCulture(!item.IsNonUnique && item.IsIntType ? "Int" : "")); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.SelectorName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildComparer())); this.Write(", key);\r\n }\r\n"); } } this.Write("\r\n public "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildReturnTypeNameForClosest(GenerationContext.ClassName))); this.Write(" FindClosestBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" key, bool selectLower = true)\r\n {\r\n return "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildFindPrefix())); this.Write("ClosestCore("); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.SelectorName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildComparer())); this.Write(", key, selectLower);\r\n }\r\n\r\n public RangeView<"); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("> FindRangeBy"); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildMethodName())); this.Write("("); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" min, "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildTypeName())); this.Write(" max, bool ascendant = true)\r\n {\r\n return "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildFindPrefix())); this.Write("RangeCore("); this.Write(this.ToStringHelper.ToStringWithCulture(item.TableName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.SelectorName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(item.BuildComparer())); this.Write(", min, max, ascendant);\r\n }\r\n\r\n"); } this.Write("\r\n void ITableUniqueValidate.ValidateUnique(ValidateResult resultSet)\r\n " + " {\r\n#if !DISABLE_MASTERMEMORY_VALIDATOR\r\n\r\n"); if (!GenerationContext.PrimaryKey.IsNonUnique) { var key = GenerationContext.PrimaryKey; this.Write(" ValidateUniqueCore("); this.Write(this.ToStringHelper.ToStringWithCulture(key.TableName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(key.SelectorName)); this.Write(", \""); this.Write(this.ToStringHelper.ToStringWithCulture(key.BuildPropertyTupleName())); this.Write("\", resultSet); \r\n"); } for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var key = GenerationContext.SecondaryKeys[i]; if (!key.IsNonUnique) { this.Write(" ValidateUniqueCore("); this.Write(this.ToStringHelper.ToStringWithCulture(key.TableName)); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(key.SelectorName)); this.Write(", \""); this.Write(this.ToStringHelper.ToStringWithCulture(key.BuildPropertyTupleName())); this.Write("\", resultSet); \r\n"); } } this.Write("\r\n#endif\r\n }\r\n\r\n#if !DISABLE_MASTERMEMORY_METADATABASE\r\n\r\n public s" + "tatic MasterMemory.Meta.MetaTable CreateMetaTable()\r\n {\r\n retu" + "rn new MasterMemory.Meta.MetaTable(typeof("); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("), typeof("); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write("Table), \""); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.MemoryTableName)); this.Write("\",\r\n new MasterMemory.Meta.MetaProperty[]\r\n {\r\n"); foreach(var prop in GenerationContext.Properties) { this.Write(" new MasterMemory.Meta.MetaProperty(typeof("); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write(").GetProperty(\""); this.Write(this.ToStringHelper.ToStringWithCulture(prop.Name)); this.Write("\")!),\r\n"); } this.Write(" },\r\n new MasterMemory.Meta.MetaIndex[]{\r\n"); foreach(var key in GenerationContext.Keys) { this.Write(" new MasterMemory.Meta.MetaIndex(new System.Reflection.Propert" + "yInfo[] {\r\n"); foreach(var keyProp in key.Properties) { this.Write(" typeof("); this.Write(this.ToStringHelper.ToStringWithCulture(GenerationContext.ClassName)); this.Write(").GetProperty(\""); this.Write(this.ToStringHelper.ToStringWithCulture(keyProp.Name)); this.Write("\")!,\r\n"); } this.Write(" }, "); this.Write(this.ToStringHelper.ToStringWithCulture(key.IsPrimary.ToString().ToLower())); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture((!key.IsNonUnique).ToString().ToLower())); this.Write(", "); this.Write(this.ToStringHelper.ToStringWithCulture(key.BuildComparer())); this.Write("),\r\n"); } this.Write(" });\r\n }\r\n\r\n#endif\r\n }\r\n}"); return this.GenerationEnvironment.ToString(); } } #region Base class /// /// Base class for this transformation /// [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.TextTemplating", "17.0.0.0")] public class TableTemplateBase { #region Fields private global::System.Text.StringBuilder generationEnvironmentField; private global::System.CodeDom.Compiler.CompilerErrorCollection errorsField; private global::System.Collections.Generic.List indentLengthsField; private string currentIndentField = ""; private bool endsWithNewline; private global::System.Collections.Generic.IDictionary sessionField; #endregion #region Properties /// /// The string builder that generation-time code is using to assemble generated output /// public System.Text.StringBuilder GenerationEnvironment { get { if ((this.generationEnvironmentField == null)) { this.generationEnvironmentField = new global::System.Text.StringBuilder(); } return this.generationEnvironmentField; } set { this.generationEnvironmentField = value; } } /// /// The error collection for the generation process /// public System.CodeDom.Compiler.CompilerErrorCollection Errors { get { if ((this.errorsField == null)) { this.errorsField = new global::System.CodeDom.Compiler.CompilerErrorCollection(); } return this.errorsField; } } /// /// A list of the lengths of each indent that was added with PushIndent /// private System.Collections.Generic.List indentLengths { get { if ((this.indentLengthsField == null)) { this.indentLengthsField = new global::System.Collections.Generic.List(); } return this.indentLengthsField; } } /// /// Gets the current indent we use when adding lines to the output /// public string CurrentIndent { get { return this.currentIndentField; } } /// /// Current transformation session /// public virtual global::System.Collections.Generic.IDictionary Session { get { return this.sessionField; } set { this.sessionField = value; } } #endregion #region Transform-time helpers /// /// Write text directly into the generated output /// public void Write(string textToAppend) { if (string.IsNullOrEmpty(textToAppend)) { return; } // If we're starting off, or if the previous text ended with a newline, // we have to append the current indent first. if (((this.GenerationEnvironment.Length == 0) || this.endsWithNewline)) { this.GenerationEnvironment.Append(this.currentIndentField); this.endsWithNewline = false; } // Check if the current text ends with a newline if (textToAppend.EndsWith(global::System.Environment.NewLine, global::System.StringComparison.CurrentCulture)) { this.endsWithNewline = true; } // This is an optimization. If the current indent is "", then we don't have to do any // of the more complex stuff further down. if ((this.currentIndentField.Length == 0)) { this.GenerationEnvironment.Append(textToAppend); return; } // Everywhere there is a newline in the text, add an indent after it textToAppend = textToAppend.Replace(global::System.Environment.NewLine, (global::System.Environment.NewLine + this.currentIndentField)); // If the text ends with a newline, then we should strip off the indent added at the very end // because the appropriate indent will be added when the next time Write() is called if (this.endsWithNewline) { this.GenerationEnvironment.Append(textToAppend, 0, (textToAppend.Length - this.currentIndentField.Length)); } else { this.GenerationEnvironment.Append(textToAppend); } } /// /// Write text directly into the generated output /// public void WriteLine(string textToAppend) { this.Write(textToAppend); this.GenerationEnvironment.AppendLine(); this.endsWithNewline = true; } /// /// Write formatted text directly into the generated output /// public void Write(string format, params object[] args) { this.Write(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Write formatted text directly into the generated output /// public void WriteLine(string format, params object[] args) { this.WriteLine(string.Format(global::System.Globalization.CultureInfo.CurrentCulture, format, args)); } /// /// Raise an error /// public void Error(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; this.Errors.Add(error); } /// /// Raise a warning /// public void Warning(string message) { System.CodeDom.Compiler.CompilerError error = new global::System.CodeDom.Compiler.CompilerError(); error.ErrorText = message; error.IsWarning = true; this.Errors.Add(error); } /// /// Increase the indent /// public void PushIndent(string indent) { if ((indent == null)) { throw new global::System.ArgumentNullException("indent"); } this.currentIndentField = (this.currentIndentField + indent); this.indentLengths.Add(indent.Length); } /// /// Remove the last indent that was added with PushIndent /// public string PopIndent() { string returnValue = ""; if ((this.indentLengths.Count > 0)) { int indentLength = this.indentLengths[(this.indentLengths.Count - 1)]; this.indentLengths.RemoveAt((this.indentLengths.Count - 1)); if ((indentLength > 0)) { returnValue = this.currentIndentField.Substring((this.currentIndentField.Length - indentLength)); this.currentIndentField = this.currentIndentField.Remove((this.currentIndentField.Length - indentLength)); } } return returnValue; } /// /// Remove any indentation /// public void ClearIndent() { this.indentLengths.Clear(); this.currentIndentField = ""; } #endregion #region ToString Helpers /// /// Utility class to produce culture-oriented representation of an object as a string. /// public class ToStringInstanceHelper { private System.IFormatProvider formatProviderField = global::System.Globalization.CultureInfo.InvariantCulture; /// /// Gets or sets format provider to be used by ToStringWithCulture method. /// public System.IFormatProvider FormatProvider { get { return this.formatProviderField ; } set { if ((value != null)) { this.formatProviderField = value; } } } /// /// This is called from the compile/run appdomain to convert objects within an expression block to a string /// public string ToStringWithCulture(object objectToConvert) { if ((objectToConvert == null)) { throw new global::System.ArgumentNullException("objectToConvert"); } System.Type t = objectToConvert.GetType(); System.Reflection.MethodInfo method = t.GetMethod("ToString", new System.Type[] { typeof(System.IFormatProvider)}); if ((method == null)) { return objectToConvert.ToString(); } else { return ((string)(method.Invoke(objectToConvert, new object[] { this.formatProviderField }))); } } } private ToStringInstanceHelper toStringHelperField = new ToStringInstanceHelper(); /// /// Helper to produce culture-oriented representation of an object as a string /// public ToStringInstanceHelper ToStringHelper { get { return this.toStringHelperField; } } #endregion } #endregion } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/TableTemplate.tt ================================================ <#@ template debug="false" hostspecific="false" linePragmas="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> // #pragma warning disable #nullable enable <#= Using #> namespace <#= Namespace #>.Tables { public sealed partial class <#= GenerationContext.ClassName #>Table : TableBase<<#= GenerationContext.ClassName #>>, ITableUniqueValidate { public Func<<#= GenerationContext.ClassName #>, <#= GenerationContext.PrimaryKey.BuildTypeName() #>> PrimaryKeySelector => <#= GenerationContext.PrimaryKey.SelectorName #>; readonly Func<<#= GenerationContext.ClassName #>, <#= GenerationContext.PrimaryKey.BuildTypeName() #>> <#= GenerationContext.PrimaryKey.SelectorName #>; <# for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var item = GenerationContext.SecondaryKeys[i]; #> readonly <#= GenerationContext.ClassName #>[] <#= item.TableName #>; readonly Func<<#= GenerationContext.ClassName #>, <#= item.BuildTypeName() #>> <#= item.SelectorName #>; <# } #> public <#= GenerationContext.ClassName #>Table(<#= GenerationContext.ClassName #>[] sortedData) : base(sortedData) { this.<#= GenerationContext.PrimaryKey.SelectorName #> = x => <#= GenerationContext.PrimaryKey.BuildKeyAccessor("x") #>; <# for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var item = GenerationContext.SecondaryKeys[i]; #> this.<#= item.SelectorName #> = x => <#= item.BuildKeyAccessor("x") #>; this.<#= item.TableName #> = CloneAndSortBy(this.secondaryIndex<#= item.IndexNo #>Selector, <#= item.BuildComparer() #>); <# } #> OnAfterConstruct(); } partial void OnAfterConstruct(); <# for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var item = GenerationContext.SecondaryKeys[i]; #> public RangeView<<#= GenerationContext.ClassName #>> SortBy<#= item.BuildMethodName() #> => new RangeView<<#= GenerationContext.ClassName #>>(<#= item.TableName #>, 0, <#= item.TableName #>.Length - 1, true); <# } #> <# foreach(var item in new KeyBase[] { GenerationContext.PrimaryKey }.Concat(GenerationContext.SecondaryKeys)) { #> <# if(item.CanInlineBinarySearch) { #> [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public <#= item.BuildReturnTypeName(GenerationContext.ClassName) + (ThrowKeyIfNotFound ? "" : "?") #> FindBy<#= item.BuildMethodName() #>(<#= item.BuildTypeName() #> key) { var lo = 0; var hi = data.Length - 1; while (lo <= hi) { var mid = (int)(((uint)hi + (uint)lo) >> 1); var selected = data[mid].<#= item.Properties[0].Name #>; var found = (selected < key) ? -1 : (selected > key) ? 1 : 0; if (found == 0) { return data[mid]; } if (found < 0) { lo = mid + 1; } else { hi = mid - 1; } } <# if(ThrowKeyIfNotFound) { #> return ThrowKeyNotFound(key); <# } else { #> return default; <# } #> } [System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)] public bool TryFindBy<#= item.BuildMethodName() #>(<#= item.BuildTypeName() #> key, out <#= item.BuildReturnTypeName(GenerationContext.ClassName) #> result) { var lo = 0; var hi = data.Length - 1; while (lo <= hi) { var mid = (int)(((uint)hi + (uint)lo) >> 1); var selected = data[mid].<#= item.Properties[0].Name #>; var found = (selected < key) ? -1 : (selected > key) ? 1 : 0; if (found == 0) { result = data[mid]; return true; } if (found < 0) { lo = mid + 1; } else { hi = mid - 1; } } result = default!; return false; } <# } else { #> <# if (!item.IsNonUnique) { #> public <#= item.BuildReturnTypeName(GenerationContext.ClassName) #> FindBy<#= item.BuildMethodName() #>(<#= item.BuildTypeName() #> key) { return <#= item.BuildFindPrefix() #>Core<#= !item.IsNonUnique && item.IsIntType ? "Int" : "" #>(<#= item.TableName #>, <#= item.SelectorName #>, <#= item.BuildComparer() #>, key, <#= ThrowKeyIfNotFound.ToString().ToLower() #>); } public bool TryFindBy<#= item.BuildMethodName() #>(<#= item.BuildTypeName() #> key, out <#= item.BuildReturnTypeName(GenerationContext.ClassName) #> result) { return Try<#= item.BuildFindPrefix() #>Core<#= !item.IsNonUnique && item.IsIntType ? "Int" : "" #>(<#= item.TableName #>, <#= item.SelectorName #>, <#= item.BuildComparer() #>, key, out result); } <# } else { #> public <#= item.BuildReturnTypeName(GenerationContext.ClassName) #> FindBy<#= item.BuildMethodName() #>(<#= item.BuildTypeName() #> key) { return <#= item.BuildFindPrefix() #>Core<#= !item.IsNonUnique && item.IsIntType ? "Int" : "" #>(<#= item.TableName #>, <#= item.SelectorName #>, <#= item.BuildComparer() #>, key); } <# } #> <# } #> public <#= item.BuildReturnTypeNameForClosest(GenerationContext.ClassName) #> FindClosestBy<#= item.BuildMethodName() #>(<#= item.BuildTypeName() #> key, bool selectLower = true) { return <#= item.BuildFindPrefix() #>ClosestCore(<#= item.TableName #>, <#= item.SelectorName #>, <#= item.BuildComparer() #>, key, selectLower); } public RangeView<<#= GenerationContext.ClassName #>> FindRangeBy<#= item.BuildMethodName() #>(<#= item.BuildTypeName() #> min, <#= item.BuildTypeName() #> max, bool ascendant = true) { return <#= item.BuildFindPrefix() #>RangeCore(<#= item.TableName #>, <#= item.SelectorName #>, <#= item.BuildComparer() #>, min, max, ascendant); } <# } #> void ITableUniqueValidate.ValidateUnique(ValidateResult resultSet) { #if !DISABLE_MASTERMEMORY_VALIDATOR <# if (!GenerationContext.PrimaryKey.IsNonUnique) { var key = GenerationContext.PrimaryKey; #> ValidateUniqueCore(<#= key.TableName #>, <#= key.SelectorName #>, "<#= key.BuildPropertyTupleName() #>", resultSet); <# } #> <# for(var i = 0; i < GenerationContext.SecondaryKeys.Length; i++) { var key = GenerationContext.SecondaryKeys[i]; #> <# if (!key.IsNonUnique) { #> ValidateUniqueCore(<#= key.TableName #>, <#= key.SelectorName #>, "<#= key.BuildPropertyTupleName() #>", resultSet); <# } #> <# } #> #endif } #if !DISABLE_MASTERMEMORY_METADATABASE public static MasterMemory.Meta.MetaTable CreateMetaTable() { return new MasterMemory.Meta.MetaTable(typeof(<#= GenerationContext.ClassName #>), typeof(<#= GenerationContext.ClassName #>Table), "<#= GenerationContext.MemoryTableName #>", new MasterMemory.Meta.MetaProperty[] { <# foreach(var prop in GenerationContext.Properties) { #> new MasterMemory.Meta.MetaProperty(typeof(<#= GenerationContext.ClassName #>).GetProperty("<#= prop.Name #>")!), <# } #> }, new MasterMemory.Meta.MetaIndex[]{ <# foreach(var key in GenerationContext.Keys) { #> new MasterMemory.Meta.MetaIndex(new System.Reflection.PropertyInfo[] { <# foreach(var keyProp in key.Properties) { #> typeof(<#= GenerationContext.ClassName #>).GetProperty("<#= keyProp.Name #>")!, <# } #> }, <#= key.IsPrimary.ToString().ToLower() #>, <#= (!key.IsNonUnique).ToString().ToLower() #>, <#= key.BuildComparer() #>), <# } #> }); } #endif } } ================================================ FILE: src/MasterMemory.SourceGenerator/GeneratorCore/Template.cs ================================================ #nullable disable using System; using System.Collections.Generic; using System.Text; namespace MasterMemory.GeneratorCore { public partial class DatabaseBuilderTemplate { public string Namespace { get; set; } public string Using { get; set; } public string PrefixClassName { get; set; } public GenerationContext[] GenerationContexts { get; set; } public string ClassName => PrefixClassName + "DatabaseBuilder"; } public partial class MemoryDatabaseTemplate { public string Namespace { get; set; } public string Using { get; set; } public string PrefixClassName { get; set; } public GenerationContext[] GenerationContexts { get; set; } public string ClassName => PrefixClassName + "MemoryDatabase"; } public partial class MetaMemoryDatabaseTemplate { public string Namespace { get; set; } public string Using { get; set; } public string PrefixClassName { get; set; } public GenerationContext[] GenerationContexts { get; set; } public string ClassName => PrefixClassName + "MetaMemoryDatabase"; } public partial class ImmutableBuilderTemplate { public string Namespace { get; set; } public string Using { get; set; } public string PrefixClassName { get; set; } public GenerationContext[] GenerationContexts { get; set; } public string ClassName => PrefixClassName + "ImmutableBuilder"; } public partial class MessagePackResolverTemplate { public string Namespace { get; set; } public string Using { get; set; } public string PrefixClassName { get; set; } public GenerationContext[] GenerationContexts { get; set; } public string ClassName => PrefixClassName + "MasterMemoryResolver"; } public partial class TableTemplate { public string Namespace { get; set; } public string Using { get; set; } public string PrefixClassName { get; set; } public GenerationContext GenerationContext { get; set; } public bool ThrowKeyIfNotFound { get; set; } } } ================================================ FILE: src/MasterMemory.SourceGenerator/MasterMemory.SourceGenerator.csproj ================================================  netstandard2.0 13 enable enable true cs MasterMemory false all runtime; build; native; contentfiles; analyzers; buildtransitive TextTemplatingFilePreprocessor DatabaseBuilderTemplate.cs TextTemplatingFilePreprocessor ImmutableBuilderTemplate.cs TextTemplatingFilePreprocessor MemoryDatabaseTemplate.cs TextTemplatingFilePreprocessor MessagePackResolverTemplate.cs TextTemplatingFilePreprocessor TableTemplate.cs True True DatabaseBuilderTemplate.tt True True ImmutableBuilderTemplate.tt True True MemoryDatabaseTemplate.tt True True MessagePackResolverTemplate.tt True True TableTemplate.tt ================================================ FILE: src/MasterMemory.SourceGenerator/MasterMemoryGenerator.cs ================================================ using MasterMemory.GeneratorCore; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using System.Diagnostics; namespace MasterMemory.SourceGenerator; [Generator(LanguageNames.CSharp)] public partial class MasterMemoryGenerator : IIncrementalGenerator { public void Initialize(IncrementalGeneratorInitializationContext context) { context.RegisterPostInitializationOutput(MasterMemoryGeneratorOptions.EmitAttribute); var namespaceProvider = context.AnalyzerConfigOptionsProvider.Select((x, _) => { x.GlobalOptions.TryGetValue("build_property.RootNamespace", out var defaultNamespace); return defaultNamespace; }) .WithTrackingName("MasterMemory.AnalyzerConfig"); var generatorOptions = context.CompilationProvider.Select((compilation, _) => { foreach (var attr in compilation.Assembly.GetAttributes()) { if (attr.AttributeClass?.Name == "MasterMemoryGeneratorOptionsAttribute") { return MasterMemoryGeneratorOptions.FromAttribute(attr); } } return default; }) .WithTrackingName("MasterMemory.CompilationProvider"); var memoryTables = context.SyntaxProvider.ForAttributeWithMetadataName("MasterMemory.MemoryTableAttribute", (node, token) => true, (ctx, token) => ctx) .WithTrackingName("MasterMemory.SyntaxProvider.0_ForAttributeWithMetadataName") .Collect() .Select((xs, _) => { var list = new List(); var reporter = new DiagnosticReporter(); foreach (var ctx in xs) { var memoryTableAttr = ctx.Attributes[0]; // AllowMultiple=false var classDecl = ctx.TargetNode as TypeDeclarationSyntax; // class or record var context = CodeGenerator.CreateGenerationContext(classDecl!, memoryTableAttr, reporter); if (context != null) { list.Add(context); } } list.Sort((a, b) => string.Compare(a.ClassName, b.ClassName, StringComparison.Ordinal)); return (reporter, new EquatableArray(list.ToArray())); }) .WithTrackingName("MasterMemory.SyntaxProvider.1_CollectAndSelect"); var allCombined = memoryTables .Combine(namespaceProvider) .Combine(generatorOptions) .WithTrackingName("MasterMemory.SyntaxProvider.2_AllCombined"); context.RegisterSourceOutput(allCombined, EmitMemoryTable); } void EmitMemoryTable(SourceProductionContext context, (((DiagnosticReporter, EquatableArray), string?), MasterMemoryGeneratorOptions) value) { var (((diagnostic, memoryTables), defaultNamespace), generatorOptions) = value; diagnostic.ReportToContext(context); if (memoryTables.Length == 0) { return; } var usingNamespace = generatorOptions.Namespace ?? defaultNamespace ?? "MasterMemory"; var prefixClassName = generatorOptions.PrefixClassName ?? ""; var throwIfKeyNotFound = !generatorOptions.IsReturnNullIfKeyNotFound; // becareful, reverse! var usingStrings = string.Join(Environment.NewLine, memoryTables.SelectMany(x => x.UsingStrings).Distinct().OrderBy(x => x, StringComparer.Ordinal)); var builderTemplate = new DatabaseBuilderTemplate(); var databaseTemplate = new MemoryDatabaseTemplate(); var immutableBuilderTemplate = new ImmutableBuilderTemplate(); var resolverTemplate = new MessagePackResolverTemplate(); builderTemplate.Namespace = databaseTemplate.Namespace = immutableBuilderTemplate.Namespace = resolverTemplate.Namespace = usingNamespace; builderTemplate.PrefixClassName = databaseTemplate.PrefixClassName = immutableBuilderTemplate.PrefixClassName = resolverTemplate.PrefixClassName = prefixClassName; builderTemplate.Using = databaseTemplate.Using = immutableBuilderTemplate.Using = resolverTemplate.Using = (usingStrings + Environment.NewLine + ("using " + usingNamespace + ".Tables;")); builderTemplate.GenerationContexts = databaseTemplate.GenerationContexts = immutableBuilderTemplate.GenerationContexts = resolverTemplate.GenerationContexts = memoryTables.ToArray(); Log(AddSource(context, builderTemplate.ClassName, builderTemplate.TransformText())); Log(AddSource(context, immutableBuilderTemplate.ClassName, immutableBuilderTemplate.TransformText())); Log(AddSource(context, databaseTemplate.ClassName, databaseTemplate.TransformText())); Log(AddSource(context, resolverTemplate.ClassName, resolverTemplate.TransformText())); foreach (var generationContext in memoryTables) { var template = new TableTemplate() { Namespace = usingNamespace, GenerationContext = generationContext, Using = string.Join(Environment.NewLine, generationContext.UsingStrings), ThrowKeyIfNotFound = throwIfKeyNotFound }; Log(AddSource(context, generationContext.ClassName + "Table", template.TransformText())); } } static void Log(string msg) => Trace.WriteLine(msg); static string AddSource(SourceProductionContext context, string fileName, string content) { var contentString = NormalizeNewLines(content); context.AddSource($"MasterMemory.{fileName}.g.cs", contentString); return $"Generate {fileName}."; static string NormalizeNewLines(string content) { // The T4 generated code may be text with mixed line ending types. (CR + CRLF) // We need to normalize the line ending type in each Operating Systems. (e.g. Windows=CRLF, Linux/macOS=LF) return content.Replace("\r\n", "\n").Replace("\n", Environment.NewLine); } } } ================================================ FILE: src/MasterMemory.SourceGenerator/MasterMemoryGeneratorOptions.cs ================================================ using Microsoft.CodeAnalysis; namespace MasterMemory.SourceGenerator; readonly record struct MasterMemoryGeneratorOptions(string? Namespace, string PrefixClassName, bool IsReturnNullIfKeyNotFound) { public static MasterMemoryGeneratorOptions FromAttribute(AttributeData attributeData) { var args = attributeData.NamedArguments; var ns = args.FirstOrDefault(x => x.Key == nameof(Namespace)).Value.Value as string ?? null; var prefix = args.FirstOrDefault(x => x.Key == nameof(PrefixClassName)).Value.Value as string ?? ""; var isReturnNull = args.FirstOrDefault(x => x.Key == nameof(IsReturnNullIfKeyNotFound)).Value.Value as bool? ?? null; return new MasterMemoryGeneratorOptions(ns, prefix, isReturnNull ?? false); } public static void EmitAttribute(IncrementalGeneratorPostInitializationContext context) { context.AddSource("MasterMemory.MasterMemoryGeneratorOptions.g.cs", """ // #pragma warning disable #nullable enable using System; namespace MasterMemory { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false, Inherited = false)] internal sealed class MasterMemoryGeneratorOptionsAttribute : Attribute { public string? Namespace { get; set; } = null; public string PrefixClassName { get; set; } = ""; public bool IsReturnNullIfKeyNotFound { get; set; } = false; } } """); } } ================================================ FILE: src/MasterMemory.SourceGenerator/Polyfill/System.CodeDom.cs ================================================ #nullable disable using System; using System.Collections.Generic; using System.Text; namespace System.CodeDom.Compiler { public class CompilerError { public string ErrorText { get; set; } public bool IsWarning { get; set; } } public class CompilerErrorCollection { public void Add(CompilerError error) { } } } ================================================ FILE: src/MasterMemory.SourceGenerator/Properties/launchSettings.json ================================================ { "profiles": { "Profile 1": { "commandName": "DebugRoslynComponent", "targetProject": "..\\..\\sandbox\\GeneratorSandbox\\GeneratorSandbox.csproj" } } } ================================================ FILE: src/MasterMemory.SourceGenerator/Utility/EquatableArray.cs ================================================ using System.Collections; using System.Runtime.CompilerServices; namespace MasterMemory; public readonly struct EquatableArray : IEquatable>, IEnumerable where T : IEquatable { readonly T[]? array; public EquatableArray() // for collection literal [] { array = []; } public EquatableArray(T[] array) { this.array = array; } public static implicit operator EquatableArray(T[] array) { return new EquatableArray(array); } public ref readonly T this[int index] { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref array![index]; } public int Length => array!.Length; public ReadOnlySpan AsSpan() { return array.AsSpan(); } public ReadOnlySpan.Enumerator GetEnumerator() { return AsSpan().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return array.AsEnumerable().GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return array.AsEnumerable().GetEnumerator(); } public bool Equals(EquatableArray other) { return AsSpan().SequenceEqual(other.AsSpan()); } } ================================================ FILE: src/MasterMemory.SourceGenerator/Utility/IgnoreEquality.cs ================================================ namespace MasterMemory; public readonly struct IgnoreEquality(T value) : IEquatable> { public readonly T Value => value; public static implicit operator IgnoreEquality(T value) { return new IgnoreEquality(value); } public static implicit operator T(IgnoreEquality value) { return value.Value; } public bool Equals(IgnoreEquality other) { // always true to ignore equality check. return true; } } ================================================ FILE: src/MasterMemory.Unity/Assets/NuGet.config ================================================ ================================================ FILE: src/MasterMemory.Unity/Assets/NuGet.config.meta ================================================ fileFormatVersion: 2 guid: e5322b2ac44bca4478137f3076edc3bb labels: - NuGetForUnity PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} defineConstraints: [] isPreloaded: 0 isOverridable: 0 isExplicitlyReferenced: 0 validateReferences: 1 platformData: - first: Any: second: enabled: 0 settings: {} - first: Editor: Editor second: enabled: 0 settings: DefaultValueInitialized: true - first: Windows Store Apps: WindowsStoreApps second: enabled: 1 settings: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/MasterMemory.Unity/Assets/Packages.meta ================================================ fileFormatVersion: 2 guid: 26358cf27391727439065c2117eb2e52 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/MasterMemory.Unity/Assets/Scenes/Main.unity ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!29 &1 OcclusionCullingSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_OcclusionBakeSettings: smallestOccluder: 5 smallestHole: 0.25 backfaceThreshold: 100 m_SceneGUID: 00000000000000000000000000000000 m_OcclusionCullingData: {fileID: 0} --- !u!104 &2 RenderSettings: m_ObjectHideFlags: 0 serializedVersion: 9 m_Fog: 0 m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} m_FogMode: 3 m_FogDensity: 0.01 m_LinearFogStart: 0 m_LinearFogEnd: 300 m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} m_AmbientIntensity: 1 m_AmbientMode: 3 m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} m_SkyboxMaterial: {fileID: 0} m_HaloStrength: 0.5 m_FlareStrength: 1 m_FlareFadeSpeed: 3 m_HaloTexture: {fileID: 0} m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} m_DefaultReflectionMode: 0 m_DefaultReflectionResolution: 128 m_ReflectionBounces: 1 m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 serializedVersion: 12 m_GIWorkflowMode: 1 m_GISettings: serializedVersion: 2 m_BounceScale: 1 m_IndirectOutputScale: 1 m_AlbedoBoost: 1 m_EnvironmentLightingMode: 0 m_EnableBakedLightmaps: 0 m_EnableRealtimeLightmaps: 0 m_LightmapEditorSettings: serializedVersion: 12 m_Resolution: 2 m_BakeResolution: 40 m_AtlasSize: 1024 m_AO: 0 m_AOMaxDistance: 1 m_CompAOExponent: 1 m_CompAOExponentDirect: 0 m_ExtractAmbientOcclusion: 0 m_Padding: 2 m_LightmapParameters: {fileID: 0} m_LightmapsBakeMode: 1 m_TextureCompression: 1 m_FinalGather: 0 m_FinalGatherFiltering: 1 m_FinalGatherRayCount: 256 m_ReflectionCompression: 2 m_MixedBakeMode: 2 m_BakeBackend: 1 m_PVRSampling: 1 m_PVRDirectSampleCount: 32 m_PVRSampleCount: 512 m_PVRBounces: 2 m_PVREnvironmentSampleCount: 256 m_PVREnvironmentReferencePointCount: 2048 m_PVRFilteringMode: 1 m_PVRDenoiserTypeDirect: 1 m_PVRDenoiserTypeIndirect: 1 m_PVRDenoiserTypeAO: 1 m_PVRFilterTypeDirect: 0 m_PVRFilterTypeIndirect: 0 m_PVRFilterTypeAO: 0 m_PVREnvironmentMIS: 1 m_PVRCulling: 1 m_PVRFilteringGaussRadiusDirect: 1 m_PVRFilteringGaussRadiusIndirect: 5 m_PVRFilteringGaussRadiusAO: 2 m_PVRFilteringAtrousPositionSigmaDirect: 0.5 m_PVRFilteringAtrousPositionSigmaIndirect: 2 m_PVRFilteringAtrousPositionSigmaAO: 1 m_ExportTrainingData: 0 m_TrainingDataDestination: TrainingData m_LightProbeSampleCountMultiplier: 4 m_LightingDataAsset: {fileID: 0} m_LightingSettings: {fileID: 0} --- !u!196 &4 NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 agentSlope: 45 agentClimb: 0.4 ledgeDropHeight: 0 maxJumpAcrossDistance: 0 minRegionArea: 2 manualCellSize: 0 cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: m_Flags: 0 m_NavMeshData: {fileID: 0} --- !u!1 &242466054 GameObject: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} serializedVersion: 6 m_Component: - component: {fileID: 242466057} - component: {fileID: 242466056} - component: {fileID: 242466055} - component: {fileID: 242466058} m_Layer: 0 m_Name: Main Camera m_TagString: MainCamera m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 --- !u!81 &242466055 AudioListener: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 242466054} m_Enabled: 1 --- !u!20 &242466056 Camera: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 242466054} m_Enabled: 1 serializedVersion: 2 m_ClearFlags: 1 m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 m_Iso: 200 m_ShutterSpeed: 0.005 m_Aperture: 16 m_FocusDistance: 10 m_FocalLength: 50 m_BladeCount: 5 m_Curvature: {x: 2, y: 11} m_BarrelClipping: 0.25 m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} m_NormalizedViewPortRect: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 near clip plane: 0.3 far clip plane: 1000 field of view: 60 orthographic: 1 orthographic size: 5 m_Depth: -1 m_CullingMask: serializedVersion: 2 m_Bits: 4294967295 m_RenderingPath: -1 m_TargetTexture: {fileID: 0} m_TargetDisplay: 0 m_TargetEye: 3 m_HDR: 1 m_AllowMSAA: 1 m_AllowDynamicResolution: 0 m_ForceIntoRT: 0 m_OcclusionCulling: 1 m_StereoConvergence: 10 m_StereoSeparation: 0.022 --- !u!4 &242466057 Transform: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 242466054} serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!114 &242466058 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 242466054} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: a7e2d905f1c43ee42914ee6de131c41e, type: 3} m_Name: m_EditorClassIdentifier: --- !u!1660057539 &9223372036854775807 SceneRoots: m_ObjectHideFlags: 0 m_Roots: - {fileID: 242466057} ================================================ FILE: src/MasterMemory.Unity/Assets/Scenes/Main.unity.meta ================================================ fileFormatVersion: 2 guid: d5ed035d1a1185e43a6e9d45e3d68f1f DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/MasterMemory.Unity/Assets/Scenes.meta ================================================ fileFormatVersion: 2 guid: 3e4672a57ce755a44805bc58b4ddea29 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/MasterMemory.Unity/Assets/Scripts/NewBehaviourScript.cs ================================================ using MasterMemory; using MessagePack; using MessagePack.Resolvers; using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using MyProj; [assembly: MasterMemoryGeneratorOptions(Namespace = "MyProj")] // If you want to use init, copy-and-paste this. namespace System.Runtime.CompilerServices { internal sealed class IsExternalInit { } } public class NewBehaviourScript : MonoBehaviour { // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } } public enum Gender { Male, Female, Unknown } // table definition marked by MemoryTableAttribute. // database-table must be serializable by MessagePack-CSsharp [MemoryTable("person"), MessagePackObject(true)] public record Person { // index definition by attributes. [PrimaryKey] public int PersonId { get; init; } // secondary index can add multiple(discriminated by index-number). [SecondaryKey(0), NonUnique] [SecondaryKey(1, keyOrder: 1), NonUnique] public int Age { get; init; } [SecondaryKey(2), NonUnique] [SecondaryKey(1, keyOrder: 0), NonUnique] public Gender Gender { get; init; } public string Name { get; init; } } public static class Initializer { [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] public static void SetupMessagePackResolver() { // Create CompositeResolver StaticCompositeResolver.Instance.Register(new[]{ MasterMemoryResolver.Instance, // set MasterMemory generated resolver StandardResolver.Instance // set default MessagePack resolver }); // Set as default var options = MessagePackSerializerOptions.Standard.WithResolver(StaticCompositeResolver.Instance); MessagePackSerializer.DefaultOptions = options; } } ================================================ FILE: src/MasterMemory.Unity/Assets/Scripts/NewBehaviourScript.cs.meta ================================================ fileFormatVersion: 2 guid: a7e2d905f1c43ee42914ee6de131c41e MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/MasterMemory.Unity/Assets/Scripts.meta ================================================ fileFormatVersion: 2 guid: dfc2745c192a6764a8f038393ed2455c folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/MasterMemory.Unity/Assets/packages.config ================================================  ================================================ FILE: src/MasterMemory.Unity/Assets/packages.config.meta ================================================ fileFormatVersion: 2 guid: 44858e3667fc6e44c8fc19fd02574910 labels: - NuGetForUnity PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} defineConstraints: [] isPreloaded: 0 isOverridable: 0 isExplicitlyReferenced: 0 validateReferences: 1 platformData: - first: Any: second: enabled: 1 settings: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: src/MasterMemory.Unity/Packages/manifest.json ================================================ { "dependencies": { "com.cysharp.runtimeunittesttoolkit": "https://github.com/Cysharp/RuntimeUnitTestToolkit.git?path=RuntimeUnitTestToolkit/Assets/RuntimeUnitTestToolkit#2.6.0", "com.github-glitchenzo.nugetforunity": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", "com.github.mastermemory.internal": "file:/../../src/MasterMemory/bin/Debug/netstandard2.0", "com.unity.ide.rider": "3.0.31", "com.unity.ide.visualstudio": "2.0.22", "com.unity.ide.vscode": "1.2.5", "com.unity.test-framework": "1.1.33", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.9", "com.unity.ugui": "1.0.0" } } ================================================ FILE: src/MasterMemory.Unity/Packages/packages-lock.json ================================================ { "dependencies": { "com.cysharp.runtimeunittesttoolkit": { "version": "https://github.com/Cysharp/RuntimeUnitTestToolkit.git?path=RuntimeUnitTestToolkit/Assets/RuntimeUnitTestToolkit#2.6.0", "depth": 0, "source": "git", "dependencies": {}, "hash": "4e3dbfaa9c40b5cfdcb71a1d4e8bca0d45ca1055" }, "com.github-glitchenzo.nugetforunity": { "version": "https://github.com/GlitchEnzo/NuGetForUnity.git?path=/src/NuGetForUnity", "depth": 0, "source": "git", "dependencies": {}, "hash": "7c80e98d3b56ecbcf854a9336458b6b0dedf1b8f" }, "com.github.mastermemory.internal": { "version": "file:C:/Users/S04451/Documents/GitHub/MasterMemory/src/MasterMemory/bin/Debug/netstandard2.0", "depth": 0, "source": "local", "dependencies": {} }, "com.unity.ext.nunit": { "version": "1.0.6", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.ide.rider": { "version": "3.0.31", "depth": 0, "source": "registry", "dependencies": { "com.unity.ext.nunit": "1.0.6" }, "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { "version": "2.0.22", "depth": 0, "source": "registry", "dependencies": { "com.unity.test-framework": "1.1.9" }, "url": "https://packages.unity.com" }, "com.unity.ide.vscode": { "version": "1.2.5", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.sysroot": { "version": "2.0.10", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.sysroot.linux-x86_64": { "version": "2.0.9", "depth": 1, "source": "registry", "dependencies": { "com.unity.sysroot": "2.0.10" }, "url": "https://packages.unity.com" }, "com.unity.test-framework": { "version": "1.1.33", "depth": 0, "source": "registry", "dependencies": { "com.unity.ext.nunit": "1.0.6", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.toolchain.win-x86_64-linux-x86_64": { "version": "2.0.9", "depth": 0, "source": "registry", "dependencies": { "com.unity.sysroot": "2.0.10", "com.unity.sysroot.linux-x86_64": "2.0.9" }, "url": "https://packages.unity.com" }, "com.unity.ugui": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0" } }, "com.unity.modules.imgui": { "version": "1.0.0", "depth": 1, "source": "builtin", "dependencies": {} }, "com.unity.modules.jsonserialize": { "version": "1.0.0", "depth": 1, "source": "builtin", "dependencies": {} }, "com.unity.modules.ui": { "version": "1.0.0", "depth": 1, "source": "builtin", "dependencies": {} } } } ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/AudioManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!11 &1 AudioManager: m_ObjectHideFlags: 0 m_Volume: 1 Rolloff Scale: 1 Doppler Factor: 1 Default Speaker Mode: 2 m_SampleRate: 0 m_DSPBufferSize: 1024 m_VirtualVoiceCount: 512 m_RealVoiceCount: 32 m_SpatializerPlugin: m_AmbisonicDecoderPlugin: m_DisableAudio: 0 m_VirtualizeEffects: 1 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/ClusterInputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!236 &1 ClusterInputManager: m_ObjectHideFlags: 0 m_Inputs: [] ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/DynamicsManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!55 &1 PhysicsManager: m_ObjectHideFlags: 0 serializedVersion: 7 m_Gravity: {x: 0, y: -9.81, z: 0} m_DefaultMaterial: {fileID: 0} m_BounceThreshold: 2 m_SleepThreshold: 0.005 m_DefaultContactOffset: 0.01 m_DefaultSolverIterations: 6 m_DefaultSolverVelocityIterations: 1 m_QueriesHitBackfaces: 0 m_QueriesHitTriggers: 1 m_EnableAdaptiveForce: 0 m_ClothInterCollisionDistance: 0 m_ClothInterCollisionStiffness: 0 m_ContactsGeneration: 1 m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff m_AutoSimulation: 1 m_AutoSyncTransforms: 0 m_ReuseCollisionCallbacks: 1 m_ClothInterCollisionSettingsToggle: 0 m_ContactPairsMode: 0 m_BroadphaseType: 0 m_WorldBounds: m_Center: {x: 0, y: 0, z: 0} m_Extent: {x: 250, y: 250, z: 250} m_WorldSubdivisions: 8 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/EditorBuildSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1045 &1 EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Scenes: - enabled: 1 path: Assets/Scenes/SampleScene.unity guid: 2cda990e2423bbf4892e6590ba056729 m_configObjects: {} ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/EditorSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!159 &1 EditorSettings: m_ObjectHideFlags: 0 serializedVersion: 7 m_ExternalVersionControlSupport: Visible Meta Files m_SerializationMode: 2 m_LineEndingsForNewScripts: 2 m_DefaultBehaviorMode: 1 m_SpritePackerMode: 4 m_SpritePackerPaddingPower: 1 m_EtcTextureCompressorBehavior: 1 m_EtcTextureFastCompressor: 1 m_EtcTextureNormalCompressor: 2 m_EtcTextureBestCompressor: 4 m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd m_ProjectGenerationRootNamespace: m_UserGeneratedProjectSuffix: m_CollabEditorSettings: inProgressEnabled: 1 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/GraphicsSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!30 &1 GraphicsSettings: m_ObjectHideFlags: 0 serializedVersion: 12 m_Deferred: m_Mode: 1 m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} m_DeferredReflections: m_Mode: 1 m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} m_ScreenSpaceShadows: m_Mode: 1 m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} m_LegacyDeferred: m_Mode: 1 m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} m_DepthNormals: m_Mode: 1 m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} m_MotionVectors: m_Mode: 1 m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} m_LightHalo: m_Mode: 1 m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} m_LensFlare: m_Mode: 1 m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} m_AlwaysIncludedShaders: - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0} m_PreloadedShaders: [] m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} m_CustomRenderPipeline: {fileID: 0} m_TransparencySortMode: 0 m_TransparencySortAxis: {x: 0, y: 0, z: 1} m_DefaultRenderingPath: 1 m_DefaultMobileRenderingPath: 1 m_TierSettings: [] m_LightmapStripping: 0 m_FogStripping: 0 m_InstancingStripping: 0 m_LightmapKeepPlain: 1 m_LightmapKeepDirCombined: 1 m_LightmapKeepDynamicPlain: 1 m_LightmapKeepDynamicDirCombined: 1 m_LightmapKeepShadowMask: 1 m_LightmapKeepSubtractive: 1 m_FogKeepLinear: 1 m_FogKeepExp: 1 m_FogKeepExp2: 1 m_AlbedoSwatchInfos: [] m_LightsUseLinearIntensity: 0 m_LightsUseColorTemperature: 0 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/InputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!13 &1 InputManager: m_ObjectHideFlags: 0 serializedVersion: 2 m_Axes: - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: left positiveButton: right altNegativeButton: a altPositiveButton: d gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: down positiveButton: up altNegativeButton: s altPositiveButton: w gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left ctrl altNegativeButton: altPositiveButton: mouse 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left alt altNegativeButton: altPositiveButton: mouse 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left shift altNegativeButton: altPositiveButton: mouse 2 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: space altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse X descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse Y descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Mouse ScrollWheel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 2 joyNum: 0 - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 0 type: 2 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 1 type: 2 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 0 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 1 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 2 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 3 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: return altNegativeButton: altPositiveButton: joystick button 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: enter altNegativeButton: altPositiveButton: space gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Cancel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: escape altNegativeButton: altPositiveButton: joystick button 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/MemorySettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!387306366 &1 MemorySettings: m_ObjectHideFlags: 0 m_EditorMemorySettings: m_MainAllocatorBlockSize: -1 m_ThreadAllocatorBlockSize: -1 m_MainGfxBlockSize: -1 m_ThreadGfxBlockSize: -1 m_CacheBlockSize: -1 m_TypetreeBlockSize: -1 m_ProfilerBlockSize: -1 m_ProfilerEditorBlockSize: -1 m_BucketAllocatorGranularity: -1 m_BucketAllocatorBucketsCount: -1 m_BucketAllocatorBlockSize: -1 m_BucketAllocatorBlockCount: -1 m_ProfilerBucketAllocatorGranularity: -1 m_ProfilerBucketAllocatorBucketsCount: -1 m_ProfilerBucketAllocatorBlockSize: -1 m_ProfilerBucketAllocatorBlockCount: -1 m_TempAllocatorSizeMain: -1 m_JobTempAllocatorBlockSize: -1 m_BackgroundJobTempAllocatorBlockSize: -1 m_JobTempAllocatorReducedBlockSize: -1 m_TempAllocatorSizeGIBakingWorker: -1 m_TempAllocatorSizeNavMeshWorker: -1 m_TempAllocatorSizeAudioWorker: -1 m_TempAllocatorSizeCloudWorker: -1 m_TempAllocatorSizeGfx: -1 m_TempAllocatorSizeJobWorker: -1 m_TempAllocatorSizeBackgroundWorker: -1 m_TempAllocatorSizePreloadManager: -1 m_PlatformMemorySettings: {} ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/NavMeshAreas.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!126 &1 NavMeshProjectSettings: m_ObjectHideFlags: 0 serializedVersion: 2 areas: - name: Walkable cost: 1 - name: Not Walkable cost: 1 - name: Jump cost: 2 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 m_LastAgentTypeID: -887442657 m_Settings: - serializedVersion: 2 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 agentSlope: 45 agentClimb: 0.75 ledgeDropHeight: 0 maxJumpAcrossDistance: 0 minRegionArea: 2 manualCellSize: 0 cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 accuratePlacement: 0 debug: m_Flags: 0 m_SettingNames: - Humanoid ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/NetworkManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!149 &1 NetworkManager: m_ObjectHideFlags: 0 m_DebugLevel: 0 m_Sendrate: 15 m_AssetToPrefab: {} ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/PackageManagerSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: m_ObjectHideFlags: 53 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: m_EnablePreReleasePackages: 0 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 m_SeeAllPackageVersions: 0 m_DismissPreviewPackagesInUse: 0 oneTimeWarningShown: 0 m_Registries: - m_Id: main m_Name: m_Url: https://packages.unity.com m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 m_ConfigSource: 0 m_UserSelectedRegistryName: m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: m_Modified: 0 m_ErrorMessage: m_UserModificationsInstanceId: -856 m_OriginalInstanceId: -858 m_LoadAssets: 0 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/Physics2DSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!19 &1 Physics2DSettings: m_ObjectHideFlags: 0 serializedVersion: 4 m_Gravity: {x: 0, y: -9.81} m_DefaultMaterial: {fileID: 0} m_VelocityIterations: 8 m_PositionIterations: 3 m_VelocityThreshold: 1 m_MaxLinearCorrection: 0.2 m_MaxAngularCorrection: 8 m_MaxTranslationSpeed: 100 m_MaxRotationSpeed: 360 m_BaumgarteScale: 0.2 m_BaumgarteTimeOfImpactScale: 0.75 m_TimeToSleep: 0.5 m_LinearSleepTolerance: 0.01 m_AngularSleepTolerance: 2 m_DefaultContactOffset: 0.01 m_JobOptions: serializedVersion: 2 useMultithreading: 0 useConsistencySorting: 0 m_InterpolationPosesPerJob: 100 m_NewContactsPerJob: 30 m_CollideContactsPerJob: 100 m_ClearFlagsPerJob: 200 m_ClearBodyForcesPerJob: 200 m_SyncDiscreteFixturesPerJob: 50 m_SyncContinuousFixturesPerJob: 50 m_FindNearestContactsPerJob: 100 m_UpdateTriggerContactsPerJob: 100 m_IslandSolverCostThreshold: 100 m_IslandSolverBodyCostScale: 1 m_IslandSolverContactCostScale: 10 m_IslandSolverJointCostScale: 10 m_IslandSolverBodiesPerJob: 50 m_IslandSolverContactsPerJob: 50 m_AutoSimulation: 1 m_QueriesHitTriggers: 1 m_QueriesStartInColliders: 1 m_CallbacksOnDisable: 1 m_ReuseCollisionCallbacks: 0 m_AutoSyncTransforms: 0 m_AlwaysShowColliders: 0 m_ShowColliderSleep: 1 m_ShowColliderContacts: 0 m_ShowColliderAABB: 0 m_ContactArrowScale: 0.2 m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/PresetManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1386491679 &1 PresetManager: m_ObjectHideFlags: 0 m_DefaultList: [] ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/ProjectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 serializedVersion: 26 productGUID: ffc3e654a5b5f54478b59336189e576a AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 AndroidEnableSustainedPerformanceMode: 0 defaultScreenOrientation: 4 targetDevice: 2 useOnDemandResources: 0 accelerometerFrequency: 60 companyName: DefaultCompany productName: MasterMemory defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 m_SplashScreenLogoStyle: 1 m_SplashScreenDrawMode: 0 m_SplashScreenBackgroundAnimationZoom: 1 m_SplashScreenLogoAnimationZoom: 1 m_SplashScreenBackgroundLandscapeAspect: 1 m_SplashScreenBackgroundPortraitAspect: 1 m_SplashScreenBackgroundLandscapeUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenBackgroundPortraitUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenLogos: [] m_VirtualRealitySplashScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1024 defaultScreenHeight: 768 defaultScreenWidthWeb: 960 defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 unsupportedMSAAFallback: 0 m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 mipStripping: 0 numberOfMipsStripped: 0 numberOfMipsStrippedPerMipmapLimitGroup: {} m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 allowedAutorotateToPortrait: 1 allowedAutorotateToPortraitUpsideDown: 1 allowedAutorotateToLandscapeRight: 1 allowedAutorotateToLandscapeLeft: 1 useOSAutorotation: 1 use32BitDisplayBuffer: 1 preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 1 androidUseSwappy: 0 androidBlitType: 0 androidResizableWindow: 0 androidDefaultWindowWidth: 1920 androidDefaultWindowHeight: 1080 androidMinimumWindowWidth: 400 androidMinimumWindowHeight: 300 androidFullscreenMode: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 deferSystemGesturesMode: 0 hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 dedicatedServerOptimizations: 0 bakeCollisionMeshes: 0 forceSingleInstance: 0 useFlipModelSwapchain: 1 resizableWindow: 0 useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 xboxEnableKinectAutoTracking: 0 xboxEnableFitness: 0 visibleInBackground: 1 allowFullscreenSwitch: 1 fullscreenMode: 1 xboxSpeechDB: 0 xboxEnableHeadOrientation: 0 xboxEnableGuest: 0 xboxEnablePIXSampling: 0 metalFramebufferOnly: 0 xboxOneResolution: 0 xboxOneSResolution: 0 xboxOneXResolution: 3 xboxOneMonoLoggingLevel: 0 xboxOneLoggingLevel: 1 xboxOneDisableEsram: 0 xboxOneEnableTypeOptimization: 0 xboxOnePresentImmediateThreshold: 0 switchQueueCommandMemory: 0 switchQueueControlMemory: 16384 switchQueueComputeMemory: 262144 switchNVNShaderPoolsGranularity: 33554432 switchNVNDefaultPoolsGranularity: 16777216 switchNVNOtherPoolsGranularity: 16777216 switchGpuScratchPoolGranularity: 2097152 switchAllowGpuScratchShrinking: 0 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 switchNVNGraphicsFirmwareMemory: 32 switchMaxWorkerMultiple: 8 stadiaPresentMode: 0 stadiaTargetFramerate: 0 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 vulkanEnableLateAcquireNextImage: 0 vulkanEnableCommandBufferRecycling: 1 loadStoreDebugModeEnabled: 0 bundleVersion: 0.1 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 1 xboxOneEnable7thCore: 1 vrSettings: enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 enableFrameTimingStats: 0 enableOpenGLProfilerGPURecorders: 1 allowHDRDisplaySupport: 0 useHDRDisplay: 0 hdrBitDepth: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 resolutionScalingMode: 0 resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: Standalone: com.DefaultCompany.MasterMemory buildNumber: Standalone: 0 VisionOS: 0 iPhone: 0 tvOS: 0 overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 AndroidMinSdkVersion: 22 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: stripEngineCode: 1 iPhoneStrippingLevel: 0 iPhoneScriptCallOptimization: 0 ForceInternetPermission: 0 ForceSDCardPermission: 0 CreateWallpaper: 0 APKExpansionFiles: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 1 strictShaderVariantMatching: 0 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 iOSTargetOSVersionString: 12.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 tvOSTargetOSVersionString: 12.0 VisionOSSdkVersion: 0 VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 uIStatusBarHidden: 1 uIExitOnSuspend: 0 uIStatusBarStyle: 0 appleTVSplashScreen: {fileID: 0} appleTVSplashScreen2x: {fileID: 0} tvOSSmallIconLayers: [] tvOSSmallIconLayers2x: [] tvOSLargeIconLayers: [] tvOSLargeIconLayers2x: [] tvOSTopShelfImageLayers: [] tvOSTopShelfImageLayers2x: [] tvOSTopShelfImageWideLayers: [] tvOSTopShelfImageWideLayers2x: [] iOSLaunchScreenType: 0 iOSLaunchScreenPortrait: {fileID: 0} iOSLaunchScreenLandscape: {fileID: 0} iOSLaunchScreenBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 iOSLaunchScreenCustomXibPath: iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreeniPadCustomXibPath: iOSLaunchScreenCustomStoryboardPath: iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 iOSRenderExtraFrameOnPause: 0 iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: iOSManualSigningProvisioningProfileID: tvOSManualSigningProvisioningProfileID: VisionOSManualSigningProvisioningProfileID: iOSManualSigningProvisioningProfileType: 0 tvOSManualSigningProvisioningProfileType: 0 VisionOSManualSigningProvisioningProfileType: 0 appleEnableAutomaticSigning: 0 iOSRequireARKit: 0 iOSAutomaticallyDetectAndAddCapabilities: 1 appleEnableProMotion: 0 shaderPrecisionModel: 0 clonedFromGUID: 5f34be1353de5cf4398729fda238591b templatePackageId: com.unity.template.2d@3.1.0 templateDefaultScene: Assets/Scenes/SampleScene.unity useCustomMainManifest: 0 useCustomLauncherManifest: 0 useCustomMainGradleTemplate: 0 useCustomLauncherGradleManifest: 0 useCustomBaseGradleTemplate: 0 useCustomGradlePropertiesTemplate: 0 useCustomGradleSettingsTemplate: 0 useCustomProguardFile: 0 AndroidTargetArchitectures: 1 AndroidTargetDevices: 0 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} AndroidKeystoreName: '{inproject}: ' AndroidKeyaliasName: AndroidEnableArmv9SecurityFeatures: 0 AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 0 AndroidIsGame: 1 AndroidEnableTango: 0 androidEnableBanner: 1 androidUseLowAccuracyLocation: 0 androidUseCustomKeystore: 0 m_AndroidBanners: - width: 320 height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 chromeosInputEmulation: 1 AndroidMinifyRelease: 0 AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 100 m_BuildTargetIcons: [] m_BuildTargetPlatformIcons: [] m_BuildTargetBatching: [] m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: - m_BuildTarget: WindowsStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: MacStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: LinuxStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: AndroidPlayer m_GraphicsJobs: 0 - m_BuildTarget: iOSSupport m_GraphicsJobs: 0 - m_BuildTarget: PS4Player m_GraphicsJobs: 0 - m_BuildTarget: PS5Player m_GraphicsJobs: 0 - m_BuildTarget: XboxOnePlayer m_GraphicsJobs: 0 - m_BuildTarget: GameCoreXboxOneSupport m_GraphicsJobs: 0 - m_BuildTarget: GameCoreScarlettSupport m_GraphicsJobs: 0 - m_BuildTarget: Switch m_GraphicsJobs: 0 - m_BuildTarget: WebGLSupport m_GraphicsJobs: 0 - m_BuildTarget: MetroSupport m_GraphicsJobs: 0 - m_BuildTarget: AppleTVSupport m_GraphicsJobs: 0 - m_BuildTarget: VisionOSPlayer m_GraphicsJobs: 0 - m_BuildTarget: BJMSupport m_GraphicsJobs: 0 - m_BuildTarget: CloudRendering m_GraphicsJobs: 0 - m_BuildTarget: EmbeddedLinux m_GraphicsJobs: 0 - m_BuildTarget: QNX m_GraphicsJobs: 0 m_BuildTargetGraphicsJobMode: - m_BuildTarget: PS4Player m_GraphicsJobMode: 0 - m_BuildTarget: XboxOnePlayer m_GraphicsJobMode: 0 m_BuildTargetGraphicsAPIs: - m_BuildTarget: AndroidPlayer m_APIs: 150000000b000000 m_Automatic: 1 - m_BuildTarget: iOSSupport m_APIs: 10000000 m_Automatic: 1 m_BuildTargetVRSettings: [] m_DefaultShaderChunkSizeInMB: 16 m_DefaultShaderChunkCount: 0 openGLRequireES31: 0 openGLRequireES31AEP: 0 openGLRequireES32: 0 m_TemplateCustomTags: {} mobileMTRendering: Android: 1 iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] m_BuildTargetGroupHDRCubemapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] m_BuildTargetGroupLoadStoreDebugModeSettings: [] m_BuildTargetNormalMapEncoding: [] m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 1 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 cameraUsageDescription: locationUsageDescription: microphoneUsageDescription: bluetoothUsageDescription: macOSTargetOSVersion: 10.13.0 switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 switchEnableFileSystemTrace: 0 switchUseGOLDLinker: 0 switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: switchCompilerFlags: switchTitleNames_0: switchTitleNames_1: switchTitleNames_2: switchTitleNames_3: switchTitleNames_4: switchTitleNames_5: switchTitleNames_6: switchTitleNames_7: switchTitleNames_8: switchTitleNames_9: switchTitleNames_10: switchTitleNames_11: switchTitleNames_12: switchTitleNames_13: switchTitleNames_14: switchTitleNames_15: switchPublisherNames_0: switchPublisherNames_1: switchPublisherNames_2: switchPublisherNames_3: switchPublisherNames_4: switchPublisherNames_5: switchPublisherNames_6: switchPublisherNames_7: switchPublisherNames_8: switchPublisherNames_9: switchPublisherNames_10: switchPublisherNames_11: switchPublisherNames_12: switchPublisherNames_13: switchPublisherNames_14: switchPublisherNames_15: switchIcons_0: {fileID: 0} switchIcons_1: {fileID: 0} switchIcons_2: {fileID: 0} switchIcons_3: {fileID: 0} switchIcons_4: {fileID: 0} switchIcons_5: {fileID: 0} switchIcons_6: {fileID: 0} switchIcons_7: {fileID: 0} switchIcons_8: {fileID: 0} switchIcons_9: {fileID: 0} switchIcons_10: {fileID: 0} switchIcons_11: {fileID: 0} switchIcons_12: {fileID: 0} switchIcons_13: {fileID: 0} switchIcons_14: {fileID: 0} switchIcons_15: {fileID: 0} switchSmallIcons_0: {fileID: 0} switchSmallIcons_1: {fileID: 0} switchSmallIcons_2: {fileID: 0} switchSmallIcons_3: {fileID: 0} switchSmallIcons_4: {fileID: 0} switchSmallIcons_5: {fileID: 0} switchSmallIcons_6: {fileID: 0} switchSmallIcons_7: {fileID: 0} switchSmallIcons_8: {fileID: 0} switchSmallIcons_9: {fileID: 0} switchSmallIcons_10: {fileID: 0} switchSmallIcons_11: {fileID: 0} switchSmallIcons_12: {fileID: 0} switchSmallIcons_13: {fileID: 0} switchSmallIcons_14: {fileID: 0} switchSmallIcons_15: {fileID: 0} switchManualHTML: switchAccessibleURLs: switchLegalInformation: switchMainThreadStackSize: 1048576 switchPresenceGroupId: switchLogoHandling: 0 switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: switchUserAccountSaveDataSize: 0 switchUserAccountSaveDataJournalSize: 0 switchApplicationAttribute: 0 switchCardSpecSize: -1 switchCardSpecClock: -1 switchRatingsMask: 0 switchRatingsInt_0: 0 switchRatingsInt_1: 0 switchRatingsInt_2: 0 switchRatingsInt_3: 0 switchRatingsInt_4: 0 switchRatingsInt_5: 0 switchRatingsInt_6: 0 switchRatingsInt_7: 0 switchRatingsInt_8: 0 switchRatingsInt_9: 0 switchRatingsInt_10: 0 switchRatingsInt_11: 0 switchRatingsInt_12: 0 switchLocalCommunicationIds_0: switchLocalCommunicationIds_1: switchLocalCommunicationIds_2: switchLocalCommunicationIds_3: switchLocalCommunicationIds_4: switchLocalCommunicationIds_5: switchLocalCommunicationIds_6: switchLocalCommunicationIds_7: switchParentalControl: 0 switchAllowsScreenshot: 1 switchAllowsVideoCapturing: 1 switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 switchUserAccountLockEnabled: 0 switchSystemResourceMemory: 16777216 switchSupportedNpadStyles: 3 switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 switchSupportedNpadCount: 8 switchEnableTouchScreen: 1 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 switchTcpAutoSendBufferSizeMax: 256 switchTcpAutoReceiveBufferSizeMax: 256 switchUdpSendBufferSize: 9 switchUdpReceiveBufferSize: 42 switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 switchUseNewStyleFilepaths: 1 switchUseLegacyFmodPriorities: 0 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 switchRamDiskSpaceSize: 12 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: ps4ParentalLevel: 11 ps4ContentID: ED1633-NPXX51362_00-0000000000000000 ps4Category: 0 ps4MasterVersion: 01.00 ps4AppVersion: 01.00 ps4AppType: 0 ps4ParamSfxPath: ps4VideoOutPixelFormat: 0 ps4VideoOutInitialWidth: 1920 ps4VideoOutBaseModeInitialWidth: 1920 ps4VideoOutReprojectionRate: 60 ps4PronunciationXMLPath: ps4PronunciationSIGPath: ps4BackgroundImagePath: ps4StartupImagePath: ps4StartupImagesFolder: ps4IconImagesFolder: ps4SaveDataImagePath: ps4SdkOverride: ps4BGMPath: ps4ShareFilePath: ps4ShareOverlayImagePath: ps4PrivacyGuardImagePath: ps4ExtraSceSysFile: ps4NPtitleDatPath: ps4RemotePlayKeyAssignment: -1 ps4RemotePlayKeyMappingDir: ps4PlayTogetherPlayerCount: 0 ps4EnterButtonAssignment: 1 ps4ApplicationParam1: 0 ps4ApplicationParam2: 0 ps4ApplicationParam3: 0 ps4ApplicationParam4: 0 ps4DownloadDataSize: 0 ps4GarlicHeapSize: 2048 ps4ProGarlicHeapSize: 2560 playerPrefsMaxSize: 32768 ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ ps4pnSessions: 1 ps4pnPresence: 1 ps4pnFriends: 1 ps4pnGameCustomData: 1 playerPrefsSupport: 0 enableApplicationExit: 0 resetTempFolder: 1 restrictedAudioUsageRights: 0 ps4UseResolutionFallback: 0 ps4ReprojectionSupport: 0 ps4UseAudio3dBackend: 0 ps4UseLowGarlicFragmentationMode: 1 ps4SocialScreenEnabled: 0 ps4ScriptOptimizationLevel: 0 ps4Audio3dVirtualSpeakerCount: 14 ps4attribCpuUsage: 0 ps4PatchPkgPath: ps4PatchLatestPkgPath: ps4PatchChangeinfoPath: ps4PatchDayOne: 0 ps4attribUserManagement: 0 ps4attribMoveSupport: 0 ps4attrib3DSupport: 0 ps4attribShareSupport: 0 ps4attribExclusiveVR: 0 ps4disableAutoHideSplash: 0 ps4videoRecordingFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0 ps4CompatibilityPS5: 0 ps4AllowPS5Detection: 0 ps4GPU800MHz: 1 ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] ps4attribVROutputEnabled: 0 monoEnv: splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} blurSplashScreenBackground: 1 spritePackerPolicy: webGLMemorySize: 16 webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 webGLShowDiagnostics: 0 webGLDataCaching: 1 webGLDebugSymbols: 0 webGLEmscriptenArgs: webGLModulesDirectory: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 webGLCompressionFormat: 1 webGLWasmArithmeticExceptions: 0 webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLDecompressionFallback: 0 webGLInitialMemorySize: 32 webGLMaximumMemorySize: 2048 webGLMemoryGrowthMode: 2 webGLMemoryLinearGrowthStep: 16 webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthCap: 96 webGLPowerPreference: 2 scriptingDefineSymbols: {} additionalCompilerArguments: {} platformArchitecture: {} scriptingBackend: Standalone: 0 il2cppCompilerConfiguration: {} il2cppCodeGeneration: {} managedStrippingLevel: EmbeddedLinux: 1 GameCoreScarlett: 1 GameCoreXboxOne: 1 Nintendo Switch: 1 PS4: 1 PS5: 1 QNX: 1 Stadia: 1 VisionOS: 1 WebGL: 1 Windows Store Apps: 1 XboxOne: 1 iPhone: 1 tvOS: 1 incrementalIl2cppBuild: {} suppressCommonWarnings: 1 allowUnsafeCode: 0 useDeterministicCompilation: 1 additionalIl2CppArgs: scriptingRuntimeVersion: 1 gcIncremental: 0 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: Template_2D metroPackageVersion: metroCertificatePath: metroCertificatePassword: metroCertificateSubject: metroCertificateIssuer: metroCertificateNotAfter: 0000000000000000 metroApplicationDescription: Template_2D wsaImages: {} metroTileShortName: metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 metroWideTileShowName: 0 metroSupportStreamingInstall: 0 metroLastRequiredScene: 0 metroDefaultTileSize: 1 metroTileForegroundText: 2 metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} metroFTAName: metroFTAFileTypes: [] metroProtocolName: vcxProjDefaultLanguage: XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: XboxOneContentId: XboxOneTitleId: XboxOneSCId: XboxOneGameOsOverridePath: XboxOnePackagingOverridePath: XboxOneAppManifestOverridePath: XboxOneVersion: 1.0.0.0 XboxOnePackageEncryption: 0 XboxOnePackageUpdateGranularity: 2 XboxOneDescription: XboxOneLanguage: - enus XboxOneCapability: [] XboxOneGameRating: {} XboxOneIsContentPackage: 0 XboxOneEnhancedXboxCompatibilityMode: 0 XboxOneEnableGPUVariability: 1 XboxOneSockets: {} XboxOneSplashScreen: {fileID: 0} XboxOneAllowedProductIds: [] XboxOnePersistentLocalStorageSize: 0 XboxOneXTitleMemory: 8 XboxOneOverrideIdentityName: XboxOneOverrideIdentityPublisher: vrEditorSettings: {} cloudServicesEnabled: UNet: 1 luminIcon: m_Name: m_ModelFolderPath: m_PortalFolderPath: luminCert: m_CertPath: m_SignPackage: 1 luminIsChannelApp: 0 luminVersion: m_VersionCode: 1 m_VersionName: hmiPlayerDataPath: hmiForceSRGBBlit: 1 embeddedLinuxEnableGamepadInput: 1 hmiLogStartupTiming: 0 hmiCpuConfiguration: apiCompatibilityLevel: 6 activeInputHandler: 0 windowsGamepadBackendHint: 0 cloudProjectId: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] projectName: organizationId: cloudEnabled: 0 legacyClampBlendShapeWeights: 1 hmiLoadingImage: {fileID: 0} platformRequiresReadableAssets: 0 virtualTexturingSupportEnabled: 0 insecureHttpOption: 0 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/ProjectVersion.txt ================================================ m_EditorVersion: 2022.3.12f1 m_EditorVersionWithRevision: 2022.3.12f1 (4fe6e059c7ef) ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/QualitySettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!47 &1 QualitySettings: m_ObjectHideFlags: 0 serializedVersion: 5 m_CurrentQuality: 3 m_QualitySettings: - serializedVersion: 2 name: Very Low pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 15 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 1 textureQuality: 1 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 0 lodBias: 0.3 maximumLODLevel: 0 particleRaycastBudget: 4 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Low pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 2 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 0 lodBias: 0.4 maximumLODLevel: 0 particleRaycastBudget: 16 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Medium pixelLightCount: 1 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 2 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 0.7 maximumLODLevel: 0 particleRaycastBudget: 64 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: High pixelLightCount: 2 shadows: 0 shadowResolution: 1 shadowProjection: 1 shadowCascades: 2 shadowDistance: 40 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 2 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 1 maximumLODLevel: 0 particleRaycastBudget: 256 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Very High pixelLightCount: 3 shadows: 0 shadowResolution: 2 shadowProjection: 1 shadowCascades: 2 shadowDistance: 70 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 4 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 1.5 maximumLODLevel: 0 particleRaycastBudget: 1024 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Ultra pixelLightCount: 4 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 4 shadowDistance: 150 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 4 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 2 maximumLODLevel: 0 particleRaycastBudget: 4096 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] m_PerPlatformDefaultQuality: Android: 2 Nintendo 3DS: 5 Nintendo Switch: 5 PS4: 5 PSM: 5 PSP2: 2 Standalone: 5 Tizen: 2 WebGL: 3 WiiU: 5 Windows Store Apps: 5 XboxOne: 5 iPhone: 2 tvOS: 2 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/SceneTemplateSettings.json ================================================ { "templatePinStates": [], "dependencyTypeInfos": [ { "userAdded": false, "type": "UnityEngine.AnimationClip", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Animations.AnimatorController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.AnimatorOverrideController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Audio.AudioMixerController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.ComputeShader", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Cubemap", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.GameObject", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.LightingDataAsset", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.LightingSettings", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Material", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.MonoScript", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.PhysicMaterial", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.PhysicsMaterial2D", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.VolumeProfile", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.SceneAsset", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Shader", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.ShaderVariantCollection", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Texture", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Texture2D", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Timeline.TimelineAsset", "defaultInstantiationMode": 0 } ], "defaultDependencyTypeInfo": { "userAdded": false, "type": "", "defaultInstantiationMode": 1 }, "newSceneOverride": 0 } ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/TagManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!78 &1 TagManager: serializedVersion: 2 tags: [] layers: - Default - TransparentFX - Ignore Raycast - - Water - UI - - - - - - - - - - - - - - - - - - - - - - - - - - m_SortingLayers: - name: Default uniqueID: 0 locked: 0 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/TimeManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!5 &1 TimeManager: m_ObjectHideFlags: 0 Fixed Timestep: 0.02 Maximum Allowed Timestep: 0.1 m_TimeScale: 1 Maximum Particle Timestep: 0.03 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/UnityConnectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!310 &1 UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 m_Enabled: 0 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events m_ConfigUrl: https://config.uca.cloud.unity3d.com m_DashboardUrl: https://dashboard.unity3d.com m_TestInitMode: 0 CrashReportingSettings: m_EventUrl: https://perf-events.cloud.unity3d.com m_Enabled: 0 m_LogBufferSize: 10 m_CaptureEditorExceptions: 1 UnityPurchasingSettings: m_Enabled: 0 m_TestMode: 0 UnityAnalyticsSettings: m_Enabled: 0 m_TestMode: 0 m_InitializeOnStartup: 1 m_PackageRequiringCoreStatsPresent: 0 UnityAdsSettings: m_Enabled: 0 m_InitializeOnStartup: 1 m_TestMode: 0 m_IosGameId: m_AndroidGameId: m_GameIds: {} m_GameId: PerformanceReportingSettings: m_Enabled: 0 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/VFXManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!937362698 &1 VFXManager: m_ObjectHideFlags: 0 m_IndirectShader: {fileID: 0} m_RenderPipeSettingsPath: ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/VersionControlSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!890905787 &1 VersionControlSettings: m_ObjectHideFlags: 0 m_Mode: Visible Meta Files m_CollabEditorSettings: inProgressEnabled: 1 ================================================ FILE: src/MasterMemory.Unity/ProjectSettings/XRSettings.asset ================================================ { "m_SettingKeys": [ "VR Device Disabled", "VR Device User Alert" ], "m_SettingValues": [ "False", "False" ] } ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/AssemblyAtrributeTest.cs ================================================ namespace MasterMemory.SourceGenerator.Tests; public class AssemblyAtrributeTest(ITestOutputHelper outputHelper) : TestBase(outputHelper) { [Fact] public void NoGeneratorOptions() { var codes = Helper.GenerateCode(""" [MemoryTable("item")] public class Item { [PrimaryKey] public int ItemId { get; set; } } """); codes.TryGetValue("MasterMemory.DatabaseBuilder.g.cs", out _).ShouldBeTrue(); var mainCode = codes["MasterMemory.ItemTable.g.cs"]; WriteLine(mainCode); mainCode.ShouldContain("namespace MasterMemory.Tables"); mainCode.ShouldContain("return ThrowKeyNotFound(key);"); mainCode.ShouldContain("public sealed partial class ItemTable"); } [Fact] public void FullOptions() { var codes = Helper.GenerateCode(""" [assembly: MasterMemoryGeneratorOptions( Namespace = "MyNamespace", IsReturnNullIfKeyNotFound = true, PrefixClassName = "FooBarBaz")] [MemoryTable("item")] public class Item { [PrimaryKey] public int ItemId { get; set; } } """); codes.TryGetValue("MasterMemory.FooBarBazDatabaseBuilder.g.cs", out _).ShouldBeTrue(); var mainCode = codes["MasterMemory.ItemTable.g.cs"]; WriteLine(mainCode); mainCode.ShouldContain("namespace MyNamespace.Tables"); mainCode.ShouldNotContain("return ThrowKeyNotFound(key);"); mainCode.ShouldContain("public sealed partial class ItemTable"); } } ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/DiagnosticsTest.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace MasterMemory.SourceGenerator.Tests; public class DiagnosticsTest(ITestOutputHelper outputHelper) : TestBase(outputHelper) { [Fact] public void RequirePrimaryKey() { Helper.Verify(1, """ [MemoryTable("item")] public class Item { // [PrimaryKey] // No PrimaryKey public int ItemId { get; set; } } """, "Item"); } [Fact] public void DuplicateSecondaryKey() { Helper.Verify(3, """ [MemoryTable("item")] public class Item { [PrimaryKey] public int ItemId1 { get; set; } [SecondaryKey(0), SecondaryKey(1)] public int ItemId2 { get; set; } } """, "ItemId2"); } } ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/GenerateTest.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit.Abstractions; namespace MasterMemory.SourceGenerator.Tests; public class GenerateTest(ITestOutputHelper outputHelper) : TestBase(outputHelper) { [Fact] public void GenerateClass() { Helper.Ok(""" [MemoryTable("item")] public class Item { [PrimaryKey] public int ItemId { get; set; } } """); } [Fact] public void GenerateRecord() { Helper.Ok(""" [MemoryTable("item")] public record Item { [PrimaryKey] public int ItemId { get; set; } } """); } } ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/IncrementalGeneratorTest.cs ================================================ namespace MasterMemory.SourceGenerator.Tests; public class IncrementalGeneratorTest { void VerifySourceOutputReasonIsCached((string Key, string Reasons)[] reasons) { var reason = reasons.FirstOrDefault(x => x.Key == "SourceOutput").Reasons; reason.ShouldBe("Cached"); } void VerifySourceOutputReasonIsNotCached((string Key, string Reasons)[] reasons) { var reason = reasons.FirstOrDefault(x => x.Key == "SourceOutput").Reasons; reason.ShouldNotBe("Cached"); } [Fact] public void CheckReasons() { var step1 = """ [MemoryTable("item")] public class Item { [PrimaryKey] public int ItemId { get; set; } } """; var step2 = """ [MemoryTable("item")] public class Item { // add unrelated line [PrimaryKey] public int ItemId { get; set; } } """; var step3 = """ [MemoryTable("item")] public class Item { [PrimaryKey] public int ItemId2 { get; set; } // changed name } """; var reasons = CSharpGeneratorRunner.GetIncrementalGeneratorTrackedStepsReasons("MasterMemory.SyntaxProvider.", step1, step2, step3); VerifySourceOutputReasonIsCached(reasons[1]); VerifySourceOutputReasonIsNotCached(reasons[2]); } } ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/MasterMemory.SourceGenerator.Tests.csproj ================================================  net9.0 enable enable ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/TestBase.cs ================================================ namespace MasterMemory.SourceGenerator.Tests; public abstract class TestBase(ITestOutputHelper testoutputHelper) { protected CodeGeneratorHelper Helper = new CodeGeneratorHelper(testoutputHelper, "MAM"); protected void WriteLine(string message) { testoutputHelper.WriteLine(message); } } ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/Utility/CSharpGeneratorRunner.cs ================================================ using MasterMemory.SourceGenerator; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Runtime.Loader; public static class CSharpGeneratorRunner { static Compilation baseCompilation = default!; [ModuleInitializer] public static void InitializeCompilation() { // Add project namespace. var globalUsings = """ global using System; global using System.Threading; global using System.Threading.Tasks; global using System.ComponentModel.DataAnnotations; global using MasterMemory; """; var references = AppDomain.CurrentDomain.GetAssemblies() .Where(x => !x.IsDynamic && !string.IsNullOrWhiteSpace(x.Location)) .Select(x => MetadataReference.CreateFromFile(x.Location)) .Concat(GetSelfReferences()); var compilation = CSharpCompilation.Create("generatortest", references: references, syntaxTrees: [CSharpSyntaxTree.ParseText(globalUsings, path: "GlobalUsings.cs")], options: new CSharpCompilationOptions(OutputKind.ConsoleApplication, allowUnsafe: true)); // .exe baseCompilation = compilation; static IEnumerable GetSelfReferences() { // MasterMemory.Annotations yield return MetadataReference.CreateFromFile(typeof(MasterMemory.MemoryTableAttribute).Assembly.Location); } } public static (Compilation, ImmutableArray) RunGenerator([StringSyntax("C#-test")] string source, string[]? preprocessorSymbols = null, AnalyzerConfigOptionsProvider? options = null) { if (preprocessorSymbols == null) { preprocessorSymbols = new[] { "NET8_0_OR_GREATER" }; } var parseOptions = new CSharpParseOptions(LanguageVersion.CSharp12, preprocessorSymbols: preprocessorSymbols); // 12 // Create SourceGenerator var driver = CSharpGeneratorDriver.Create(new MasterMemoryGenerator()).WithUpdatedParseOptions(parseOptions); if (options != null) { driver = (Microsoft.CodeAnalysis.CSharp.CSharpGeneratorDriver)driver.WithUpdatedAnalyzerConfigOptions(options); } var compilation = baseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(source, parseOptions)); driver.RunGeneratorsAndUpdateCompilation(compilation, out var newCompilation, out var diagnostics); return (newCompilation, diagnostics); } public static (Compilation, ImmutableArray, string) CompileAndExecute(string source, string[] args, string[]? preprocessorSymbols = null, AnalyzerConfigOptionsProvider? options = null) { var (compilation, diagnostics) = RunGenerator(source, preprocessorSymbols, options); using var ms = new MemoryStream(); var emitResult = compilation.Emit(ms); if (!emitResult.Success) { throw new InvalidOperationException("Emit Failed\r\n" + string.Join("\r\n", emitResult.Diagnostics.Select(x => x.ToString()))); } ms.Position = 0; // capture stdout log // modify global stdout so can't run in parallel unit-test var originalOut = Console.Out; try { var stringWriter = new StringWriter(); Console.SetOut(stringWriter); // load and invoke Main(args) var loadContext = new AssemblyLoadContext("source-generator", isCollectible: true); // isCollectible to support Unload var assembly = loadContext.LoadFromStream(ms); assembly.EntryPoint!.Invoke(null, new object[] { args }); loadContext.Unload(); return (compilation, diagnostics, stringWriter.ToString()); } finally { Console.SetOut(originalOut); } } public static (string Key, string Reasons)[][] GetIncrementalGeneratorTrackedStepsReasons(string keyPrefixFilter, params string[] sources) { var parseOptions = new CSharpParseOptions(LanguageVersion.CSharp12); // 12 var driver = CSharpGeneratorDriver.Create( [new MasterMemoryGenerator().AsSourceGenerator()], driverOptions: new GeneratorDriverOptions(IncrementalGeneratorOutputKind.None, trackIncrementalGeneratorSteps: true)) .WithUpdatedParseOptions(parseOptions); var generatorResults = sources .Select(source => { var compilation = baseCompilation.AddSyntaxTrees(CSharpSyntaxTree.ParseText(source, parseOptions)); driver = driver.RunGenerators(compilation); return driver.GetRunResult().Results[0]; }) .ToArray(); var reasons = generatorResults .Select(x => x.TrackedSteps .Where(x => x.Key.StartsWith(keyPrefixFilter) || x.Key == "SourceOutput") .Select(x => { if (x.Key == "SourceOutput") { var values = x.Value.Where(x => x.Inputs[0].Source.Name?.StartsWith(keyPrefixFilter) ?? false); return ( x.Key, Reasons: string.Join(", ", values.SelectMany(x => x.Outputs).Select(x => x.Reason).ToArray()) ); } else { return ( Key: x.Key.Substring(keyPrefixFilter.Length), Reasons: string.Join(", ", x.Value.SelectMany(x => x.Outputs).Select(x => x.Reason).ToArray()) ); } }) .OrderBy(x => x.Key) .ToArray()) .ToArray(); return reasons; } } ================================================ FILE: tests/MasterMemory.SourceGenerator.Tests/Utility/CodeGeneratorHelper.cs ================================================ using Microsoft.CodeAnalysis; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; public class CodeGeneratorHelper(ITestOutputHelper output, string idPrefix) { // Diagnostics Verify public void Ok([StringSyntax("C#-test")] string code, [CallerArgumentExpression("code")] string? codeExpr = null) { output.WriteLine(codeExpr); var (compilation, diagnostics) = CSharpGeneratorRunner.RunGenerator(code); foreach (var item in diagnostics) { output.WriteLine(item.ToString()); } OutputGeneratedCode(compilation); diagnostics.Length.ShouldBe(0); } public Dictionary GenerateCode([StringSyntax("C#-test")] string code, [CallerArgumentExpression("code")] string? codeExpr = null) { var (compilation, diagnostics) = CSharpGeneratorRunner.RunGenerator(code); foreach (var item in diagnostics) { output.WriteLine(item.ToString()); } diagnostics.Length.ShouldBe(0); var dict = new Dictionary(); foreach (var syntaxTree in compilation.SyntaxTrees) { // only shows ConsoleApp.Run/Builder generated code if (!syntaxTree.FilePath.Contains("g.cs")) continue; var generatedCode = syntaxTree.ToString(); var fileName = Path.GetFileName(syntaxTree.FilePath); dict.Add(fileName, generatedCode); } return dict; } public void Verify(int id, [StringSyntax("C#-test")] string code, string diagnosticsCodeSpan, [CallerArgumentExpression("code")] string? codeExpr = null) { output.WriteLine(codeExpr); var (compilation, diagnostics) = CSharpGeneratorRunner.RunGenerator(code); foreach (var item in diagnostics) { output.WriteLine(item.ToString()); } OutputGeneratedCode(compilation); diagnostics.Length.ShouldBe(1); diagnostics[0].Id.ShouldBe(idPrefix + id.ToString("000")); var text = GetLocationText(diagnostics[0], compilation.SyntaxTrees); text.ShouldBe(diagnosticsCodeSpan); } public (string, string)[] Verify([StringSyntax("C#-test")] string code, [CallerArgumentExpression("code")] string? codeExpr = null) { output.WriteLine(codeExpr); var (compilation, diagnostics) = CSharpGeneratorRunner.RunGenerator(code); OutputGeneratedCode(compilation); return diagnostics.Select(x => (x.Id, GetLocationText(x, compilation.SyntaxTrees))).ToArray(); } // Execute and check stdout result public void Execute([StringSyntax("C#-test")] string code, string args, string expected, [CallerArgumentExpression("code")] string? codeExpr = null) { output.WriteLine(codeExpr); var (compilation, diagnostics, stdout) = CSharpGeneratorRunner.CompileAndExecute(code, args == "" ? [] : args.Split(' ')); foreach (var item in diagnostics) { output.WriteLine(item.ToString()); } OutputGeneratedCode(compilation); stdout.ShouldBe(expected); } public string Error([StringSyntax("C#-test")] string code, string args, [CallerArgumentExpression("code")] string? codeExpr = null) { output.WriteLine(codeExpr); var (compilation, diagnostics, stdout) = CSharpGeneratorRunner.CompileAndExecute(code, args == "" ? [] : args.Split(' ')); foreach (var item in diagnostics) { output.WriteLine(item.ToString()); } OutputGeneratedCode(compilation); return stdout; } string GetLocationText(Diagnostic diagnostic, IEnumerable syntaxTrees) { var location = diagnostic.Location; var textSpan = location.SourceSpan; var sourceTree = location.SourceTree; if (sourceTree == null) { var lineSpan = location.GetLineSpan(); if (lineSpan.Path == null) return ""; sourceTree = syntaxTrees.FirstOrDefault(x => x.FilePath == lineSpan.Path); if (sourceTree == null) return ""; } var text = sourceTree.GetText().GetSubText(textSpan).ToString(); return text; } void OutputGeneratedCode(Compilation compilation) { foreach (var syntaxTree in compilation.SyntaxTrees) { // only shows ConsoleApp.Run/Builder generated code if (!syntaxTree.FilePath.Contains("g.cs")) continue; output.WriteLine(syntaxTree.ToString()); } } } ================================================ FILE: tests/MasterMemory.Tests/BinarySearchTest.cs ================================================ using MasterMemory.Internal; using MessagePack.Resolvers; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; using MessagePack; namespace MasterMemory.Tests { public class BinarySearchTest { public BinarySearchTest() { MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions.WithResolver(MessagePackResolver.Instance); } [Fact] public void Find() { var rand = new Random(); for (int iii = 0; iii < 30; iii++) { var seed = Enumerable.Range(1, 10); var randomSeed = seed.Where(x => rand.Next() % 2 == 0); var array = randomSeed.Concat(randomSeed).Concat(randomSeed).Concat(randomSeed) .OrderBy(x => x) .ToArray(); for (int i = 1; i <= 10; i++) { var firstIndex = Array.IndexOf(array, i); var lastIndex = Array.LastIndexOf(array, i); var f = BinarySearch.FindFirst(array, i, x => x, Comparer.Default); var l = BinarySearch.LowerBound(array, 0, array.Length, i, x => x, Comparer.Default); var u = BinarySearch.UpperBound(array, 0, array.Length, i, x => x, Comparer.Default); // not found if (firstIndex == -1) { f.ShouldBe(-1); l.ShouldBe(-1); u.ShouldBe(-1); continue; } array[f].ShouldBe(i); array[l].ShouldBe(i); array[u].ShouldBe(i); l.ShouldBe(firstIndex); u.ShouldBe(lastIndex); } } // and empty var emptyArray = Enumerable.Empty().ToArray(); BinarySearch.FindFirst(emptyArray, 0, x => x, Comparer.Default).ShouldBe(-1); BinarySearch.LowerBound(emptyArray, 0, emptyArray.Length, 0, x => x, Comparer.Default).ShouldBe(-1); BinarySearch.UpperBound(emptyArray, 0, emptyArray.Length, 0, x => x, Comparer.Default).ShouldBe(-1); } [Fact] public void Closest() { // empty var array = Enumerable.Empty().ToArray(); var near = BinarySearch.FindClosest(array, 0, 0, array.Length, x => x, Comparer.Default, false); near.ShouldBe(-1); // mid var source = new[]{ new { id = 0, bound = 0 }, new { id = 1, bound = 100 }, new { id = 2, bound = 200 }, new { id = 3, bound = 300 }, new { id = 4, bound = 500 }, new { id = 5, bound = 1000 }, }; BinarySearch.FindClosest(source, 0, source.Length, -100, x => x.bound, Comparer.Default, true).ShouldBe(-1); // BinarySearch.FindClosest(source, 0, source.Length, -100, x => x.bound, Comparer.Default, true).ShouldBe(0); BinarySearch.FindClosest(source, 0, source.Length, 0, x => x.bound, Comparer.Default, true).ShouldBe(0); BinarySearch.FindClosest(source, 0, source.Length, 10, x => x.bound, Comparer.Default, true).ShouldBe(0); BinarySearch.FindClosest(source, 0, source.Length, 50, x => x.bound, Comparer.Default, true).ShouldBe(0); source[BinarySearch.FindClosest(source, 0, source.Length, 100, x => x.bound, Comparer.Default, true)].id.ShouldBe(1); source[BinarySearch.FindClosest(source, 0, source.Length, 100, x => x.bound, Comparer.Default, false)].id.ShouldBe(1); source[BinarySearch.FindClosest(source, 0, source.Length, 150, x => x.bound, Comparer.Default, true)].id.ShouldBe(1); source[BinarySearch.FindClosest(source, 0, source.Length, 300, x => x.bound, Comparer.Default, true)].id.ShouldBe(3); source[BinarySearch.FindClosest(source, 0, source.Length, 999, x => x.bound, Comparer.Default, true)].id.ShouldBe(4); source[BinarySearch.FindClosest(source, 0, source.Length, 1000, x => x.bound, Comparer.Default, true)].id.ShouldBe(5); source[BinarySearch.FindClosest(source, 0, source.Length, 1001, x => x.bound, Comparer.Default, true)].id.ShouldBe(5); source[BinarySearch.FindClosest(source, 0, source.Length, 10000, x => x.bound, Comparer.Default, true)].id.ShouldBe(5); // source[BinarySearch.FindClosest(source, 0, source.Length, 10000, x => x.bound, Comparer.Default, false)].id.ShouldBe(5); BinarySearch.FindClosest(source, 0, source.Length, 10000, x => x.bound, Comparer.Default, false).ShouldBe(6); } } } ================================================ FILE: tests/MasterMemory.Tests/DatabaseTest.cs ================================================ #pragma warning disable using MessagePack; using MessagePack.Resolvers; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; namespace MasterMemory.Tests { public class DatabaseTest { public DatabaseTest() { MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions.WithResolver(MessagePackResolver.Instance); } Sample[] CreateData() { // Id = Unique, PK // FirstName + LastName = Unique var data = new[] { new Sample { Id = 5, Age = 19, FirstName = "aaa", LastName = "foo" }, new Sample { Id = 6, Age = 29, FirstName = "bbb", LastName = "foo" }, new Sample { Id = 7, Age = 39, FirstName = "ccc", LastName = "foo" }, new Sample { Id = 8, Age = 49, FirstName = "ddd", LastName = "foo" }, new Sample { Id = 1, Age = 59, FirstName = "eee", LastName = "foo" }, new Sample { Id = 2, Age = 89, FirstName = "aaa", LastName = "bar" }, new Sample { Id = 3, Age = 79, FirstName = "be", LastName = "de" }, new Sample { Id = 4, Age = 89, FirstName = "aaa", LastName = "tako" }, new Sample { Id = 9, Age = 99, FirstName = "aaa", LastName = "ika" }, new Sample { Id = 10, Age = 9, FirstName = "eee", LastName = "baz" }, }; return data; } [Fact] public void SingleDb() { var builder = new DatabaseBuilder(); builder.Append(CreateData()); var bin = builder.Build(); var db = new MemoryDatabase(bin); db.SampleTable.FindById(8).Age.ShouldBe(49); var tableInfo = MemoryDatabase.GetTableInfo(bin); tableInfo[0].TableName.ShouldBe("s_a_m_p_l_e"); } [Fact] public void All() { var builder = new DatabaseBuilder(); builder.Append(CreateData()); var bin = builder.Build(); var db = new MemoryDatabase(bin); db.SampleTable.All.Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); db.SampleTable.AllReverse.Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.Reverse().ToArray()); db.SampleTable.SortByAge.Select(x => x.Id).OrderBy(x => x).ToArray().ShouldBeEquivalentTo(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); } [Fact] public void Ranges() { var builder = new DatabaseBuilder(); builder.Append(CreateData()); var bin = builder.Build(); var db = new MemoryDatabase(bin); db.SampleTable.FindRangeByAge(2, 2).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new int[] { }); db.SampleTable.FindRangeByAge(30, 50).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new int[] { 7, 8 }); db.SampleTable.FindRangeByAge(100, 100).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new int[] { }); } [Fact] public void EmptyAll() { { var builder = new DatabaseBuilder(); builder.Append(new Sample[] { }); var bin = builder.Build(); var db = new MemoryDatabase(bin); db.SampleTable.All.Any().ShouldBeFalse(); } { var builder = new DatabaseBuilder(); builder.Append(new Sample[] { }.Select(x => x)); var bin = builder.Build(); var db = new MemoryDatabase(bin); db.SampleTable.All.Any().ShouldBeFalse(); } } [Fact] public void WithNull() { var builder = new DatabaseBuilder(); builder.Append(new Sample[] {new Sample { Age = 10, FirstName = null, Id = 999, LastName = "abcde" } }); var bin = builder.Build(); var db = new MemoryDatabase(bin); var sample = db.SampleTable.FindById(999); sample.Age.ShouldBe(10); sample.FirstName.ShouldBeNull(); sample.LastName.ShouldBe("abcde"); } } } ================================================ FILE: tests/MasterMemory.Tests/IssueTest.cs ================================================ using MasterMemory.Tests.TestStructures; using System; using System.Collections.Generic; using System.Linq; using System.Text; using Xunit; namespace MasterMemory.Tests { public class IssueTest { //[Fact] //public void Issue49() //{ // var builder = new DatabaseBuilder().Append(new[] // { // new PersonModel { FirstName = "realname", LastName="reallast" }, // new PersonModel { FirstName = "fakefirst", LastName="fakelast" }, // }); // var data = builder.Build(); // var database = new MemoryDatabase(data); // var entries = database.PersonModelTable.FindClosestByFirstNameAndLastName(("real", "real"), false); // var firstEntry = entries.FirstOrDefault(); // var firstIs = firstEntry.FirstName; //} Sample[] CreateData() { // Id = Unique, PK // FirstName + LastName = Unique var data = new[] { new Sample { Id = 5, Age = 19, FirstName = "aaa", LastName = "foo" }, new Sample { Id = 6, Age = 29, FirstName = "bbb", LastName = "foo" }, new Sample { Id = 7, Age = 39, FirstName = "ccc", LastName = "foo" }, new Sample { Id = 8, Age = 49, FirstName = "ddd", LastName = "foo" }, new Sample { Id = 1, Age = 59, FirstName = "eee", LastName = "foo" }, new Sample { Id = 2, Age = 89, FirstName = "aaa", LastName = "bar" }, new Sample { Id = 3, Age = 79, FirstName = "be", LastName = "de" }, new Sample { Id = 4, Age = 89, FirstName = "aaa", LastName = "tako" }, new Sample { Id = 9, Age = 99, FirstName = "aaa", LastName = "ika" }, new Sample { Id = 10, Age = 9, FirstName = "eee", LastName = "baz" }, }; return data; } [Fact] public void Issue57() { var builder = new DatabaseBuilder(); builder.Append(CreateData()); var bin = builder.Build(); var db = new MemoryDatabase(bin); db.SampleTable.FindRangeByAge(2, 2).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new int[] { }); db.SampleTable.FindRangeByAge(30, 50).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new int[] { 7, 8 }); db.SampleTable.FindRangeByAge(100, 100).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new int[] { }); } } } ================================================ FILE: tests/MasterMemory.Tests/MasterMemory.Tests.csproj ================================================  net9.0 Analyzer false ================================================ FILE: tests/MasterMemory.Tests/MemoryKeyTest.cs ================================================ #pragma warning disable using Xunit; using System.Linq; using MasterMemory.Tests.Tables; using MessagePack; using System.Collections.Generic; namespace MasterMemory.Tests { public class MemoryKeyMemoryTest { public MemoryKeyMemoryTest() { MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions.WithResolver(MessagePackResolver.Instance); } Sample[] CreateData() { // Id = Unique, PK // FirstName + LastName = Unique var data = new[] { new Sample { Id = 5, Age = 19, FirstName = "aaa", LastName = "foo" }, new Sample { Id = 6, Age = 29, FirstName = "bbb", LastName = "foo" }, new Sample { Id = 7, Age = 39, FirstName = "ccc", LastName = "foo" }, new Sample { Id = 8, Age = 49, FirstName = "ddd", LastName = "foo" }, new Sample { Id = 1, Age = 59, FirstName = "eee", LastName = "foo" }, new Sample { Id = 2, Age = 89, FirstName = "aaa", LastName = "bar" }, new Sample { Id = 3, Age = 79, FirstName = "be", LastName = "de" }, new Sample { Id = 4, Age = 89, FirstName = "aaa", LastName = "tako" }, new Sample { Id = 9, Age = 99, FirstName = "aaa", LastName = "ika" }, new Sample { Id = 10, Age = 9, FirstName = "eee", LastName = "baz" }, }; return data; } SampleTable CreateTable() { return new MemoryDatabase(new DatabaseBuilder().Append(CreateData()).Build()).SampleTable; } [Fact] public void Unique() { var table = CreateTable(); table.FindById(8).Id.ShouldBe(8); Assert.Throws(() => table.FindById(100)); table.FindByIdAndAge((4, 89)).Id.ShouldBe(4); Assert.Throws(() => table.FindByIdAndAge((4, 899))); Assert.Throws(() => table.FindByIdAndAge((5, 89))); table.FindByIdAndAgeAndFirstName((6, 29, "bbb")).Id.ShouldBe(6); Assert.Throws(() => table.FindByIdAndAgeAndFirstName((6, 29, "bbbz"))); } [Fact] public void Range() { var table = CreateTable(); var test = table.FindByFirstName("eee"); table.FindByFirstName("eee").Select(x => x.Id).OrderBy(x => x).ToArray().ShouldBeEquivalentTo(new[] { 1, 10 }); table.FindByFirstName("eeee").Count.ShouldBe(0); table.FindClosestByFirstNameAndLastName(("aaa", "takz")).Id.ShouldBe(4); } } } ================================================ FILE: tests/MasterMemory.Tests/MemoryTest.cs ================================================ using MasterMemory.Tests.Tables; using MessagePack; using System.Collections.Generic; using System.Linq; using Xunit; namespace MasterMemory.Tests { public class MemoryTest { public MemoryTest() { MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions.WithResolver(MessagePackResolver.Instance); } Sample[] CreateData() { // Id = Unique, PK // FirstName + LastName = Unique var data = new[] { new Sample { Id = 5, Age = 19, FirstName = "aaa", LastName = "foo" }, new Sample { Id = 6, Age = 29, FirstName = "bbb", LastName = "foo" }, new Sample { Id = 7, Age = 39, FirstName = "ccc", LastName = "foo" }, new Sample { Id = 8, Age = 49, FirstName = "ddd", LastName = "foo" }, new Sample { Id = 1, Age = 59, FirstName = "eee", LastName = "foo" }, new Sample { Id = 2, Age = 89, FirstName = "aaa", LastName = "bar" }, new Sample { Id = 3, Age = 79, FirstName = "be", LastName = "de" }, new Sample { Id = 4, Age = 89, FirstName = "aaa", LastName = "tako" }, new Sample { Id = 9, Age = 99, FirstName = "aaa", LastName = "ika" }, new Sample { Id = 10, Age = 9, FirstName = "eee", LastName = "baz" }, }; return data; } SampleTable CreateTable(Sample[] data) { return new MemoryDatabase(new DatabaseBuilder().Append(data).Build()).SampleTable; } [Fact] public void Count() { var data = CreateData(); var table = CreateTable(data); table.Count.ShouldBe(data.Length); } [Fact] public void Find() { var data = CreateData(); var table = CreateTable(data); foreach (var item in data) { var f = table.FindById(item.Id); item.Id.ShouldBe(f.Id); } Assert.Throws(() => table.FindById(110)); table.TryFindById(110, out _).ShouldBeFalse(); } [Fact] public void MultiKeyFind() { var data = CreateData(); var table = CreateTable(data); foreach (var item in data) { var f = table.FindByFirstNameAndLastName((item.FirstName, item.LastName)); item.Id.ShouldBe(f.Id); } Assert.Throws(() => table.FindByFirstNameAndLastName(("aaa", "___"))); Assert.Throws(() => table.FindByFirstNameAndLastName(("___", "foo"))); table.TryFindByFirstNameAndLastName(("aaa", "___"), out _).ShouldBeFalse(); table.TryFindByFirstNameAndLastName(("___", "foo"), out _).ShouldBeFalse(); } [Fact] public void FindClosest() { var data = CreateData(); var table = CreateTable(data); { table.FindClosestByAge(56, true).First.Age.ShouldBe(49); table.FindClosestByAge(56, false).First.Age.ShouldBe(59); } { // first for (int i = 0; i < 9; i++) { table.FindClosestByAge(i, selectLower: true).Count.ShouldBe(0); // table.FindClosestByAge(i, selectLower: true).First.Age.ShouldBe(9); } var lastAge = 9; foreach (var item in data.OrderBy(x => x.Age)) { for (int i = lastAge + 1; i < item.Age; i++) { table.FindClosestByAge(i, selectLower: true).First.Age.ShouldBe(lastAge); } lastAge = item.Age; } // last table.FindClosestByAge(99, selectLower: false).First.Age.ShouldBe(99); for (int i = 100; i < 120; i++) { table.FindClosestByAge(i, selectLower: false).Count.ShouldBe(0); // table.FindClosestByAge(i, selectLower: true).First.Age.ShouldBe(99); } } { // first for (int i = 0; i < 9; i++) { table.FindClosestByAge(i, selectLower: false).First.Age.ShouldBe(9); } var xss = data.OrderBy(x => x.Age).ToArray(); for (int j = 1; j < xss.Length - 1; j++) { var item = xss[j]; for (int i = xss[j - 1].Age + 1; i < item.Age; i++) { table.FindClosestByAge(i, selectLower: false).First.Age.ShouldBe(xss[j].Age); } } // last table.FindClosestByAge(99, selectLower: false).First.Age.ShouldBe(99); for (int i = 100; i < 120; i++) { table.FindClosestByAge(i, selectLower: false).Count.ShouldBe(0); } } } [Fact] public void FindClosestMultiKey() { var data = CreateData(); var table = CreateTable(data); // Age of aaa //new Sample { Id = 5, Age = 19, FirstName = "aaa", LastName = "foo" }, //new Sample { Id = 2, Age = 89, FirstName = "aaa", LastName = "bar" }, //new Sample { Id = 4, Age = 89, FirstName = "aaa", LastName = "tako" }, //new Sample { Id = 9, Age = 99, FirstName = "aaa", LastName = "ika" }, table.FindClosestByFirstNameAndAge(("aaa", 10), true).Count.ShouldBe(0); table.FindClosestByFirstNameAndAge(("aaa", 10), false).First.Age.ShouldBe(19); table.FindClosestByFirstNameAndAge(("aaa", 92), true).First.Age.ShouldBe(89); table.FindClosestByFirstNameAndAge(("aaa", 120), true).First.Age.ShouldBe(99); table.FindClosestByFirstNameAndAge(("aaa", 10), false).First.Age.ShouldBe(19); table.FindClosestByFirstNameAndAge(("aaa", 73), false).First.Age.ShouldBe(89); } [Fact] public void FindMany() { var data = CreateData(); var table = CreateTable(data); table.FindByFirstName("aaa").OrderBy(x => x.Id).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new[] { 2, 4, 5, 9 }); } [Fact] public void FindManyMultiKey() { var data = CreateData(); var table = CreateTable(data); table.FindByFirstNameAndAge(("aaa", 89)).Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new[] { 2, 4 }); table.FindByFirstNameAndAge(("aaa", 89)).Reverse.Select(x => x.Id).ToArray().ShouldBeEquivalentTo(new[] { 4, 2 }); } } } ================================================ FILE: tests/MasterMemory.Tests/MessagePackResolver.cs ================================================ using MessagePack; using MessagePack.Resolvers; namespace MasterMemory.Tests; [CompositeResolver(typeof(MasterMemoryResolver), typeof(StandardResolver))] public partial class MessagePackResolver; ================================================ FILE: tests/MasterMemory.Tests/MetaTest.cs ================================================ #pragma warning disable using System; using System.Collections.Generic; using System.Text; using Xunit; namespace MasterMemory.Tests { public class MetaTest { [Fact] public void Meta() { var metaDb = MemoryDatabase.GetMetaDatabase(); var sampleTable = metaDb.GetTableInfo("s_a_m_p_l_e"); sampleTable.TableName.ShouldBe("s_a_m_p_l_e"); sampleTable.Properties[0].Name.ShouldBe("Id"); sampleTable.Properties[0].NameLowerCamel.ShouldBe("id"); sampleTable.Properties[0].NameSnakeCase.ShouldBe("id"); sampleTable.Properties[2].Name.ShouldBe("FirstName"); sampleTable.Properties[2].NameLowerCamel.ShouldBe("firstName"); sampleTable.Properties[2].NameSnakeCase.ShouldBe("first_name"); var primary = sampleTable.Indexes[0]; primary.IsUnique.ShouldBeTrue(); primary.IndexProperties[0].Name.ShouldBe("Id"); } } } ================================================ FILE: tests/MasterMemory.Tests/RangeViewTest.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Xunit; namespace MasterMemory.Tests { public class RangeViewTest { [Fact] public void Range() { // 4 -> 8 { var range = new RangeView(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 4, 8, true); range.Count.ShouldBe(5); range[0].ShouldBe(4); range[1].ShouldBe(5); range[2].ShouldBe(6); range[3].ShouldBe(7); range[4].ShouldBe(8); Assert.Throws(() => range[-1]); Assert.Throws(() => range[5]); var begin = 4; foreach (var item in range) { item.ShouldBe(begin++); } var xs = new int[10]; range.CopyTo(xs, 3); xs[3].ShouldBe(4); xs[4].ShouldBe(5); xs[5].ShouldBe(6); xs[6].ShouldBe(7); xs[7].ShouldBe(8); xs[8].ShouldBe(0); range.IndexOf(5).ShouldBe(1); range.IndexOf(9).ShouldBe(-1); range.Contains(5).ShouldBeTrue(); range.Contains(9).ShouldBeFalse(); } { // 7 -> 2 var range = new RangeView(new[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, 2, 7, false); range.Count.ShouldBe(6); range[0].ShouldBe(7); range[1].ShouldBe(6); range[2].ShouldBe(5); range[3].ShouldBe(4); range[4].ShouldBe(3); range[5].ShouldBe(2); Assert.Throws(() => range[-1]); Assert.Throws(() => range[6]); var begin = 7; foreach (var item in range) { item.ShouldBe(begin--); } var xs = new int[10]; range.CopyTo(xs, 3); xs[3].ShouldBe(7); xs[4].ShouldBe(6); xs[5].ShouldBe(5); xs[6].ShouldBe(4); xs[7].ShouldBe(3); xs[8].ShouldBe(2); range.IndexOf(5).ShouldBe(2); range.IndexOf(9).ShouldBe(-1); range.Contains(5).ShouldBeTrue(); range.Contains(9).ShouldBeFalse(); } var empty = new RangeView(Enumerable.Empty().ToArray(), 0, 0, true); empty.Count.ShouldBe(0); var same = new RangeView(Enumerable.Range(1, 1000).ToArray(), 100, 100, true); same.Count.ShouldBe(1); same[0].ShouldBe(101); } } } ================================================ FILE: tests/MasterMemory.Tests/TestStructures/PersonModel.cs ================================================ #pragma warning disable using MessagePack; using System; using System.Collections.Generic; using System.Text; namespace MasterMemory.Tests.TestStructures { [MemoryTable("people"), MessagePackObject(true)] public class PersonModel { [SecondaryKey(0), NonUnique] [SecondaryKey(1, keyOrder: 1), NonUnique] public string LastName { get; set; } [SecondaryKey(2), NonUnique] [SecondaryKey(1, keyOrder: 0), NonUnique] public string FirstName { get; set; } [PrimaryKey] public string RandomId { get; set; } } } ================================================ FILE: tests/MasterMemory.Tests/TestStructures/QuestMaster.cs ================================================ #pragma warning disable using MessagePack; using System; using System.Collections.Generic; using System.Text; namespace MasterMemory.Tests.TestStructures { [MemoryTable("quest_master"), MessagePackObject(true)] public class QuestMaster : IValidatable { [PrimaryKey] public int QuestId { get; set; } public string Name { get; set; } public int RewardItemId { get; set; } public int Cost { get; set; } public void Validate(IValidator validator) { var itemMaster = validator.GetReferenceSet(); itemMaster.Exists(x => x.RewardItemId, x => x.ItemId); validator.Validate(x => x.Cost <= 100); validator.Validate(x => x.Cost >= 0, ">= 0!!!"); validator.ValidateAction(() => this.Cost <= 1000); validator.ValidateAction(() => this.Cost >= -90, ">= -90!!!"); if (validator.CallOnce()) { var quests = validator.GetTableSet(); quests.Unique(x => x.Name); } } } [MemoryTable("item_master"), MessagePackObject(true)] public class ItemMaster : IValidatable { [PrimaryKey] public int ItemId { get; set; } public void Validate(IValidator validator) { } } [MemoryTable("quest_master_empty"), MessagePackObject(true)] public class QuestMasterEmptyValidate { [PrimaryKey] public int QuestId { get; set; } public string Name { get; set; } public int RewardItemId { get; set; } public int Cost { get; set; } } [MemoryTable("item_master_empty"), MessagePackObject(true)] public class ItemMasterEmptyValidate { [PrimaryKey] public int ItemId { get; set; } } [MemoryTable("sequantial_master"), MessagePackObject(true)] public class SequentialCheckMaster : IValidatable { [PrimaryKey] public int Id { get; set; } public int Cost { get; set; } public void Validate(IValidator validator) { if (validator.CallOnce()) { var set = validator.GetTableSet(); set.Sequential(x => x.Id); set.Sequential(x => x.Cost, true); } } } [MemoryTable("single_master"), MessagePackObject(true)] public class SingleMaster : IValidatable { public static int CalledValidateCount; public static int CalledOnceCount; [PrimaryKey] public int Id { get; set; } public void Validate(IValidator validator) { CalledValidateCount++; if (validator.CallOnce()) { CalledOnceCount++; } } } [MemoryTable("fail"), MessagePackObject(true)] public class Fail : IValidatable { [PrimaryKey] public int Id { get; set; } public void Validate(IValidator validator) { validator.Fail("Failed Id:" + Id); } } } ================================================ FILE: tests/MasterMemory.Tests/TestStructures/Sample.cs ================================================ #pragma warning disable using MessagePack; namespace MasterMemory.Tests { [MemoryTable("s_a_m_p_l_e"), MessagePackObject(true)] public class Sample { [PrimaryKey] [SecondaryKey(1)] [SecondaryKey(2)] [SecondaryKey(3)] public int Id { get; set; } [SecondaryKey(1)] [SecondaryKey(2)] [SecondaryKey(3)] [SecondaryKey(5), NonUnique] [SecondaryKey(6, 1), NonUnique] public int Age { get; set; } [SecondaryKey(0)] [SecondaryKey(1)] [SecondaryKey(3)] [SecondaryKey(4), NonUnique] [SecondaryKey(6, 0), NonUnique] public string FirstName { get; set; } [SecondaryKey(0)] [SecondaryKey(1)] public string LastName { get; set; } [MessagePack.IgnoreMember] public int Hoge { get; set; } [System.Runtime.Serialization.IgnoreDataMember] public int Huga { get; set; } public override string ToString() { return $"{Id} {Age} {FirstName} {LastName}"; } public Sample() { } public Sample(int Id, int Age, string FirstName, string LastName) { this.Id = Id; this.Age = Age; this.FirstName = FirstName; this.LastName = LastName; } } } ================================================ FILE: tests/MasterMemory.Tests/TestStructures/SkillMaster.cs ================================================ #pragma warning disable using MessagePack; namespace MasterMemory.Tests { [MemoryTable("skillmaster"), MessagePackObject(true)] public class SkillMaster { [PrimaryKey] public int SkillId { get; set; } [PrimaryKey] public int SkillLevel { get; set; } public int AttackPower { get; set; } public string SkillName { get; set; } public string Description { get; set; } public SkillMaster() { } public SkillMaster(int SkillId, int SkillLevel, int AttackPower, string SkillName, string Description) { this.SkillId = SkillId; this.SkillLevel = SkillLevel; this.AttackPower = AttackPower; this.SkillName = SkillName; this.Description = Description; } } } ================================================ FILE: tests/MasterMemory.Tests/TestStructures/TestMaster.cs ================================================ using MessagePack; using System; using System.Collections.Generic; using System.Text; namespace MasterMemory.Tests.TestStructures { [MessagePackObject(true)] [MemoryTable(nameof(TestMaster))] public class TestMaster { [PrimaryKey, NonUnique] public int TestID { get; set; } public int Value { get; set; } public TestMaster(int TestID, int Value) { this.TestID = TestID; this.Value = Value; } } } ================================================ FILE: tests/MasterMemory.Tests/TestStructures/UserLevel.cs ================================================ using MessagePack; namespace MasterMemory.Tests { [MemoryTable("UserLevel"), MessagePackObject(true)] public class UserLevel { [PrimaryKey] public int Level { get; set; } [SecondaryKey(0)] public int Exp { get; set; } public UserLevel() { } public UserLevel(int Level, int Exp) { this.Level = Level; this.Exp = Exp; } } } ================================================ FILE: tests/MasterMemory.Tests/ValidatorTest.cs ================================================ using MasterMemory.Tests.TestStructures; using MessagePack; using System; using System.Collections.Generic; using System.Text; using Xunit; using System.Linq; namespace MasterMemory.Tests { public class ValidatorTest { readonly Xunit.Abstractions.ITestOutputHelper output; #if UNITY_2018_3_OR_NEWER public ValidatorTest() { this.output = new Xunit.Abstractions.DebugLogTestOutputHelper(); MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions.WithResolver(MessagePackResolver.Instance); } #else public ValidatorTest(Xunit.Abstractions.ITestOutputHelper output) { this.output = output; MessagePackSerializer.DefaultOptions = MessagePackSerializer.DefaultOptions.WithResolver(MessagePackResolver.Instance); } #endif MemoryDatabase CreateDatabase(Fail[] data1) { var bin = new DatabaseBuilder() .Append(data1) .Build(); return new MemoryDatabase(bin, internString: false); } MemoryDatabase CreateDatabase(SingleMaster[] data1) { var bin = new DatabaseBuilder() .Append(data1) .Build(); return new MemoryDatabase(bin, internString: false); } MemoryDatabase CreateDatabase(SequentialCheckMaster[] data1) { var bin = new DatabaseBuilder() .Append(data1) .Build(); return new MemoryDatabase(bin, internString: false); } MemoryDatabase CreateDatabase(QuestMaster[] data1, ItemMaster[] data2) { var bin = new DatabaseBuilder() .Append(data1) .Append(data2) .Build(); return new MemoryDatabase(bin, internString: false); } MemoryDatabase CreateDatabase(QuestMasterEmptyValidate[] data1, ItemMasterEmptyValidate[] data2) { var bin = new DatabaseBuilder() .Append(data1) .Append(data2) .Build(); return new MemoryDatabase(bin, internString: false); } [Fact] public void Empty() { var validateResult = CreateDatabase(new QuestMaster[] { }, new ItemMaster[] { }).Validate(); validateResult.IsValidationFailed.ShouldBeFalse(); validateResult.FailedResults.Count.ShouldBe(0); } [Fact] public void PKUnique() { var validateResult = CreateDatabase(new QuestMasterEmptyValidate[] { new QuestMasterEmptyValidate { QuestId = 1 }, new QuestMasterEmptyValidate { QuestId = 2 }, new QuestMasterEmptyValidate { QuestId = 1 }, new QuestMasterEmptyValidate { QuestId = 4 }, new QuestMasterEmptyValidate { QuestId = 4 }, }, new ItemMasterEmptyValidate[] { new ItemMasterEmptyValidate { ItemId = 1 }, new ItemMasterEmptyValidate { ItemId = 2 }, new ItemMasterEmptyValidate { ItemId = 2 }, }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeTrue(); validateResult.FailedResults.Count.ShouldBe(3); // Q:1,4 + I:2 var faileds = validateResult.FailedResults.OrderBy(x => x.Message).ToArray(); faileds[0].Message.ShouldBe("Unique failed: ItemId, value = 2"); faileds[1].Message.ShouldBe("Unique failed: QuestId, value = 1"); faileds[2].Message.ShouldBe("Unique failed: QuestId, value = 4"); } // test IValidator /* public interface IValidator { ValidatableSet GetTableSet(); ReferenceSet GetReferenceSet(); void Validate(Expression> predicate); void Validate(Func predicate, string message); void ValidateAction(Expression> predicate); void ValidateAction(Func predicate, string message); void Fail(string message); bool CallOnce(); } ReferenceSet.Exists ValidatableSet.Unique ValidatableSet.Sequential */ [Fact] public void Exists() { var validateResult = CreateDatabase(new QuestMaster[] { new QuestMaster { QuestId = 1, RewardItemId = 1, Name = "foo" }, new QuestMaster { QuestId = 2, RewardItemId = 3, Name = "bar" }, new QuestMaster { QuestId = 3, RewardItemId = 2, Name = "baz" }, new QuestMaster { QuestId = 4, RewardItemId = 5, Name = "tako"}, new QuestMaster { QuestId = 5, RewardItemId = 4, Name = "nano"}, }, new ItemMaster[] { new ItemMaster { ItemId = 1 }, new ItemMaster { ItemId = 2 }, new ItemMaster { ItemId = 3 }, }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeTrue(); validateResult.FailedResults[0].Message.ShouldBe("Exists failed: QuestMaster.RewardItemId -> ItemMaster.ItemId, value = 5, PK(QuestId) = 4"); validateResult.FailedResults[1].Message.ShouldBe("Exists failed: QuestMaster.RewardItemId -> ItemMaster.ItemId, value = 4, PK(QuestId) = 5"); } [Fact] public void Unique() { var validateResult = CreateDatabase(new QuestMaster[] { new QuestMaster { QuestId = 1, Name = "foo" }, new QuestMaster { QuestId = 2, Name = "bar" }, new QuestMaster { QuestId = 3, Name = "bar" }, new QuestMaster { QuestId = 4, Name = "tako" }, new QuestMaster { QuestId = 5, Name = "foo" }, }, new ItemMaster[] { new ItemMaster { ItemId = 0 } }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeTrue(); validateResult.FailedResults[0].Message.ShouldBe("Unique failed: .Name, value = bar, PK(QuestId) = 3"); validateResult.FailedResults[1].Message.ShouldBe("Unique failed: .Name, value = foo, PK(QuestId) = 5"); } [Fact] public void Sequential() { { var validateResult = CreateDatabase(new SequentialCheckMaster[] { new SequentialCheckMaster { Id = 1, Cost = 10 }, new SequentialCheckMaster { Id = 2, Cost = 11 }, new SequentialCheckMaster { Id = 3, Cost = 11 }, new SequentialCheckMaster { Id = 4, Cost = 12 }, }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeFalse(); } { var validateResult = CreateDatabase(new SequentialCheckMaster[] { new SequentialCheckMaster { Id = 1, Cost = 10 }, new SequentialCheckMaster { Id = 2, Cost = 11 }, new SequentialCheckMaster { Id = 3, Cost = 11 }, new SequentialCheckMaster { Id = 5, Cost = 13 }, }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeTrue(); validateResult.FailedResults[0].Message.ShouldBe("Sequential failed: .Id, value = (3, 5), PK(Id) = 5"); validateResult.FailedResults[1].Message.ShouldBe("Sequential failed: .Cost, value = (11, 13), PK(Id) = 5"); } } [Fact] public void CallOnce() { _ = CreateDatabase(new SingleMaster[] { new SingleMaster { Id = 1}, new SingleMaster { Id = 2}, new SingleMaster { Id = 3}, new SingleMaster { Id = 4}, }).Validate(); SingleMaster.CalledValidateCount.ShouldBe(4); SingleMaster.CalledOnceCount.ShouldBe(1); } [Fact] public void Validate() { var validateResult = CreateDatabase(new QuestMaster[] { new QuestMaster { QuestId = 1, RewardItemId = 1, Name = "foo", Cost = -1 }, new QuestMaster { QuestId = 2, RewardItemId = 3, Name = "bar", Cost = 99 }, new QuestMaster { QuestId = 3, RewardItemId = 2, Name = "baz", Cost = 100 }, new QuestMaster { QuestId = 4, RewardItemId = 3, Name = "tao", Cost = 101 }, new QuestMaster { QuestId = 5, RewardItemId = 3, Name = "nao", Cost = 33 }, }, new ItemMaster[] { new ItemMaster { ItemId = 1 }, new ItemMaster { ItemId = 2 }, new ItemMaster { ItemId = 3 }, }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeTrue(); validateResult.FailedResults[0].Message.ShouldBe("Validate failed: >= 0!!!, PK(QuestId) = 1"); validateResult.FailedResults[1].Message.ShouldBe("Validate failed: (this.Cost <= 100), Cost = 101, PK(QuestId) = 4"); } [Fact] public void ValidateAction() { var validateResult = CreateDatabase(new QuestMaster[] { new QuestMaster { QuestId = 1, RewardItemId = 1, Name = "foo", Cost = -100 }, new QuestMaster { QuestId = 2, RewardItemId = 3, Name = "bar", Cost = 99 }, new QuestMaster { QuestId = 3, RewardItemId = 2, Name = "baz", Cost = 100 }, new QuestMaster { QuestId = 4, RewardItemId = 3, Name = "tao", Cost = 1001 }, new QuestMaster { QuestId = 5, RewardItemId = 3, Name = "nao", Cost = 33 }, }, new ItemMaster[] { new ItemMaster { ItemId = 1 }, new ItemMaster { ItemId = 2 }, new ItemMaster { ItemId = 3 }, }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeTrue(); var results = validateResult.FailedResults.Select(x => x.Message).Where(x => x.Contains("ValidateAction faile")).ToArray(); results[0].ShouldBe("ValidateAction failed: >= -90!!!, PK(QuestId) = 1"); results[1].ShouldBe("ValidateAction failed: (value(MasterMemory.Tests.TestStructures.QuestMaster).Cost <= 1000), PK(QuestId) = 4"); } [Fact] public void Fail() { var validateResult = CreateDatabase(new Fail[] { new Fail { Id = 1}, new Fail { Id = 2}, new Fail { Id = 3}, }).Validate(); output.WriteLine(validateResult.FormatFailedResults()); validateResult.IsValidationFailed.ShouldBeTrue(); var msg = validateResult.FailedResults.Select(x => x.Message).ToArray(); msg[0].ShouldBe("Failed Id:1, PK(Id) = 1"); msg[1].ShouldBe("Failed Id:2, PK(Id) = 2"); msg[2].ShouldBe("Failed Id:3, PK(Id) = 3"); } } }