Merge branch 'main' into webSupport

# Conflicts:
#	Projects/Benchmarks/Benchmarks/Packets/GumpUtilities.cs
#	Projects/Server/Gumps/Gump.cs
#	Projects/Server/Network/Packets/IncomingPlayerPackets.cs
#	Projects/Server/Network/Packets/OutgoingGumpPackets.cs
This commit is contained in:
Kamron Batman 2022-07-08 16:09:07 -07:00
commit 217c63a07a
No known key found for this signature in database
GPG key ID: 5C9DFD15804B6BB8
2215 changed files with 59074 additions and 66412 deletions

12
.config/dotnet-tools.json Normal file
View file

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"modernuoschemagenerator": {
"version": "2.1.4",
"commands": [
"ModernUOSchemaGenerator"
]
}
}
}

View file

@ -14,10 +14,10 @@ jobs:
fail-fast: false
matrix:
include:
- os: macos-latest
name: MacOS 10
- os: macos-11
name: MacOS 11
- os: macos-12
name: MacOS 12
steps:
- uses: actions/checkout@v2
@ -26,7 +26,7 @@ jobs:
- name: Setup .NET 6
uses: actions/setup-dotnet@v1
with:
dotnet-version: 6.0.100
dotnet-version: 6.0.300
- name: Build
run: ./publish.cmd
- name: Test

View file

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v2
with:
fetch-depth: 0 # avoid shallow clone so nbgv can do its work.
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.WORKFLOW_TOKEN }}
- name: Install NGBV
uses: dotnet/nbgv@master
id: nbgv
@ -31,7 +31,11 @@ jobs:
git checkout ${{ steps.last_release.outputs.release }}
git tag -fa v0.1.0 -m "Fake release"
git checkout -
git push --follow-tags
- name: Push git changes
uses: ad-m/github-push-action@master
with:
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
tags: true
- name: Conventional Changelog
id: changelog
uses: TriPSs/conventional-changelog-action@v3
@ -49,14 +53,14 @@ jobs:
delete_release: true
tag_name: v${{ steps.changelog.outputs.version }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
- name: Delete v0.1.0 Tag
uses: dev-drprasad/delete-tag-and-release@v0.1.3
with:
delete_release: true
tag_name: v0.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
@ -67,4 +71,4 @@ jobs:
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.WORKFLOW_TOKEN }}

2
.gitignore vendored
View file

@ -3,6 +3,7 @@
/Distribution/ModernUO.*
/Distribution/Assemblies
/Distribution/bsdtar
/Distribution/Configuration/assistants.json
/Distribution/Configuration/modernuo.json
/Distribution/Configuration/email-settings.json
/Distribution/Configuration/throttles.json
@ -32,3 +33,4 @@
.DS_Store
/packages/*
/Distribution/Configuration/server-access.json

View file

@ -28,6 +28,7 @@
<DefineConstants Condition="'$(SkipLocalsInitAttribute)'=='true'">NO_LOCAL_INIT</DefineConstants>
<DefineConstants>MUO</DefineConstants>
<GitVersionBaseDirectory>$(SolutionDir)</GitVersionBaseDirectory>
<PredefinedCulturesOnly>false</PredefinedCulturesOnly>
</PropertyGroup>
<PropertyGroup Condition="'$(RuntimeIdentifier)'==''">
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
@ -54,13 +55,13 @@
<AnalysisLevel>latest</AnalysisLevel>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog" Version="2.11.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Nerdbank.GitVersioning" Condition="!Exists('packages.config')">
<Version>3.4.244</Version>
<Version>3.5.107</Version>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<AdditionalFiles Include=".\Rules.ruleset" />
<AdditionalFiles Include="..\..\Rules.ruleset" />
</ItemGroup>
</Project>

View file

@ -770,4 +770,4 @@ Static 0x1F3C
# Arch Cure
Static 0x1F45
5304 88 19
5304 88 19

View file

@ -3337,7 +3337,6 @@
{ "type": "DiseasedCat" },
{ "type": "Doppleganger" },
{ "type": "Drithen" },
{ "type": "DummyThief" },
{ "type": "Efreet" },
{ "type": "ElderAbbein" },
{ "type": "ElderAcob" },
@ -3630,19 +3629,6 @@
{ "type": "SeaSerpent" }
]
},
{
"category": "Mobiles.Dummies",
"objects": [
{ "type": "DummyAssassin" },
{ "type": "DummyFence" },
{ "type": "DummyHealer" },
{ "type": "DummyMace" },
{ "type": "DummyNox" },
{ "type": "DummyStun" },
{ "type": "DummySuper" },
{ "type": "DummySword" }
]
},
{
"category": "Mobiles.Escortables",
"objects": [
@ -3682,8 +3668,7 @@
{
"category": "Mobiles.Uncategorized",
"objects": [
{ "type": "TownCrier" },
{ "type": "Wanderer" }
{ "type": "TownCrier" }
]
},
{

View file

@ -4,7 +4,6 @@
"ClientVersion": null,
"ClientFlags": null,
"FeatureFlags": {
"None": true,
"T2A": false,
"UOR": false,
"UOTD": false,
@ -13,7 +12,7 @@
"SixthCharacterSlot": false,
"SE": false,
"ML": false,
"EigthAge": false,
"EighthAge": false,
"NinthAge": false,
"TenthAge": false,
"IncreasedStorage": false,
@ -31,7 +30,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": true,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -50,7 +48,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": true,
"AOS": false,
"SE": false,
"ML": false,
@ -70,7 +67,6 @@
"ClientVersion": null,
"ClientFlags": "Felucca",
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": false,
"UOTD": false,
@ -79,7 +75,7 @@
"SixthCharacterSlot": false,
"SE": false,
"ML": false,
"EigthAge": false,
"EighthAge": false,
"NinthAge": false,
"TenthAge": false,
"IncreasedStorage": false,
@ -97,7 +93,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -116,7 +111,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": true,
"AOS": false,
"SE": false,
"ML": false,
@ -136,7 +130,6 @@
"ClientVersion": null,
"ClientFlags": "Trammel",
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": false,
@ -145,7 +138,7 @@
"SixthCharacterSlot": false,
"SE": false,
"ML": false,
"EigthAge": false,
"EighthAge": false,
"NinthAge": false,
"TenthAge": false,
"IncreasedStorage": false,
@ -163,7 +156,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -182,7 +174,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": true,
"AOS": false,
"SE": false,
"ML": false,
@ -202,7 +193,6 @@
"ClientVersion": null,
"ClientFlags": "Ilshenar",
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
@ -211,7 +201,7 @@
"SixthCharacterSlot": false,
"SE": false,
"ML": false,
"EigthAge": false,
"EighthAge": false,
"NinthAge": false,
"TenthAge": false,
"IncreasedStorage": false,
@ -229,7 +219,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -248,7 +237,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": true,
"AOS": false,
"SE": false,
"ML": false,
@ -268,7 +256,6 @@
"ClientVersion": null,
"ClientFlags": "Ilshenar",
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
@ -277,7 +264,7 @@
"SixthCharacterSlot": false,
"SE": false,
"ML": false,
"EigthAge": false,
"EighthAge": false,
"NinthAge": false,
"TenthAge": false,
"IncreasedStorage": false,
@ -295,7 +282,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -314,7 +300,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": true,
"AOS": false,
"SE": false,
"ML": false,
@ -334,16 +319,15 @@
"ClientVersion": null,
"ClientFlags": "Malas",
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
"UOTD": false,
"LBR": true,
"AOS": true,
"SixthCharacterSlot": false,
"SE": false,
"ML": false,
"EigthAge": false,
"EighthAge": false,
"NinthAge": false,
"TenthAge": false,
"IncreasedStorage": false,
@ -361,7 +345,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -380,7 +363,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": false,
"AOS": true,
"SE": false,
"ML": false,
@ -400,16 +382,15 @@
"ClientVersion": null,
"ClientFlags": "Tokuno",
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
"UOTD": false,
"LBR": true,
"AOS": true,
"SixthCharacterSlot": false,
"SE": true,
"ML": false,
"EigthAge": false,
"EighthAge": false,
"NinthAge": false,
"TenthAge": false,
"IncreasedStorage": false,
@ -427,7 +408,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -446,7 +426,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": false,
"AOS": true,
"SE": true,
"ML": false,
@ -466,16 +445,15 @@
"ClientVersion": "5.0.0a",
"ClientFlags": null,
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
"UOTD": false,
"LBR": true,
"AOS": true,
"SixthCharacterSlot": false,
"SE": true,
"ML": true,
"EigthAge": false,
"EighthAge": false,
"NinthAge": true,
"TenthAge": false,
"IncreasedStorage": false,
@ -493,7 +471,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -512,7 +489,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": false,
"AOS": true,
"SE": true,
"ML": true,
@ -532,16 +508,15 @@
"ClientVersion": null,
"ClientFlags": "TerMur",
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
"UOTD": false,
"LBR": true,
"AOS": true,
"SixthCharacterSlot": false,
"SE": true,
"ML": true,
"EigthAge": false,
"EighthAge": false,
"NinthAge": true,
"TenthAge": false,
"IncreasedStorage": false,
@ -559,7 +534,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -578,7 +552,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": false,
"AOS": true,
"SE": true,
"ML": true,
@ -598,16 +571,15 @@
"ClientVersion": "7.0.9.0",
"ClientFlags": null,
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
"UOTD": false,
"LBR": true,
"AOS": true,
"SixthCharacterSlot": false,
"SE": true,
"ML": true,
"EigthAge": false,
"EighthAge": false,
"NinthAge": true,
"TenthAge": false,
"IncreasedStorage": false,
@ -625,7 +597,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -644,7 +615,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": false,
"AOS": true,
"SE": true,
"ML": true,
@ -664,16 +634,15 @@
"ClientVersion": "7.0.45.65",
"ClientFlags": null,
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
"UOTD": false,
"LBR": true,
"AOS": true,
"SixthCharacterSlot": false,
"SE": true,
"ML": true,
"EigthAge": false,
"EighthAge": false,
"NinthAge": true,
"TenthAge": false,
"IncreasedStorage": false,
@ -691,7 +660,6 @@
"EJ": false
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -710,7 +678,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": false,
"AOS": true,
"SE": true,
"ML": true,
@ -730,16 +697,15 @@
"ClientVersion": "7.0.61.0",
"ClientFlags": null,
"FeatureFlags": {
"None": false,
"T2A": true,
"UOR": true,
"UOTD": true,
"UOTD": false,
"LBR": true,
"AOS": true,
"SixthCharacterSlot": false,
"SE": true,
"ML": true,
"EigthAge": false,
"EighthAge": false,
"NinthAge": true,
"TenthAge": false,
"IncreasedStorage": false,
@ -757,7 +723,6 @@
"EJ": true
},
"CharacterListFlags": {
"None": false,
"Unk1": false,
"OverwriteConfigButton": false,
"OneCharacterSlot": false,
@ -776,7 +741,6 @@
"NewFeluccaAreas": false
},
"HousingFlags": {
"None": false,
"AOS": true,
"SE": true,
"ML": true,

View file

@ -0,0 +1,148 @@
[
{
"name": "Slow",
"active": 0.3,
"passive": 0.6,
"types": [
"AntLion", "ArcticOgreLord", "BogThing",
"Bogle", "BoneKnight", "EarthElemental",
"Ettin", "FrostOoze", "FrostTroll",
"GazerLarva", "Ghoul", "Golem",
"HeadlessOne", "Jwilson", "Mummy",
"Ogre", "OgreLord", "PlagueBeast",
"Quagmire", "Rat", "RottingCorpse",
"SewerRat", "Skeleton", "Slime",
"Zombie", "Walrus", "RestlessSoul",
"CrystalElemental", "DarknightCreeper", "MoundOfMaggots",
"Juggernaut", "Yamandon", "Serado",
"GreaterDragon", "PlagueBeastLord"
]
},
{
"name": "Medium",
"active": 0.25,
"passive": 0.5,
"types": [
"AcidElemental", "AgapiteElemental", "Alligator",
"AncientLich", "Betrayer", "Bird",
"BlackBear", "BlackSolenInfiltratorQueen", "BlackSolenInfiltratorWarrior",
"BlackSolenQueen", "BlackSolenWarrior", "BlackSolenWorker",
"BloodElemental", "Boar", "Bogling",
"BoneMagi", "Brigand", "BronzeElemental",
"BrownBear", "Bull", "BullFrog",
"Cat", "Centaur", "ChaosDaemon",
"Chicken", "GolemController", "CopperElemental",
"CopperElemental", "Cougar", "Cow",
"Cyclops", "Daemon", "DeepSeaSerpent",
"DesertOstard", "DireWolf", "Dog",
"Dolphin", "Dragon", "Drake",
"DullCopperElemental", "Eagle", "ElderGazer",
"EvilMage", "EvilMageLord", "Executioner",
"Savage", "FireElemental", "FireGargoyle",
"FireSteed", "ForestOstard", "FrenziedOstard",
"FrostSpider", "Gargoyle", "Gazer",
"IceSerpent", "GiantRat", "GiantSerpent",
"GiantSpider", "GiantToad", "Goat",
"GoldenElemental", "Gorilla", "GreatHart",
"GreyWolf", "GrizzlyBear", "Guardian",
"Harpy", "Harrower", "HellHound",
"Hind", "HordeMinion", "Horse",
"Horse", "IceElemental", "IceFiend",
"IceSnake", "Imp", "JackRabbit",
"Kirin", "Kraken", "PredatorHellCat",
"LavaLizard", "LavaSerpent", "LavaSnake",
"Lizardman", "Llama", "Mongbat",
"StrongMongbat", "MountainGoat", "Orc",
"OrcBomber", "OrcBrute", "OrcCaptain",
"OrcishLord", "OrcishMage", "PackHorse",
"PackLlama", "Panther", "Pig",
"PlagueSpawn", "PolarBear", "Rabbit",
"Ratman", "RatmanArcher", "RatmanMage",
"RedSolenInfiltratorQueen", "RedSolenInfiltratorWarrior", "RedSolenQueen",
"RedSolenWarrior", "RedSolenWorker", "RidableLlama",
"Ridgeback", "Scorpion", "SeaSerpent",
"SerpentineDragon", "Shade", "ShadowIronElemental",
"ShadowWisp", "ShadowWyrm", "Sheep",
"SilverSteed", "SkeletalDragon", "SkeletalMage",
"SkeletalMount", "HellCat", "Snake",
"SnowLeopard", "SpectralArmour", "Spectre",
"StoneGargoyle", "StoneHarpy", "SwampDragon",
"ScaledSwampDragon", "SwampTentacle", "TerathanAvenger",
"TerathanDrone", "TerathanMatriarch", "TerathanWarrior",
"TimberWolf", "Titan", "Troll",
"Unicorn", "ValoriteElemental", "VeriteElemental",
"CoMWarHorse", "MinaxWarHorse", "SLWarHorse",
"TBWarHorse", "WaterElemental", "WhippingVine",
"WhiteWolf", "Wraith", "Wyvern",
"KhaldunZealot", "KhaldunSummoner", "SavageRidgeback",
"LichLord", "SkeletalKnight", "SummonedDaemon",
"SummonedEarthElemental", "SummonedWaterElemental", "SummonedFireElemental",
"MeerWarrior", "MeerEternal", "MeerMage",
"MeerCaptain", "JukaLord", "JukaMage",
"JukaWarrior", "AbysmalHorror", "BoneDemon",
"Devourer", "FleshGolem", "Gibberling",
"GoreFiend", "Impaler", "PatchworkSkeleton",
"Ravager", "ShadowKnight", "SkitteringHopper",
"Treefellow", "VampireBat", "WailingBanshee",
"WandererOfTheVoid", "Cursed", "GrimmochDrummel",
"LysanderGathenwale", "MorgBergen", "ShadowFiend",
"SpectralArmour", "TavaraSewel", "ArcaneDaemon",
"Doppleganger", "EnslavedGargoyle", "ExodusMinion",
"ExodusOverseer", "GargoyleDestroyer", "GargoyleEnforcer",
"Moloch", "BakeKitsune", "DeathwatchBeetleHatchling",
"Kappa", "KazeKemono", "DeathwatchBeetle",
"TsukiWolf", "YomotsuElder", "YomotsuPriest",
"YomotsuWarrior", "RevenantLion", "Oni",
"Gaman", "Crane", "Beetle",
"Parrot", "ElfBrigand", "GreaterMongbat",
"AnimatedWeapon", "Reaper", "Corpser"
]
},
{
"name": "Fast",
"active": 0.2,
"passive": 0.4,
"types": [
"LordOaks", "Silvani", "AirElemental",
"AncientWyrm", "Balron", "BladeSpirits",
"DreadSpider", "Efreet", "EtherealWarrior",
"Lich", "Nightmare", "OphidianArchmage",
"OphidianMage", "OphidianWarrior", "OphidianMatriarch",
"OphidianKnight", "PoisonElemental",
"SandVortex", "SavageRider", "SavageShaman",
"SnowElemental", "WhiteWyrm", "Wisp",
"DemonKnight", "GiantBlackWidow", "SummonedAirElemental",
"LesserHiryu", "Hiryu", "LadyOfTheSnow",
"RaiJu", "Ronin", "RuneBeetle",
"Changeling", "LadyJennifyr", "LadyMarai", "MasterJonath",
"MasterMikael", "MasterTheophilus", "RedDeath",
"SirPatrick", "Miasma", "Rend",
"Grobu", "Gnaw", "Guile",
"Irk", "Spite", "LadyLissith",
"LadySabrix", "Malefic", "Silk",
"Virulent", "SeaHorse", "UnholyFamiliar",
"HolyFamiliar", "GiantIceWorm", "Phoenix",
"Succubus", "EnragedRabbit", "EnragedHind",
"EnragedHart", "EnragedBlackBear", "EnragedEagle",
"RagingGrizzlyBear", "CorrosiveSlime", "DarkWisp",
"DarkGuardian", "HarrowerTentacles", "ServantOfSemidar",
"Reptalon"
]
},
{
"name": "Very Fast",
"active": 0.175,
"passive": 0.35,
"types": [
"Barracoon", "Mephitis", "Neira",
"Rikktor", "Semidar", "EnergyVortex",
"EliteNinja", "Pixie", "SilverSerpent",
"VorpalBunny", "FleshRenderer", "KhaldunRevenant",
"FactionDragoon", "FactionKnight", "FactionPaladin",
"FactionHenchman", "FactionMercenary", "FactionNecromancer",
"FactionSorceress", "FactionWizard", "FactionBerserker",
"FactionPaladin", "Leviathan", "FireBeetle",
"FanDancer", "FactionDeathKnight", "Revenant"
]
}
]

View file

@ -40,9 +40,9 @@
"back": true
},
{
"src": { "map": "Felucca", "loc": [589, 1637, 0] },
"dst": { "map": "Felucca", "loc": [6575, 888, 0] },
"back": false
"src": { "map": "Felucca", "loc": [588, 1637, 0] },
"dst": { "map": "Felucca", "loc": [6472, 868, 26] },
"back": true
},
{
"src": { "map": "Felucca", "loc": [766, 1645, 0] },
@ -2054,16 +2054,6 @@
"dst": { "map": "Felucca", "loc": [6503, 88, 0] },
"back": false
},
{
"src": { "map": "Felucca", "loc": [6488, 849, 40] },
"dst": { "map": "Felucca", "loc": [6587, 867, 0] },
"back": false
},
{
"src": { "map": "Felucca", "loc": [6488, 850, 41] },
"dst": { "map": "Felucca", "loc": [6587, 868, 0] },
"back": false
},
{
"src": { "map": "Felucca", "loc": [6540, 116, -15] },
"dst": { "map": "Felucca", "loc": [6537, 138, -20] },
@ -2085,13 +2075,8 @@
"back": false
},
{
"src": { "map": "Felucca", "loc": [6574, 889, 0] },
"dst": { "map": "Felucca", "loc": [6472, 869, 20] },
"back": false
},
{
"src": { "map": "Felucca", "loc": [6575, 889, 0] },
"dst": { "map": "Felucca", "loc": [6473, 869, 20] },
"src": { "map": "Felucca", "loc": [6477, 860, 9] },
"dst": { "map": "Felucca", "loc": [6574, 889, 0] },
"back": false
},
{
@ -2116,7 +2101,7 @@
},
{
"src": { "map": "Felucca", "loc": [6588, 867, 0] },
"dst": { "map": "Felucca", "loc": [6489, 849, 39] },
"dst": { "map": "Felucca", "loc": [6488, 849, 40] },
"back": false
},
{
@ -2220,8 +2205,13 @@
"back": true
},
{
"src": { "map": "Trammel", "loc": [589, 1637, 0] },
"dst": { "map": "Trammel", "loc": [6575, 888, 0] },
"src": { "map": "Trammel", "loc": [588, 1637, 0] },
"dst": { "map": "Trammel", "loc": [6472, 868, 26] },
"back": true
},
{
"src": { "map": "Trammel", "loc": [6477, 860, 9] },
"dst": { "map": "Trammel", "loc": [6574, 889, 0] },
"back": false
},
{
@ -4179,16 +4169,6 @@
"dst": { "map": "Trammel", "loc": [6503, 88, 0] },
"back": false
},
{
"src": { "map": "Trammel", "loc": [6488, 849, 40] },
"dst": { "map": "Trammel", "loc": [6587, 867, 0] },
"back": false
},
{
"src": { "map": "Trammel", "loc": [6488, 850, 41] },
"dst": { "map": "Trammel", "loc": [6587, 868, 0] },
"back": false
},
{
"src": { "map": "Trammel", "loc": [6540, 116, -15] },
"dst": { "map": "Trammel", "loc": [6537, 138, -20] },
@ -4209,16 +4189,6 @@
"dst": { "map": "Trammel", "loc": [6576, 73, 0] },
"back": false
},
{
"src": { "map": "Trammel", "loc": [6574, 889, 0] },
"dst": { "map": "Trammel", "loc": [6472, 869, 20] },
"back": false
},
{
"src": { "map": "Trammel", "loc": [6575, 889, 0] },
"dst": { "map": "Trammel", "loc": [6473, 869, 20] },
"back": false
},
{
"src": { "map": "Trammel", "loc": [6577, 159, 13] },
"dst": { "map": "Trammel", "loc": [6577, 177, 30] },
@ -4241,7 +4211,7 @@
},
{
"src": { "map": "Trammel", "loc": [6588, 867, 0] },
"dst": { "map": "Trammel", "loc": [6489, 849, 39] },
"dst": { "map": "Trammel", "loc": [6488, 849, 40] },
"back": false
},
{

21
FAQ.md
View file

@ -1,21 +1,24 @@
# Frequently Asked Questions
- [Why do I have thousands of compile errors](#why-do-i-have-thousands-of-compile-errors)
- [Where do I go to run ModernUO?](#where-do-i-go-to-run-modernuo)
- [I don't see my scripts compiling, what happened?](#i-dont-see-my-scripts-compiling-what-happened)
- [Why can't I run ModernUO on a 32-bit operating system?](#why-cant-i-run-modernuo-on-a-32-bit-operating-system)
## What has changed since RunUO 2.7?
We have compiled a non-exhaustive list of the features changes, technical improvements, and new additions.
- See: [RunUO to ModernUO](./RUNUO_TO_MODERNUO.md)
## Why do I have thousands of compile errors?
ModernUO uses a new technique called source generation. Most likely the compile errors are related to that.
To resolve the compile errors make sure you are using Visual Studio 2019 v16.10+, or Rider 2021.2+ for development and the newest .NET available.
ModernUO uses a _very_ new technique called source generation. Most likely the compile errors are related to the source generator crashing.
To resolve the compile errors make sure you are using Visual Studio 2022, or Rider 2022+ for development and the newest .NET available.
Specifically for Visual Studio, you must build the entire solution once, restart Visual Studio entirely, and then build again.
This is required every time code changes in the `SerializationGenerator` or `SerializationSchemaGenerator` projects.
It is possible that while you are developing, many of your files will go red and properties/symbols cannot be resolved. Contact us on discord
and let us know how to reproduce it so we can get it fixed! Under the same vein, we are working on building an analyzer for the IDE so if there
are errors, you are not in the dark.
## Where do I go to run ModernUO?
Everything is run from the `Distribution` directory.
This folder is portable, so it can be moved to the production server for deployments.
## I don't see my scripts compiling, what happened?
.NET 5 does not support dynamic compiling newer versions of C#. Scripts are now compiled separately and moved to the `Distribution/Assemblies` directory.
Scripts are now compiled separately (UOContent project) and moved to the `Distribution/Assemblies` directory.
## Why can't I run ModernUO on a 32-bit operating system?
ModernUO is optimized for performance and modern hardware. For this reason we no longer support 32-bit operating systems
ModernUO is optimized for modern hardware. We cannot support 32-bit systems, sorry.

View file

@ -1,63 +0,0 @@
# GOALS
The goal of ModernUO is a complete rewrite of RunUO that focuses on performance.
Some of the many high level goals include:
### Architecture
- [x] Upgrade Server/Scripts to target .NET Core 3
- [X] Upgrade the code to use C# 8 standards
- [X] Remove compilation of scripts at startup in favor of loading DLLs.
- [X] Support configuration via a `modernuo.json` file
### Networking
- [ ] Replace Packet classes with functions
- [X] Improve asynchronous socket handling using Pipes
- [X] Improve socket handling (2-5x) and event loop using libuv
### Administration
- [ ] Move IP logging and account data to SQL
- [ ] Move account management to web
### Serialization & World Saves
- [ ] Create a flag to indicate when objects will be serialized.
* Players, all items held by players, houses, etc.
- [ ] Create transactions which handle changes to objects including:
* Transitioning from not serialized to serialized (created)
* Transitioning from serialized to not serialized (deleted)
* Property value changes
- [ ] Create asynchronous request from cold storage
* External system must provide objects and their nested objects (e.g. containers)
### Optimizations for large shards
- [ ] Eject inactive players and their items after they are serialized to cold storage
- [ ] Create hydration techniques for non-persisted systems (e.g. spawners, decorations, static, champions, etc)
- [ ] Simplify certain items by eliminating all of their individual types and using an enum to reference their individual type
* Items subject to change: reagents, logs, ore, ingots, potions, and containers
* Syntax change: `new MandrakeRoot()` -> `new Reagent(ReagentType.MandrakeRoot)`
- [ ] Create object pools for high availability items such as gold and reagents
* For example, `new MandrakeRoot()` -> `ObjectPool.Get<Reagent>(ReagentType.MandrakeRoot)`.
* Pools should be elastic and adjust according to nominal usage. For example, if thousands of gold objects are created and destroyed in a small period of time, the pool should be expanded and replenished properly so it is never empty, or full.
- [ ] Replace timer system with libuv implementation
- [X] Create `DefaultName` for mobiles
### Plugins (Separate Repos & Optional)
- [ ] RunUO 2.0 Migration Utility
* Template code for updating serializations
* Convert World file and dump to new serialization technique if there is one
- [ ] Login/Auth Gateway (Like OSI!)
* Use central AuthID via redis or similar
* Support 2FA Auths via at login gump
- [ ] 2FA Auth at Login to prevent password theft
* Support Authy via universal interface
* Support external app/device, magic number email, SMS, and FCM
* Support user identity via web browser to curb flooding
- [ ] Serialization via Database (no world saves)
* Asynchronously offload the transaction to another service for permanent serialization
* Support basic KVP databases such as MongoDB, RocksDB, and Redis, etc.
### Bugs & Misc
- [X] Rounding errors with new bank system
- [X] Replace specific checks for `Item`/`Mobile` by extended `IEntity` and mainstreaming the `NEWPARENT` code
- [ ] TryDropItem dropping items until it fails when it should be all-or-nothing
- [ ] Consumable items are deleted directly in several places instead of using `Item.Consume()`
- [ ] Stack/Hold checks are broken in certain situations

View file

@ -10,12 +10,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server.Tests", "Projects\Se
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UOContent.Tests", "Projects\UOContent.Tests\UOContent.Tests.csproj", "{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Benchmarks", "Projects\Benchmarks\Benchmarks.csproj", "{38B7E4A1-FDDD-486C-98EE-BA7B13712528}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerializationGenerator", "Projects\SerializationGenerator\SerializationGenerator.csproj", "{07DDB8CF-F926-44F4-A584-FF2997D2C9D0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SerializationSchemaGenerator", "Projects\SerializationSchemaGenerator\SerializationSchemaGenerator.csproj", "{A30150A3-796C-4C6D-B3E4-B7BEB0021701}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Analyze|x64 = Analyze|x64
@ -47,24 +41,6 @@ Global
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Debug|x64.Build.0 = Debug|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|x64.ActiveCfg = Release|x64
{3C4797F9-603E-44EF-8E8C-9275CC9EA74B}.Release|x64.Build.0 = Release|x64
{38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Analyze|x64.ActiveCfg = Release|x64
{38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Analyze|x64.Build.0 = Release|x64
{38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Debug|x64.ActiveCfg = Debug|x64
{38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Debug|x64.Build.0 = Debug|x64
{38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Release|x64.ActiveCfg = Release|x64
{38B7E4A1-FDDD-486C-98EE-BA7B13712528}.Release|x64.Build.0 = Release|x64
{07DDB8CF-F926-44F4-A584-FF2997D2C9D0}.Analyze|x64.ActiveCfg = Analyze|x64
{07DDB8CF-F926-44F4-A584-FF2997D2C9D0}.Analyze|x64.Build.0 = Analyze|x64
{07DDB8CF-F926-44F4-A584-FF2997D2C9D0}.Debug|x64.ActiveCfg = Debug|x64
{07DDB8CF-F926-44F4-A584-FF2997D2C9D0}.Debug|x64.Build.0 = Debug|x64
{07DDB8CF-F926-44F4-A584-FF2997D2C9D0}.Release|x64.ActiveCfg = Release|x64
{07DDB8CF-F926-44F4-A584-FF2997D2C9D0}.Release|x64.Build.0 = Release|x64
{A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Analyze|x64.ActiveCfg = Analyze|x64
{A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Analyze|x64.Build.0 = Analyze|x64
{A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Debug|x64.ActiveCfg = Debug|x64
{A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Debug|x64.Build.0 = Debug|x64
{A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Release|x64.ActiveCfg = Release|x64
{A30150A3-796C-4C6D-B3E4-B7BEB0021701}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View file

@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<Platforms>x64</Platforms>
<PlatformTarget>x64</PlatformTarget>
<LangVersion>9</LangVersion>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<SkipLocalsInitiAttribute>true</SkipLocalsInitiAttribute>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.1" />
<ProjectReference Include="..\Server\Server.csproj" />
<ProjectReference Include="..\UOContent\UOContent.csproj" />
</ItemGroup>
</Project>

View file

@ -1,130 +0,0 @@
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Collections;
namespace Benchmarks
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net60)]
public class BenchmarkOrderedHashSet
{
private readonly string[] _iterations = new string[16];
[IterationSetup]
public void IterationSetup()
{
for (var i = 0; i < _iterations.Length; i++)
{
_iterations[i] = i.ToString();
}
}
[Benchmark]
public int UsingList()
{
var list = new List<string>();
for (int i = 0; i < _iterations.Length / 2; i++)
{
AddIfNotPresent(list, _iterations[i]);
}
for (int i = 0; i < _iterations.Length; i++)
{
AddIfNotPresent(list, _iterations[i]);
}
for (int i = 0; i < list.Count; i++)
{
list[i].ToString();
}
return list.Count;
}
private static int AddIfNotPresent<T>(List<T> list, T item)
{
var index = list.IndexOf(item);
if (index > -1)
{
return index;
}
list.Add(item);
return list.Count - 1;
}
[Benchmark]
public int UsingOrderedHashSet()
{
var ordered = new OrderedHashSet<string>();
for (int i = 0; i < _iterations.Length / 2; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
for (int i = 0; i < _iterations.Length; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
foreach (var str in ordered)
{
str.ToString();
}
return ordered.Count;
}
[Benchmark]
public int UsingPooledOrderedHashSet()
{
var ordered = new PooledOrderedHashSet<string>();
for (int i = 0; i < _iterations.Length / 2; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
for (int i = 0; i < _iterations.Length; i++)
{
ordered.GetOrAdd(_iterations[i]).ToString();
}
foreach (var str in ordered)
{
str.ToString();
}
return ordered.Count;
}
[Benchmark]
public int UsingHashSet()
{
var hashSet = new HashSet<(string, int)>(new OrderedStringComparer());
for (int i = 0; i < _iterations.Length / 2; i++)
{
hashSet.Add((_iterations[i], i));
}
for (int i = 0; i < _iterations.Length; i++)
{
hashSet.Add((_iterations[i], i));
}
foreach (var str in hashSet)
{
str.ToString();
}
return hashSet.Count;
}
private class OrderedStringComparer : EqualityComparer<(string, int)>
{
public override bool Equals((string, int) x, (string, int) y) => x.Item1.Equals(y.Item1, System.StringComparison.Ordinal);
public override int GetHashCode((string, int) obj) => obj.Item1.GetHashCode();
}
}
}

View file

@ -1,63 +0,0 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Serilog;
using Serilog.Core;
namespace Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
public class BenchmarkConsoleLogging
{
private const string text = "Sample message";
private Logger logger;
private Logger asyncLogger;
[GlobalSetup]
public void GlobalSetup()
{
logger = new LoggerConfiguration()
.WriteTo.Console()
.CreateLogger();
asyncLogger = new LoggerConfiguration()
.WriteTo.Async(a => a.Console())
.CreateLogger();
}
[GlobalCleanup]
public void GlobalCleanup()
{
logger = null;
asyncLogger = null;
}
[Benchmark]
public void TestConsoleWriteLine()
{
for (int i = 0; i < 10000; i++)
{
Console.WriteLine(text);
}
}
[Benchmark]
public void TestSerilogConsoleSink()
{
for (int i = 0; i < 10000; i++)
{
logger.Information(text);
}
}
[Benchmark]
public void TestSerilogAsyncConsoleSink()
{
for (int i = 0; i < 10000; i++)
{
asyncLogger.Information(text);
}
}
}
}

View file

@ -1,665 +0,0 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Benchmarks
{
[SimpleJob(RuntimeMoniker.NetCoreApp50)]
public class MapSelectors
{
static readonly Sector sector = new Sector();
static Server.Rectangle2D bounds = new Server.Rectangle2D(0, 0, 100, 100);
public static void Init()
{
for (int i = 0; i < 50; ++i)
{
sector.Multis.Add(new BaseMulti());
}
for (int i = 0; i < 1000; ++i)
{
sector.BItems.Add(new BItem());
}
for (int i = 0; i < 1000; ++i)
{
sector.Mobiles.Add(new Mobile());
}
}
#region MultiTiles
[Benchmark]
public void SelectMultiTilesNew()
{
foreach(StaticTile[] tiles in SelectMultiTiles(sector, bounds))
{
for(int i = 0; i < tiles.Length; ++i)
{
int id = tiles[i].ID;
}
}
}
[Benchmark]
public void SelectMultiTilesLinq()
{
foreach(StaticTile[] tiles in SelectMultiTilesLinq(sector, bounds))
{
for(int i = 0; i < tiles.Length; ++i)
{
int id = tiles[i].ID;
}
}
}
public IEnumerable<StaticTile[]> SelectMultiTilesLinq(Sector s, Server.Rectangle2D bounds)
{
foreach (var o in s.Multis.Where(o => o != null && !o.Deleted))
{
var c = o.Components;
int x, y, xo, yo;
StaticTile[] t, r;
for (x = bounds.Start.X; x < bounds.End.X; x++)
{
xo = x - (o.X + c.Min.X);
if (xo < 0 || xo >= c.Width)
{
continue;
}
for (y = bounds.Start.Y; y < bounds.End.Y; y++)
{
yo = y - (o.Y + c.Min.Y);
if (yo < 0 || yo >= c.Height)
{
continue;
}
t = c.Tiles[xo][yo];
if (t.Length <= 0)
{
continue;
}
r = new StaticTile[t.Length];
for (var i = 0; i < t.Length; i++)
{
r[i] = t[i];
r[i].Z += o.Z;
}
yield return r;
}
}
}
}
public IEnumerable<StaticTile[]> SelectMultiTiles(Sector s, Server.Rectangle2D bounds)
{
for (int l = s.Multis.Count - 1; l >= 0; --l)
{
BaseMulti o = s.Multis[l];
if (o != null && !o.Deleted)
{
MultiComponentList c = o.Components;
int x, y, xo, yo;
StaticTile[] t, r;
for (x = bounds.Start.X; x < bounds.End.X; x++)
{
xo = x - (o.X + c.Min.X);
if (xo < 0 || xo >= c.Width)
{
continue;
}
for (y = bounds.Start.Y; y < bounds.End.Y; y++)
{
yo = y - (o.Y + c.Min.Y);
if (yo < 0 || yo >= c.Height)
{
continue;
}
t = c.Tiles[xo][yo];
if (t.Length <= 0)
{
continue;
}
r = new StaticTile[t.Length];
for (var i = 0; i < t.Length; i++)
{
r[i] = t[i];
r[i].Z += o.Z;
}
yield return r;
}
}
}
}
}
#endregion
#region Multis
[Benchmark]
public void SelectMultisNew()
{
SelectMultis(sector, bounds);
}
[Benchmark]
public void SelectMultisLinq()
{
SelectMultisLinq(sector, bounds);
}
public IEnumerable<BaseMulti> SelectMultisLinq(Sector s, Server.Rectangle2D bounds)
{
return s.Multis.Where(o => o != null && !o.Deleted && bounds.Contains(o.Location));
}
public IEnumerable<BaseMulti> SelectMultis(Sector s, Server.Rectangle2D bounds)
{
List<BaseMulti> entities = new List<BaseMulti>(s.Multis.Count);
for (int i = s.Multis.Count - 1; i >= 0; --i)
{
BaseMulti BItem = s.Multis[i];
if (BItem != null && !BItem.Deleted && bounds.Contains(BItem.Location))
entities.Add(BItem);
}
return entities;
}
#endregion
#region BItems
[Benchmark]
public void SelectBItemsNew()
{
SelectBItems<BItem>(sector, bounds);
}
[Benchmark]
public void SelectBItemsLinq()
{
SelectBItemsLinq<BItem>(sector, bounds);
}
public IEnumerable<T> SelectBItemsLinq<T>(Sector s, Server.Rectangle2D bounds) where T : BItem
{
return s.BItems.OfType<T>().Where(o => o != null && !o.Deleted && o.Parent == null && bounds.Contains(o.Location));
}
public IEnumerable<T> SelectBItems<T>(Sector s, Server.Rectangle2D bounds) where T : BItem
{
List<T> entities = new List<T>(s.BItems.Count);
Type type = typeof(T);
for (int i = s.BItems.Count - 1; i >= 0; --i)
{
BItem BItem = s.BItems[i];
if (BItem != null && !BItem.Deleted && BItem.Parent == null && bounds.Contains(BItem.Location) && type.IsAssignableFrom(BItem.GetType()))
entities.Add(BItem as T);
}
return entities;
}
#endregion
#region Mobiles
[Benchmark]
public void SelectMobilesNew()
{
SelectMobiles<Mobile>(sector, bounds);
}
[Benchmark]
public void SelectMobilesLinq()
{
SelectMobilesLinq<Mobile>(sector, bounds);
}
public IEnumerable<T> SelectMobilesLinq<T>(Sector s, Server.Rectangle2D bounds) where T : Mobile
{
return s.Mobiles.OfType<T>().Where(o => o != null && !o.Deleted && bounds.Contains(o.Location));
}
public IEnumerable<T> SelectMobiles<T>(Sector s, Server.Rectangle2D bounds) where T : Mobile
{
List<T> entities = new List<T>(s.Mobiles.Count);
Type type = typeof(T);
for (int i = s.Mobiles.Count - 1; i >= 0; --i)
{
Mobile mob = s.Mobiles[i];
if (mob != null && !mob.Deleted && bounds.Contains(mob.Location) && type.IsAssignableFrom(mob.GetType()))
entities.Add(mob as T);
}
return entities;
}
#endregion
#region Entities
[Benchmark]
public void SelectEntitiesNew()
{
SelectEntities(sector, bounds);
}
[Benchmark]
public void SelectEntitiesLinq()
{
SelectEntitiesLinq(sector, bounds);
}
public IEnumerable<Server.IEntity> SelectEntitiesLinq(Sector s, Server.Rectangle2D bounds)
{
return Enumerable.Empty<IEntity>()
.Union(s.Mobiles.Where(o => o != null && !o.Deleted))
.Union(s.BItems.Where(o => o != null && !o.Deleted && o.Parent == null))
.Where(o => bounds.Contains(o.Location));
}
private readonly List<Server.IEntity> entities = new (10);
public IEnumerable<Server.IEntity> SelectEntities(Sector s, Server.Rectangle2D bounds)
{
entities.Clear();
entities.Capacity = s.Mobiles.Count + s.BItems.Count;
for (int i = s.Mobiles.Count - 1, j = s.BItems.Count - 1; i >= 0 || j >= 0; --i, --j)
{
if (j >= 0)
{
BItem BItem = s.BItems[j];
if (BItem != null && !BItem.Deleted && BItem.Parent == null && bounds.Contains(BItem.Location))
entities.Add(BItem);
}
if (i >= 0)
{
Mobile mob = s.Mobiles[i];
if (mob != null && !mob.Deleted && bounds.Contains(mob.Location))
entities.Add(mob);
}
}
return entities;
}
#endregion
}
public class BItem : Server.IPoint3D, IEntity
{
public object Parent { get; set; } = null;
public bool Deleted { get; set; } = false;
public int Z { get; set; } = 1;
public int X { get; set; } = 1;
public int Y { get; set; } = 1;
public Serial Serial => throw new System.NotImplementedException();
public Point3D Location { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public Map Map { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public Region Region => throw new System.NotImplementedException();
public string Name { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public int Hue { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public Direction Direction { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public DateTime Created { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public int TypeRef => throw new NotImplementedException();
Point3D IEntity.Location => throw new NotImplementedException();
Map IEntity.Map => throw new NotImplementedException();
int IPoint3D.Z => throw new NotImplementedException();
int IPoint2D.X => throw new NotImplementedException();
int IPoint2D.Y => throw new NotImplementedException();
DateTime ISerializable.Created { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
DateTime ISerializable.LastSerialized { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
long ISerializable.SavePosition { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
BufferWriter ISerializable.SaveBuffer { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
int ISerializable.TypeRef => throw new NotImplementedException();
Serial ISerializable.Serial => throw new NotImplementedException();
bool ISerializable.Deleted => throw new NotImplementedException();
public BItem()
{
}
public void Delete()
{
throw new System.NotImplementedException();
}
public void ProcessDelta()
{
throw new System.NotImplementedException();
}
public void OnStatsQuery(Server.Mobile m)
{
throw new System.NotImplementedException();
}
public void InvalidateProperties()
{
throw new System.NotImplementedException();
}
public int CompareTo(object obj)
{
throw new System.NotImplementedException();
}
public int CompareTo(IEntity other)
{
throw new System.NotImplementedException();
}
public void MoveToWorld(Point3D location, Map map)
{
throw new NotImplementedException();
}
public bool InRange(Point2D p, int range)
{
throw new NotImplementedException();
}
public bool InRange(Point3D p, int range)
{
throw new NotImplementedException();
}
public void RemoveBItem(BItem BItem)
{
throw new NotImplementedException();
}
public void BeforeSerialize()
{
throw new NotImplementedException();
}
public void Deserialize(IGenericReader reader)
{
throw new NotImplementedException();
}
public void Serialize(IGenericWriter writer)
{
throw new NotImplementedException();
}
public void SetTypeRef(Type type)
{
throw new NotImplementedException();
}
void IEntity.MoveToWorld(Point3D location, Map map)
{
throw new NotImplementedException();
}
void IEntity.ProcessDelta()
{
throw new NotImplementedException();
}
bool IEntity.InRange(Point2D p, int range)
{
throw new NotImplementedException();
}
bool IEntity.InRange(Point3D p, int range)
{
throw new NotImplementedException();
}
void ISerializable.BeforeSerialize()
{
throw new NotImplementedException();
}
void ISerializable.Deserialize(IGenericReader reader)
{
throw new NotImplementedException();
}
void ISerializable.Serialize(IGenericWriter writer)
{
throw new NotImplementedException();
}
void ISerializable.Delete()
{
throw new NotImplementedException();
}
void ISerializable.SetTypeRef(Type type)
{
throw new NotImplementedException();
}
public void RemoveItem(Item item)
{
throw new NotImplementedException();
}
}
public class Mobile : Server.IPoint3D, IEntity
{
public bool Deleted { get; set; } = false;
public int Z { get; set; } = 1;
public int X { get; set; } = 1;
public int Y { get; set; } = 1;
public Serial Serial => throw new System.NotImplementedException();
public Point3D Location { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public Map Map { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public Region Region => throw new System.NotImplementedException();
public string Name { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public int Hue { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public Direction Direction { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); }
public DateTime Created { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public int TypeRef => throw new NotImplementedException();
Point3D IEntity.Location => throw new NotImplementedException();
Map IEntity.Map => throw new NotImplementedException();
int IPoint3D.Z => throw new NotImplementedException();
int IPoint2D.X => throw new NotImplementedException();
int IPoint2D.Y => throw new NotImplementedException();
DateTime ISerializable.Created { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
DateTime ISerializable.LastSerialized { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
long ISerializable.SavePosition { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
BufferWriter ISerializable.SaveBuffer { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
int ISerializable.TypeRef => throw new NotImplementedException();
Serial ISerializable.Serial => throw new NotImplementedException();
bool ISerializable.Deleted => throw new NotImplementedException();
public Mobile()
{
}
public void Delete()
{
throw new System.NotImplementedException();
}
public void ProcessDelta()
{
throw new System.NotImplementedException();
}
public void OnStatsQuery(Server.Mobile m)
{
throw new System.NotImplementedException();
}
public void InvalidateProperties()
{
throw new System.NotImplementedException();
}
public int CompareTo(object obj)
{
throw new System.NotImplementedException();
}
public int CompareTo(IEntity other)
{
throw new System.NotImplementedException();
}
public void MoveToWorld(Point3D location, Map map)
{
throw new NotImplementedException();
}
public bool InRange(Point2D p, int range)
{
throw new NotImplementedException();
}
public bool InRange(Point3D p, int range)
{
throw new NotImplementedException();
}
public void RemoveBItem(BItem BItem)
{
throw new NotImplementedException();
}
public void BeforeSerialize()
{
throw new NotImplementedException();
}
public void Deserialize(IGenericReader reader)
{
throw new NotImplementedException();
}
public void Serialize(IGenericWriter writer)
{
throw new NotImplementedException();
}
public void SetTypeRef(Type type)
{
throw new NotImplementedException();
}
void IEntity.MoveToWorld(Point3D location, Map map)
{
throw new NotImplementedException();
}
void IEntity.ProcessDelta()
{
throw new NotImplementedException();
}
bool IEntity.InRange(Point2D p, int range)
{
throw new NotImplementedException();
}
bool IEntity.InRange(Point3D p, int range)
{
throw new NotImplementedException();
}
void ISerializable.BeforeSerialize()
{
throw new NotImplementedException();
}
void ISerializable.Deserialize(IGenericReader reader)
{
throw new NotImplementedException();
}
void ISerializable.Serialize(IGenericWriter writer)
{
throw new NotImplementedException();
}
void ISerializable.Delete()
{
throw new NotImplementedException();
}
void ISerializable.SetTypeRef(Type type)
{
throw new NotImplementedException();
}
public void RemoveItem(Item item)
{
throw new NotImplementedException();
}
}
public class BaseMulti : BItem
{
public MultiComponentList Components = MultiComponentList.Empty;
public BaseMulti()
{
for (int i = 0; i < 20; ++i)
for (int j = 0; j < 20; ++j)
for (int z = 0; z < 20; ++z)
Components.Add(123, i, j, z);
}
}
public class Sector
{
public List<BItem> BItems { get; set; } = new List<BItem>();
public List<Mobile> Mobiles { get; set; } = new List<Mobile>();
public List<BaseMulti> Multis { get; set; } = new List<BaseMulti>();
}
}

View file

@ -1,173 +0,0 @@
using System;
using System.Buffers;
using System.IO;
using System.IO.Compression;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Collections;
using Server.Gumps;
using Server.Network;
using Server.Tests.Network;
namespace Benchmarks
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net60)]
public class OutgoingGumpPacketBenchmarks
{
private static readonly byte[] _layoutBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
private static readonly byte[] _stringsBuffer = GC.AllocateUninitializedArray<byte>(0x20000);
public static void CreateDisplayGump(Gump gump, out int switches, out int entries)
{
switches = 0;
entries = 0;
const bool packed = false;
var layoutWriter = new SpanWriter(_layoutBuffer);
if (!gump.Draggable)
{
layoutWriter.Write(Gump.NoMove);
}
if (!gump.Closable)
{
layoutWriter.Write(Gump.NoClose);
}
if (!gump.Disposable)
{
layoutWriter.Write(Gump.NoDispose);
}
if (!gump.Resizable)
{
layoutWriter.Write(Gump.NoResize);
}
var stringsList = new OrderedHashSet<string>(32);
foreach (var entry in gump.Entries)
{
entry.AppendTo(ref layoutWriter, stringsList, ref entries, ref switches);
}
var stringsWriter = new SpanWriter(_stringsBuffer);
foreach (var str in stringsList)
{
var s = str ?? "";
stringsWriter.Write((ushort)s.Length);
stringsWriter.WriteBigUni(s);
}
int maxLength;
if (packed)
{
var worstLayoutLength = Zlib.MaxPackSize(layoutWriter.BytesWritten);
var worstStringsLength = Zlib.MaxPackSize(stringsWriter.BytesWritten);
maxLength = 40 + worstLayoutLength + worstStringsLength;
}
else
{
maxLength = 23 + layoutWriter.BytesWritten + stringsWriter.BytesWritten;
}
var writer = new SpanWriter(maxLength);
writer.Write((byte)(packed ? 0xDD : 0xB0)); // Packet ID
writer.Seek(2, SeekOrigin.Current);
writer.Write(gump.Serial);
writer.Write(gump.TypeID);
writer.Write(gump.X);
writer.Write(gump.Y);
if (packed)
{
layoutWriter.Write((byte)0); // Layout text terminator
OutgoingGumpPackets.WritePacked(layoutWriter.Span, ref writer);
writer.Write(stringsList.Count);
OutgoingGumpPackets.WritePacked(stringsWriter.Span, ref writer);
}
else
{
writer.Write((ushort)layoutWriter.BytesWritten);
writer.Write(layoutWriter.Span);
writer.Write((ushort)stringsList.Count);
writer.Write(stringsWriter.Span);
}
writer.WritePacketLength();
layoutWriter.Dispose(); // Just in case
stringsWriter.Dispose(); // Just in case
}
public class NameChangeDeedGump : Gump
{
public NameChangeDeedGump() : base(50, 50)
{
Closable = false;
Draggable = false;
Resizable = false;
AddPage(0);
AddBlackAlpha(10, 120, 250, 85);
AddHtml(10, 125, 250, 20, Color(Center("Name Change Deed"), 0xFFFFFF));
AddLabel(73, 15, 1152, "");
AddLabel(20, 150, 0x480, "New Name:");
AddTextField(100, 150, 150, 20, 0);
AddButtonLabeled(75, 180, 1, "Submit");
}
public void AddBlackAlpha(int x, int y, int width, int height)
{
AddImageTiled(x, y, width, height, 2624);
AddAlphaRegion(x, y, width, height);
}
public void AddTextField(int x, int y, int width, int height, int index)
{
AddBackground(x - 2, y - 2, width + 4, height + 4, 0x2486);
AddTextEntry(x + 2, y + 2, width - 4, height - 4, 0, index, "");
}
public static string Center(string text) => $"<CENTER>{text}</CENTER>";
public static string Color(string text, int color) => $"<BASEFONT COLOR=#{color:X6}>{text}</BASEFONT>";
public void AddButtonLabeled(int x, int y, int buttonID, string text)
{
AddButton(x, y - 1, 4005, 4007, buttonID);
AddHtml(x + 35, y, 240, 20, Color(text, 0xFFFFFF));
}
}
private static Gump _gump;
[GlobalSetup]
public void Setup()
{
_gump = new NameChangeDeedGump();
}
[Benchmark]
public void TestNewStack()
{
CreateDisplayGump(_gump, out var _, out var _);
}
[Benchmark]
public void TestOldStack()
{
_gump.Compile().Compile(false, out var _);
}
}
}

View file

@ -1,174 +0,0 @@
using System;
using System.Buffers;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server;
using Server.Network;
namespace Benchmarks
{
[SimpleJob(RuntimeMoniker.Net60)]
public class BenchmarkPacketBroadcast
{
public static int SendUnicodeMessage(
ArraySegment<byte>[] buffer,
Serial serial, int graphic, MessageType type, int hue, int font, string lang, string name, string text
)
{
name = name?.Trim() ?? "";
text = text?.Trim() ?? "";
lang = lang?.Trim() ?? "ENU";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new CircularBufferWriter(buffer);
writer.Write((byte)0xAE);
writer.Write((ushort)(50 + text.Length * 2));
writer.Write(serial.Value);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.WriteAscii(lang, 4);
writer.WriteAscii(name, 30);
writer.WriteBigUniNull(text);
return writer.Position;
}
public static int CreateUnicodeMessage(
Span<byte> buffer,
Serial serial, int graphic, MessageType type, int hue, int font, string lang, string name, string text
)
{
name = name?.Trim() ?? "";
text = text?.Trim() ?? "";
lang = lang?.Trim() ?? "ENU";
if (hue == 0)
{
hue = 0x3B2;
}
var writer = new SpanWriter(buffer);
writer.Write((byte)0xAE);
writer.Write((ushort)(50 + text.Length * 2));
writer.Write(serial);
writer.Write((short)graphic);
writer.Write((byte)type);
writer.Write((short)hue);
writer.Write((short)font);
writer.WriteAscii(lang, 4);
writer.WriteAscii(name, 30);
writer.WriteBigUniNull(text);
return writer.Position;
}
private Pipe<byte>[] _pipes = new Pipe<byte>[25000];
[IterationSetup]
public void SetUp()
{
for (var i = 0; i < _pipes.Length; i++)
{
_pipes[i] = new Pipe<byte>(new byte[4096]);
}
}
[IterationCleanup]
public void CleanUp()
{
for (var i = 0; i < _pipes.Length; i++)
{
_pipes[i] = null;
}
}
[Benchmark]
public int TestCircularBuffer()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
foreach (var pipe in _pipes)
{
var result = pipe.Writer.TryGetMemory();
var length = SendUnicodeMessage(
result.Buffer,
Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
pipe.Writer.Advance((uint)length);
}
return _pipes.Length;
}
[Benchmark]
public int TestSpanWriterFromBuffer()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
foreach (var pipe in _pipes)
{
var result = pipe.Writer.TryGetMemory();
Span<byte> buffer = result.Buffer[0];
var length = CreateUnicodeMessage(
buffer, Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
pipe.Writer.Advance((uint)length);
}
return _pipes.Length;
}
[Benchmark]
public int TestSpanWriter()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
Span<byte> buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)];
var length = CreateUnicodeMessage(
buffer, Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
buffer = buffer[..length];
foreach (var pipe in _pipes)
{
var result = pipe.Writer.TryGetMemory();
result.CopyFrom(buffer);
pipe.Writer.Advance((uint)buffer.Length);
}
return _pipes.Length;
}
private static void SendUnicodeMessageWithSpan(Pipe<byte> pipe, string text)
{
Span<byte> buffer = stackalloc byte[OutgoingMessagePackets.GetMaxMessageLength(text)];
var length = CreateUnicodeMessage(
buffer, Serial.MinusOne, -1, MessageType.Regular, 0x3B2, 3, "ENU", "System", text
);
buffer = buffer[..length];
var result = pipe.Writer.TryGetMemory();
result.CopyFrom(buffer);
pipe.Writer.Advance((uint)buffer.Length);
}
[Benchmark]
public int TestSpanWriterLooped()
{
var text = "This is some really long text that we want to handle. It should take a little bit to encode this.";
foreach (var pipe in _pipes)
{
SendUnicodeMessageWithSpan(pipe, text);
}
return _pipes.Length;
}
}
}

View file

@ -1,315 +0,0 @@
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;
using Server.Gumps;
using Server.Network;
namespace Server.Tests
{
public interface IGumpWriter
{
int TextEntries { get; set; }
int Switches { get; set; }
void AppendLayout(bool val);
void AppendLayout(int val);
void AppendLayout(uint val);
void AppendLayout(Serial serial);
void AppendLayoutNS(int val);
void AppendLayout(string text);
void AppendLayoutNS(string text);
void AppendLayout(byte[] buffer);
void WriteStrings(List<string> strings);
void Flush();
}
public sealed class CloseGump : Packet
{
public CloseGump(int typeID, int buttonID) : base(0xBF)
{
EnsureCapacity(13);
Stream.Write((short)0x04);
Stream.Write(typeID);
Stream.Write(buttonID);
}
}
public sealed class DisplayGumpPacked : Packet, IGumpWriter
{
private static readonly byte[] m_True = Gump.StringToBuffer(" 1");
private static readonly byte[] m_False = Gump.StringToBuffer(" 0");
private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @");
private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@");
private static readonly byte[] m_Buffer = new byte[48];
private readonly Gump m_Gump;
private readonly PacketWriter m_Layout;
private readonly PacketWriter m_Strings;
private int m_StringCount;
static DisplayGumpPacked() => m_Buffer[0] = (byte)' ';
public DisplayGumpPacked(Gump gump)
: base(0xDD)
{
m_Gump = gump;
m_Layout = PacketWriter.CreateInstance(8192);
m_Strings = PacketWriter.CreateInstance(8192);
}
public int TextEntries { get; set; }
public int Switches { get; set; }
public void AppendLayout(bool val)
{
AppendLayout(val ? m_True : m_False);
}
public void AppendLayout(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
m_Layout.Write(m_Buffer, 0, bytes);
}
public void AppendLayout(uint val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
m_Layout.Write(m_Buffer, 0, bytes);
}
public void AppendLayout(Serial serial) => AppendLayout(serial.Value);
public void AppendLayoutNS(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1);
m_Layout.Write(m_Buffer, 1, bytes);
}
public void AppendLayoutNS(string text)
{
m_Layout.WriteAsciiFixed(text, text.Length);
}
public void AppendLayout(string text)
{
AppendLayout(m_BeginTextSeparator);
m_Layout.WriteAsciiFixed(text, text.Length);
AppendLayout(m_EndTextSeparator);
}
public void AppendLayout(byte[] buffer)
{
m_Layout.Write(buffer, 0, buffer.Length);
}
public void WriteStrings(List<string> strings)
{
m_StringCount = strings.Count;
for (var i = 0; i < strings.Count; ++i)
{
var v = strings[i] ?? "";
m_Strings.Write((ushort)v.Length);
m_Strings.WriteBigUniFixed(v, v.Length);
}
}
public void Flush()
{
EnsureCapacity(28 + (int)m_Layout.Length + (int)m_Strings.Length);
Stream.Write(m_Gump.Serial);
Stream.Write(m_Gump.TypeID);
Stream.Write(m_Gump.X);
Stream.Write(m_Gump.Y);
// Note: layout MUST be null terminated (don't listen to krrios)
m_Layout.Write((byte)0);
WritePacked(m_Layout);
Stream.Write(m_StringCount);
WritePacked(m_Strings);
PacketWriter.ReleaseInstance(m_Layout);
PacketWriter.ReleaseInstance(m_Strings);
}
private void WritePacked(PacketWriter src)
{
var buffer = src.UnderlyingStream.GetBuffer();
var length = (int)src.Length;
if (length == 0)
{
Stream.Write(0);
return;
}
var wantLength = 1 + length * 1024 / 1000;
wantLength += 4095;
wantLength &= ~4095;
var packBuffer = ArrayPool<byte>.Shared.Rent(wantLength);
var packLength = wantLength;
Zlib.Pack(packBuffer, ref packLength, buffer, length, ZlibQuality.Default);
Stream.Write(4 + packLength);
Stream.Write(length);
Stream.Write(packBuffer, 0, packLength);
ArrayPool<byte>.Shared.Return(packBuffer);
}
}
public sealed class DisplayGumpFast : Packet, IGumpWriter
{
private static readonly byte[] m_True = Gump.StringToBuffer(" 1");
private static readonly byte[] m_False = Gump.StringToBuffer(" 0");
private static readonly byte[] m_BeginTextSeparator = Gump.StringToBuffer(" @");
private static readonly byte[] m_EndTextSeparator = Gump.StringToBuffer("@");
private readonly byte[] m_Buffer = new byte[48];
private int m_LayoutLength;
public DisplayGumpFast(Gump g) : base(0xB0)
{
m_Buffer[0] = (byte)' ';
EnsureCapacity(4096);
Stream.Write(g.Serial);
Stream.Write(g.TypeID);
Stream.Write(g.X);
Stream.Write(g.Y);
Stream.Write((ushort)0xFFFF);
}
public int TextEntries { get; set; }
public int Switches { get; set; }
public void AppendLayout(bool val)
{
AppendLayout(val ? m_True : m_False);
}
public void AppendLayout(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
Stream.Write(m_Buffer, 0, bytes);
m_LayoutLength += bytes;
}
public void AppendLayout(uint val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1) + 1;
Stream.Write(m_Buffer, 0, bytes);
m_LayoutLength += bytes;
}
public void AppendLayout(Serial serial) => AppendLayout(serial.Value);
public void AppendLayoutNS(int val)
{
var toString = val.ToString();
var bytes = Encoding.ASCII.GetBytes(toString, 0, toString.Length, m_Buffer, 1);
Stream.Write(m_Buffer, 1, bytes);
m_LayoutLength += bytes;
}
public void AppendLayoutNS(string text)
{
var length = text.Length;
Stream.WriteAsciiFixed(text, length);
m_LayoutLength += length;
}
public void AppendLayout(string text)
{
AppendLayout(m_BeginTextSeparator);
var length = text.Length;
Stream.WriteAsciiFixed(text, length);
m_LayoutLength += length;
AppendLayout(m_EndTextSeparator);
}
public void AppendLayout(byte[] buffer)
{
var length = buffer.Length;
Stream.Write(buffer, 0, length);
m_LayoutLength += length;
}
public void WriteStrings(List<string> text)
{
Stream.Seek(19, SeekOrigin.Begin);
Stream.Write((ushort)m_LayoutLength);
Stream.Seek(0, SeekOrigin.End);
Stream.Write((ushort)text.Count);
for (var i = 0; i < text.Count; ++i)
{
var v = text[i] ?? "";
int length = (ushort)v.Length;
Stream.Write((ushort)length);
Stream.WriteBigUniFixed(v, length);
}
}
public void Flush()
{
}
}
public sealed class DisplaySignGump : Packet
{
public DisplaySignGump(Serial serial, int gumpID, string unknown, string caption) : base(0x8B)
{
unknown ??= "";
caption ??= "";
EnsureCapacity(15 + unknown.Length + caption.Length);
Stream.Write(serial);
Stream.Write((short)gumpID);
Stream.Write((short)(unknown.Length + 1));
Stream.WriteAsciiNull(unknown);
Stream.Write((short)(caption.Length + 1));
Stream.WriteAsciiNull(caption);
}
}
}

View file

@ -1,481 +0,0 @@
using System.Collections.Generic;
using Server.Gumps;
using Server.Network;
namespace Server.Tests.Network
{
public static class GumpUtilities
{
private static readonly byte[] m_BeginLayout = Gump.StringToBuffer("{ ");
private static readonly byte[] m_EndLayout = Gump.StringToBuffer(" }");
public static Packet Compile(this Gump g, NetState ns = null)
{
IGumpWriter disp = new DisplayGumpFast(g);
// IGumpWriter disp = new DisplayGumpPacked(g);
if (g.UseWebRender)
{
disp.AppendLayout(Gump.WebRender);
}
if (!g.Draggable)
{
disp.AppendLayout(Gump.NoMove);
}
if (!g.Closable)
{
disp.AppendLayout(Gump.NoClose);
}
if (!g.Disposable)
{
disp.AppendLayout(Gump.NoDispose);
}
if (!g.Resizable)
{
disp.AppendLayout(Gump.NoResize);
}
var count = g.Entries.Count;
var strings = new List<string>();
for (var i = 0; i < count; ++i)
{
var e = g.Entries[i];
disp.AppendLayout(m_BeginLayout);
e.AppendToByType(disp, strings);
disp.AppendLayout(m_EndLayout);
}
disp.WriteStrings(strings);
disp.Flush();
return (Packet)disp;
}
public static int Intern(this List<string> strings, string value)
{
var indexOf = strings.IndexOf(value);
if (indexOf >= 0)
{
return indexOf;
}
strings.Add(value);
return strings.Count - 1;
}
public static void AppendToByType(this GumpEntry e, IGumpWriter disp, List<string> strings)
{
switch (e)
{
case GumpAlphaRegion g:
{
g.AppendTo(disp, strings);
break;
}
case GumpBackground g:
{
g.AppendTo(disp, strings);
break;
}
case GumpButton g:
{
g.AppendTo(disp, strings);
break;
}
case GumpCheck g:
{
g.AppendTo(disp, strings);
break;
}
case GumpGroup g:
{
g.AppendTo(disp, strings);
break;
}
case GumpECHandleInput g:
{
g.AppendTo(disp, strings);
break;
}
case GumpHtml g:
{
g.AppendTo(disp, strings);
break;
}
case GumpHtmlLocalized g:
{
g.AppendTo(disp, strings);
break;
}
case GumpImage g:
{
g.AppendTo(disp, strings);
break;
}
case GumpImageTileButton g:
{
g.AppendTo(disp, strings);
break;
}
case GumpImageTiled g:
{
g.AppendTo(disp, strings);
break;
}
case GumpItem g:
{
g.AppendTo(disp, strings);
break;
}
case GumpItemProperty g:
{
g.AppendTo(disp, strings);
break;
}
case GumpLabel g:
{
g.AppendTo(disp, strings);
break;
}
case GumpLabelCropped g:
{
g.AppendTo(disp, strings);
break;
}
case GumpMasterGump g:
{
g.AppendTo(disp, strings);
break;
}
case GumpPage g:
{
g.AppendTo(disp, strings);
break;
}
case GumpRadio g:
{
g.AppendTo(disp, strings);
break;
}
case GumpSpriteImage g:
{
g.AppendTo(disp, strings);
break;
}
case GumpTextEntry g:
{
g.AppendTo(disp, strings);
break;
}
case GumpTextEntryLimited g:
{
g.AppendTo(disp, strings);
break;
}
case GumpTooltip g:
{
g.AppendTo(disp, strings);
break;
}
}
}
public static void AppendTo(this GumpAlphaRegion g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpAlphaRegion.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
}
public static void AppendTo(this GumpBackground g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpBackground.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
}
public static void AppendTo(this GumpButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
disp.AppendLayout(g.PressedID);
disp.AppendLayout((int)g.Type);
disp.AppendLayout(g.Param);
disp.AppendLayout(g.ButtonID);
}
public static void AppendTo(this GumpCheck g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
disp.AppendLayout(g.ActiveID);
disp.AppendLayout(g.InitialState);
disp.AppendLayout(g.SwitchID);
disp.Switches++;
}
public static void AppendTo(this GumpGroup g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpGroup.LayoutName);
disp.AppendLayout(g.Group);
}
public static void AppendTo(this GumpECHandleInput g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpECHandleInput.LayoutName);
}
public static void AppendTo(this GumpHtml g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpHtml.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(strings.Intern(g.Text));
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
}
public static void AppendTo(this GumpHtmlLocalized g, IGumpWriter disp, List<string> strings)
{
switch (g.Type)
{
case GumpHtmlLocalizedType.Plain:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNamePlain);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Number);
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
break;
}
case GumpHtmlLocalizedType.Color:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameColor);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Number);
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
disp.AppendLayout(g.Color);
break;
}
case GumpHtmlLocalizedType.Args:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameArgs);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Background);
disp.AppendLayout(g.Scrollbar);
disp.AppendLayout(g.Color);
disp.AppendLayout(g.Number);
disp.AppendLayout(g.Args);
break;
}
}
}
public static void AppendTo(this GumpImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImage.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
if (g.Hue != 0)
{
disp.AppendLayout(GumpImage.HueEquals);
disp.AppendLayoutNS(g.Hue);
}
if (!string.IsNullOrEmpty(g.Class))
{
disp.AppendLayout(GumpImage.ClassEquals);
disp.AppendLayoutNS(g.Class);
}
}
public static void AppendTo(this GumpImageTileButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTileButton.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
disp.AppendLayout(g.PressedID);
disp.AppendLayout((int)g.Type);
disp.AppendLayout(g.Param);
disp.AppendLayout(g.ButtonID);
disp.AppendLayout(g.ItemID);
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
if (g.LocalizedTooltip > 0)
{
disp.AppendLayout(GumpImageTileButton.LayoutTooltip);
disp.AppendLayout(g.LocalizedTooltip);
}
}
public static void AppendTo(this GumpImageTiled g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTiled.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.GumpID);
}
public static void AppendTo(this GumpItem g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(g.Hue == 0 ? GumpItem.LayoutName : GumpItem.LayoutNameHue);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.ItemID);
if (g.Hue != 0)
{
disp.AppendLayout(g.Hue);
}
}
public static void AppendTo(this GumpItemProperty g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpItemProperty.LayoutName);
disp.AppendLayout(g.Serial);
}
public static void AppendTo(this GumpLabel g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabel.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Hue);
disp.AppendLayout(strings.Intern(g.Text));
}
public static void AppendTo(this GumpLabelCropped g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabelCropped.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Hue);
disp.AppendLayout(strings.Intern(g.Text));
}
public static void AppendTo(this GumpMasterGump g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpMasterGump.LayoutName);
disp.AppendLayout(g.GumpID);
}
public static void AppendTo(this GumpPage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpPage.LayoutName);
disp.AppendLayout(g.Page);
}
public static void AppendTo(this GumpRadio g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpRadio.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
disp.AppendLayout(g.ActiveID);
disp.AppendLayout(g.InitialState);
disp.AppendLayout(g.SwitchID);
disp.Switches++;
}
public static void AppendTo(this GumpSpriteImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpSpriteImage.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.SX);
disp.AppendLayout(g.SY);
}
public static void AppendTo(this GumpTextEntry g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntry.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.EntryID);
disp.AppendLayout(strings.Intern(g.InitialText));
disp.TextEntries++;
}
public static void AppendTo(this GumpTextEntryLimited g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntryLimited.LayoutName);
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.EntryID);
disp.AppendLayout(strings.Intern(g.InitialText));
disp.AppendLayout(g.Size);
disp.TextEntries++;
}
public static void AppendTo(this GumpTooltip g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTooltip.LayoutName);
disp.AppendLayout(g.Number);
if (!string.IsNullOrEmpty(g.Args))
{
disp.AppendLayout(g.Args);
}
}
}
}

View file

@ -1,265 +0,0 @@
using System;
using System.Buffers;
using System.Diagnostics;
using System.IO;
using Server.Diagnostics;
namespace Server.Network
{
public abstract class Packet
{
private const int CompressorBufferSize = 0x10000;
private readonly int m_Length;
private byte[] m_CompiledBuffer;
private int m_CompiledLength;
private State m_State;
protected Packet(int packetID)
{
PacketID = packetID;
if (Core.Profiling)
{
var prof = PacketSendProfile.Acquire(PacketID);
prof.Increment();
}
}
protected Packet(int packetID, int length)
{
PacketID = packetID;
m_Length = length;
Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length );
Stream.Write((byte)packetID);
if (Core.Profiling)
{
var prof = PacketSendProfile.Acquire(PacketID);
prof.Increment();
}
}
public int PacketID { get; }
public PacketWriter Stream { get; protected set; }
public void EnsureCapacity(int length)
{
Stream = PacketWriter.CreateInstance(length); // new PacketWriter( length );
Stream.Write((byte)PacketID);
Stream.Write((short)0);
}
public static Packet SetStatic(Packet p)
{
p.SetStatic();
return p;
}
public static Packet Acquire(Packet p)
{
p.Acquire();
return p;
}
public static void Release(ref Packet p)
{
p?.Release();
p = null;
}
public static void Release(Packet p)
{
p?.Release();
}
public void SetStatic()
{
m_State |= State.Static | State.Acquired;
}
public void Acquire()
{
m_State |= State.Acquired;
}
public void OnSend()
{
if ((m_State & (State.Acquired | State.Static)) == 0)
{
Free();
}
}
private void Free()
{
if (m_CompiledBuffer == null)
{
return;
}
if ((m_State & State.Buffered) != 0)
{
ArrayPool<byte>.Shared.Return(m_CompiledBuffer);
}
m_State &= ~(State.Static | State.Acquired | State.Buffered);
m_CompiledBuffer = null;
}
public void Release()
{
if ((m_State & State.Acquired) != 0)
{
Free();
}
}
private readonly object _object = new();
public byte[] Compile(bool compress, out int length)
{
lock (_object)
{
if (m_CompiledBuffer == null)
{
if ((m_State & State.Accessed) == 0)
{
m_State |= State.Accessed;
}
else
{
if ((m_State & State.Warned) == 0)
{
m_State |= State.Warned;
try
{
using var op = new StreamWriter("net_opt.log", true);
op.WriteLine("Redundant compile for packet {0}, use Acquire() and Release()", GetType());
op.WriteLine(new StackTrace());
}
catch
{
// ignored
}
}
m_CompiledBuffer = Array.Empty<byte>();
m_CompiledLength = 0;
length = m_CompiledLength;
return m_CompiledBuffer;
}
InternalCompile(compress);
}
length = m_CompiledLength;
return m_CompiledBuffer;
}
}
private void InternalCompile(bool compress)
{
if (m_Length == 0)
{
var streamLen = Stream.Length;
Stream.Seek(1, SeekOrigin.Begin);
Stream.Write((ushort)streamLen);
}
else if (Stream.Length != m_Length)
{
var diff = (int)Stream.Length - m_Length;
Console.WriteLine(
"Packet: 0x{0:X2}: Bad packet length! ({1}{2} bytes)",
PacketID,
diff >= 0 ? "+" : "",
diff
);
}
var ms = Stream.UnderlyingStream;
m_CompiledBuffer = ms.GetBuffer();
var length = (int)ms.Length;
if (compress)
{
var compressorBuffer = new byte[CompressorBufferSize];
var compressedLength = NetworkCompression.Compress(m_CompiledBuffer.AsSpan(0, length), compressorBuffer);
if (length <= 0)
{
Console.WriteLine(
"Warning: Compression buffer overflowed on packet 0x{0:X2} ('{1}') (length={2})",
PacketID,
GetType().Name,
length
);
using var op = new StreamWriter("compression_overflow.log", true);
op.WriteLine(
"{0} Warning: Compression buffer overflowed on packet 0x{1:X2} ('{2}') (length={3})",
Core.Now,
PacketID,
GetType().Name,
length
);
op.WriteLine(new StackTrace());
}
else
{
m_CompiledBuffer = compressorBuffer;
m_CompiledLength = compressedLength;
}
}
else
{
m_CompiledLength = length;
}
if (m_CompiledLength > 0)
{
var old = m_CompiledBuffer;
if ((m_State & State.Static) != 0)
{
m_CompiledBuffer = new byte[m_CompiledLength];
}
else
{
// Release it later using Release()
m_CompiledBuffer = ArrayPool<byte>.Shared.Rent(m_CompiledLength);
m_State |= State.Buffered;
}
Buffer.BlockCopy(old, 0, m_CompiledBuffer, 0, m_CompiledLength);
if (compress)
{
ArrayPool<byte>.Shared.Return(old);
}
}
PacketWriter.ReleaseInstance(Stream);
Stream = null;
}
[Flags]
private enum State
{
Inactive = 0x00,
Static = 0x01,
Acquired = 0x02,
Accessed = 0x04,
Buffered = 0x08,
Warned = 0x10
}
}
}

View file

@ -1,31 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using Server;
using Server.Network;
namespace Benchmarks
{
public static class PacketTestUtilities
{
public static Span<byte> Compile(this Packet p) =>
p.Compile(false, out var length).AsSpan(0, length);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(this Span<byte> data, ref int pos, Serial serial)
{
BinaryPrimitives.WriteUInt32BigEndian(data.Slice(pos, 4), serial.Value);
pos += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(this Span<byte> data, ref int pos, ushort value)
{
BinaryPrimitives.WriteUInt16BigEndian(data.Slice(pos, 2), value);
pos += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Write(this Span<byte> data, ref int pos, byte value) => data[pos++] = value;
}
}

View file

@ -1,354 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Text;
namespace Server.Network
{
/// <summary>
/// Provides functionality for writing primitive binary data.
/// </summary>
public class PacketWriter
{
private static readonly ConcurrentQueue<PacketWriter> m_Pool = new();
/// <summary>
/// Internal format buffer.
/// </summary>
private readonly byte[] m_Buffer = new byte[4];
private int m_Capacity;
/// <summary>
/// Instantiates a new PacketWriter instance with a given capacity.
/// </summary>
/// <param name="capacity">Initial capacity for the internal stream.</param>
public PacketWriter(int capacity = 32)
{
UnderlyingStream = new MemoryStream(capacity);
m_Capacity = capacity;
}
/// <summary>
/// Gets the total stream length.
/// </summary>
public long Length => UnderlyingStream.Length;
/// <summary>
/// Gets or sets the current stream position.
/// </summary>
public long Position
{
get => UnderlyingStream.Position;
set => UnderlyingStream.Position = value;
}
/// <summary>
/// The internal stream used by this PacketWriter instance.
/// </summary>
public MemoryStream UnderlyingStream { get; }
public static PacketWriter CreateInstance(int capacity = 32)
{
if (m_Pool.TryDequeue(out var pw))
{
pw.m_Capacity = capacity;
pw.UnderlyingStream.SetLength(0);
return pw;
}
return new PacketWriter(capacity);
}
public static void ReleaseInstance(PacketWriter pw)
{
m_Pool.Enqueue(pw);
}
/// <summary>
/// Writes a 1-byte boolean value to the underlying stream. False is represented by 0, true by 1.
/// </summary>
public void Write(bool value)
{
UnderlyingStream.WriteByte((byte)(value ? 1 : 0));
}
/// <summary>
/// Writes a 1-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write(byte value)
{
UnderlyingStream.WriteByte(value);
}
/// <summary>
/// Writes a 1-byte signed integer value to the underlying stream.
/// </summary>
public void Write(sbyte value)
{
UnderlyingStream.WriteByte((byte)value);
}
/// <summary>
/// Writes a 2-byte signed integer value to the underlying stream.
/// </summary>
public void Write(short value)
{
m_Buffer[0] = (byte)(value >> 8);
m_Buffer[1] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 2);
}
/// <summary>
/// Writes a 2-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write(ushort value)
{
m_Buffer[0] = (byte)(value >> 8);
m_Buffer[1] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 2);
}
public void Write(Serial serial) => Write(serial.Value);
/// <summary>
/// Writes a 4-byte signed integer value to the underlying stream.
/// </summary>
public void Write(int value)
{
m_Buffer[0] = (byte)(value >> 24);
m_Buffer[1] = (byte)(value >> 16);
m_Buffer[2] = (byte)(value >> 8);
m_Buffer[3] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 4);
}
/// <summary>
/// Writes a 4-byte unsigned integer value to the underlying stream.
/// </summary>
public void Write(uint value)
{
m_Buffer[0] = (byte)(value >> 24);
m_Buffer[1] = (byte)(value >> 16);
m_Buffer[2] = (byte)(value >> 8);
m_Buffer[3] = (byte)value;
UnderlyingStream.Write(m_Buffer, 0, 4);
}
/// <summary>
/// Writes a sequence of bytes to the underlying stream
/// </summary>
public void Write(byte[] buffer, int offset, int size)
{
UnderlyingStream.Write(buffer, offset, size);
}
/// <summary>
/// Writes a fixed-length ASCII-encoded string value to the underlying stream. To fit (size), the string content is either
/// truncated or padded with null characters.
/// </summary>
public void WriteAsciiFixed(string value, int size)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteAsciiFixed() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + size);
if (length >= size)
{
UnderlyingStream.Position +=
Encoding.ASCII.GetBytes(value, 0, size, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
}
else
{
Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += size;
}
}
/// <summary>
/// Writes a dynamic-length ASCII-encoded string value to the underlying stream, followed by a 1-byte null character.
/// </summary>
public void WriteAsciiNull(string value)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteAsciiNull() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + length + 1);
Encoding.ASCII.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += length + 1;
}
/// <summary>
/// Writes a dynamic-length little-endian unicode string value to the underlying stream, followed by a 2-byte null
/// character.
/// </summary>
public void WriteLittleUniNull(string value)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteLittleUniNull() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2);
UnderlyingStream.Position +=
Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += 2;
}
/// <summary>
/// Writes a fixed-length little-endian unicode string value to the underlying stream. To fit (size), the string content is
/// either truncated or padded with null characters.
/// </summary>
public void WriteLittleUniFixed(string value, int size)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteLittleUniFixed() with null value");
value = string.Empty;
}
var length = value.Length;
size *= 2;
UnderlyingStream.SetLength(UnderlyingStream.Length + size);
if (length * 2 >= size)
{
UnderlyingStream.Position +=
Encoding.Unicode.GetBytes(
value,
0,
size / 2,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
}
else
{
Encoding.Unicode.GetBytes(value, 0, length, UnderlyingStream.GetBuffer(), (int)UnderlyingStream.Position);
UnderlyingStream.Position += size;
}
}
/// <summary>
/// Writes a dynamic-length big-endian unicode string value to the underlying stream, followed by a 2-byte null character.
/// </summary>
public void WriteBigUniNull(string value)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteBigUniNull() with null value");
value = string.Empty;
}
var length = value.Length;
UnderlyingStream.SetLength(UnderlyingStream.Length + (length + 1) * 2);
UnderlyingStream.Position +=
Encoding.BigEndianUnicode.GetBytes(
value,
0,
length,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
UnderlyingStream.Position += 2;
}
/// <summary>
/// Writes a fixed-length big-endian unicode string value to the underlying stream. To fit (size), the string content is
/// either truncated or padded with null characters.
/// </summary>
public void WriteBigUniFixed(string value, int size)
{
if (value == null)
{
Console.WriteLine("Network: Attempted to WriteBigUniFixed() with null value");
value = string.Empty;
}
var length = value.Length;
size *= 2;
UnderlyingStream.SetLength(UnderlyingStream.Length + size);
if (length * 2 >= size)
{
UnderlyingStream.Position +=
Encoding.BigEndianUnicode.GetBytes(
value,
0,
size / 2,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
}
else
{
Encoding.BigEndianUnicode.GetBytes(
value,
0,
length,
UnderlyingStream.GetBuffer(),
(int)UnderlyingStream.Position
);
UnderlyingStream.Position += size;
}
}
/// <summary>
/// Fills the stream from the current position up to (capacity) with 0x00's
/// </summary>
public void Fill()
{
Fill(m_Capacity - UnderlyingStream.Length);
}
/// <summary>
/// Writes a number of 0x00 byte values to the underlying stream.
/// </summary>
public void Fill(long length)
{
if (UnderlyingStream.Position == UnderlyingStream.Length)
{
UnderlyingStream.SetLength(UnderlyingStream.Length + length);
UnderlyingStream.Seek(0, SeekOrigin.End);
}
else
{
UnderlyingStream.Write(new byte[length], 0, (int)length);
}
}
/// <summary>
/// Offsets the current position from an origin.
/// </summary>
public long Seek(long offset, SeekOrigin origin) => UnderlyingStream.Seek(offset, origin);
/// <summary>
/// Gets the entire stream content as a byte array.
/// </summary>
public byte[] ToArray() => UnderlyingStream.ToArray();
}
}

View file

@ -1,28 +0,0 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Random;
namespace Benchmarks.Benchmarks.Rng
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net60)]
public class BenchmarkDoubleVsFixed
{
private Xoshiro256PlusPlus _xoshiro256PlusPlus;
[GlobalSetup]
public void Setup()
{
_xoshiro256PlusPlus = new Xoshiro256PlusPlus();
}
[Benchmark]
public bool NextDouble() => 50.1 < _xoshiro256PlusPlus.NextDouble() * 100;
[Benchmark]
public bool NextFixedInt() => 501 < _xoshiro256PlusPlus.Next(1000);
[Benchmark]
public bool NextHighResDouble() => 50.1 < _xoshiro256PlusPlus.NextDoubleHighRes() * 100;
}
}

View file

@ -1,46 +0,0 @@
using System;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Random;
namespace Benchmarks.Benchmarks.Rng
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net60)]
public class BenchmarkXoshiro
{
private Random _random;
private Xoshiro256PlusPlus _xoshiro256PlusPlus;
[GlobalSetup]
public void Setup()
{
_xoshiro256PlusPlus = new Xoshiro256PlusPlus();
_random = new Random();
}
[Benchmark]
public int SystemRandomULong() => _random.Next(10000);
[Benchmark]
public int XoshiroRandomULong() => _xoshiro256PlusPlus.Next(10000);
[Benchmark]
public double SystemRandomDouble() => _random.NextDouble();
[Benchmark]
public double XoshiroRandomDouble() => _xoshiro256PlusPlus.NextDouble();
[Benchmark]
public int SystemRandomMinMax() => _random.Next(5000, 85000);
[Benchmark]
public int XoshiroRandomMinMax()
{
const int min = 5000;
const int max = 85000;
return min + (int)_xoshiro256PlusPlus.Next((uint)(max - min + 1));
}
}
}

View file

@ -1,32 +0,0 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Text;
namespace Benchmarks.BenchmarkText
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net60)]
public class BenchmarkTextEncoding
{
private const string text =
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l" +
"This is supposed to be a really long text Ƞă¼ÖƄŭȘú😅¥♓ǵDƤĂ😋ġǁ⚕😐'ƿī😪_l";
[Benchmark]
public byte[] TestEncodingOldReturnBytes()
{
var bytes = TextEncoding.UTF8.GetBytes(text);
return bytes;
}
[Benchmark]
public byte[] TestEncodingNewReturnBytes()
{
var bytes = text.GetBytesUtf8();
return bytes;
}
}
}

View file

@ -1,87 +0,0 @@
using System.Buffers;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using Server.Buffers;
namespace Benchmarks.BenchmarkUtilities
{
[MemoryDiagnoser]
[SimpleJob(RuntimeMoniker.Net60)]
public class BenchmarkStringHelpers
{
private readonly string[] names =
{
"Kamron", "Owyn", "Luthius", "Jaedan", "Vorspire", "other people",
"Kamron-2", "Owyn-2", "Luthius-2", "Jaedan-2", "Vorspire-2", "other people too"
};
private int length;
[GlobalSetup]
public void Setup()
{
var chrs = ArrayPool<char>.Shared.Rent(65535);
ArrayPool<char>.Shared.Return(chrs);
length = 0;
for (int i = 0; i < names.Length; i++)
{
length += names.Length;
}
length += 2 * (names.Length - 1) + 3;
}
[Benchmark]
public string BenchmarkStringBuilder()
{
var sb = new StringBuilder();
for (var i = 0; i < names.Length; i++)
{
if (i > 0)
{
sb.Append(i == names.Length - 1 ? ", and" : ", ");
}
sb.Append(names[i]);
}
return sb.ToString();
}
[Benchmark]
public string BenchmarkValueStringBuilderWithStack()
{
using var sb = new ValueStringBuilder(stackalloc char[length]);
for (var i = 0; i < names.Length; i++)
{
if (i > 0)
{
sb.Append(i == names.Length - 1 ? ", and" : ", ");
}
sb.Append(names[i]);
}
return sb.ToString();
}
[Benchmark]
public string BenchmarkValueStringBuilderWithRentedBuffer()
{
using var sb = new ValueStringBuilder(stackalloc char[32]);
for (var i = 0; i < names.Length; i++)
{
if (i > 0)
{
sb.Append(i == names.Length - 1 ? ", and" : ", ");
}
sb.Append(names[i]);
}
return sb.ToString();
}
}
}

View file

@ -1,3 +0,0 @@
<Project>
<!-- Only here so that the default Directory.Build.props will not be used. -->
</Project>

View file

@ -1,24 +0,0 @@
using BenchmarkDotNet.Running;
using Benchmarks.Benchmarks.Rng;
namespace Benchmarks
{
public static class Program
{
private static void Main(string[] args)
{
// var featureFlags = BenchmarkRunner.Run<BenchmarkFeatureFlags>();
// var packetConstruction = BenchmarkRunner.Run<BenchmarkPacketConstruction>();
// var broadcast = BenchmarkRunner.Run<BenchmarkPacketBroadcast>();
// var stringHelpers = BenchmarkRunner.Run<BenchmarkStringHelpers>();
// var indexList = BenchmarkRunner.Run<BenchmarkOrderedHashSet>();
// var textEncoding = BenchmarkRunner.Run<BenchmarkTextEncoding>();
// var logging = BenchmarkRunner.Run<BenchmarkConsoleLogging>();
// var gumpPacket = BenchmarkRunner.Run<OutgoingGumpPacketBenchmarks>();
// MapSelectors.Init();
// var mapSelectors = BenchmarkRunner.Run<MapSelectors>();
// var rngTest = BenchmarkRunner.Run<BenchmarkXoshiro>();
var doubleRngText = BenchmarkRunner.Run<BenchmarkDoubleVsFixed>();
}
}
}

View file

@ -1,91 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: EntityJsonGenerator.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using SerializableMigration;
namespace SerializationGenerator
{
[Generator]
public class EntitySerializationGenerator : ISourceGenerator
{
public void Initialize(GeneratorInitializationContext context)
{
context.RegisterForSyntaxNotifications(() => new SerializerSyntaxReceiver());
}
public void Execute(GeneratorExecutionContext context)
{
if (context.SyntaxContextReceiver is not SerializerSyntaxReceiver receiver)
{
return;
}
var jsonOptions = SerializableMigrationSchema.GetJsonSerializerOptions();
// List of types that _will_ become ISerializable
var serializableList = receiver.SerializableList;
var embeddedSerializableList = receiver.EmbeddedSerializableList;
foreach (var (classSymbol, (serializableAttr, fieldsList)) in receiver.ClassAndFields)
{
if (serializableAttr == null)
{
continue;
}
string classSource = context.GenerateSerializationPartialClass(
classSymbol,
serializableAttr,
false,
fieldsList.ToImmutableArray(),
jsonOptions,
serializableList,
embeddedSerializableList
);
if (classSource != null)
{
context.AddSource($"{classSymbol.ToDisplayString()}.Serialization.cs", SourceText.From(classSource, Encoding.UTF8));
}
}
foreach (var (classSymbol, (embeddedSerializableAttr, fieldsList)) in receiver.EmbeddedClassAndFields)
{
if (embeddedSerializableAttr == null)
{
continue;
}
string classSource = context.GenerateSerializationPartialClass(
classSymbol,
embeddedSerializableAttr,
true,
fieldsList.ToImmutableArray(),
jsonOptions,
serializableList,
embeddedSerializableList
);
if (classSource != null)
{
context.AddSource($"{classSymbol.ToDisplayString()}.Serialization.cs", SourceText.From(classSource, Encoding.UTF8));
}
}
}
}
}

View file

@ -1,4 +0,0 @@
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit {}
}

View file

@ -1,436 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.Class.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static string GenerateSerializationPartialClass(
this GeneratorExecutionContext context,
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
bool embedded,
ImmutableArray<ISymbol> fieldsAndProperties,
JsonSerializerOptions jsonSerializerOptions,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
var migrations = context.GetMigrationsByAnalyzerConfig(
classSymbol,
version,
jsonSerializerOptions
);
return context.Compilation.GenerateSerializationPartialClass(
classSymbol,
serializableAttr,
null, // Do not generate schema
embedded,
null,
migrations.ToImmutableArray(),
fieldsAndProperties,
serializableTypes,
embeddedSerializableTypes
);
}
public static string GenerateSerializationPartialClass(
this Compilation compilation,
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
string? migrationPath,
bool embedded,
JsonSerializerOptions? jsonSerializerOptions,
ImmutableArray<ISymbol> fieldsAndProperties,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
var migrations = SerializableMigrationSchema.GetMigrations(
classSymbol,
version,
migrationPath,
jsonSerializerOptions
);
return compilation.GenerateSerializationPartialClass(
classSymbol,
serializableAttr,
migrationPath,
embedded,
jsonSerializerOptions,
migrations.ToImmutableArray(),
fieldsAndProperties,
serializableTypes,
embeddedSerializableTypes
);
}
public static string GenerateSerializationPartialClass(
this Compilation compilation,
INamedTypeSymbol classSymbol,
AttributeData serializableAttr,
string? migrationPath,
bool embedded,
JsonSerializerOptions? jsonSerializerOptions,
ImmutableArray<SerializableMetadata> migrations,
ImmutableArray<ISymbol> fieldsAndProperties,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
)
{
var serializableFieldAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE);
var serializableFieldAttrAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTR_ATTRIBUTE);
var serializableInterface =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_INTERFACE);
var parentSerializableAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE);
var serializableFieldSaveFlagAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE);
var serializableFieldDefaultAttribute =
compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_DEFAULT_ATTRIBUTE);
// If we have a parent that is or derives from ISerializable, then we are in override
var isOverride = classSymbol.BaseType.ContainsInterface(serializableInterface);
if (!(embedded || isOverride || classSymbol.ContainsInterface(serializableInterface)))
{
return null;
}
var isRawSerializable = classSymbol.HasRawSerializableInterface(compilation, ImmutableArray<INamedTypeSymbol>.Empty);
var version = (int)serializableAttr.ConstructorArguments[0].Value!;
var encodedVersion = (bool)serializableAttr.ConstructorArguments[1].Value!;
// Let's find out if we need to do serialization flags
var serializableFieldSaveFlags = new SortedDictionary<int, SerializableFieldSaveFlagMethods>();
foreach (var m in classSymbol.GetMembers().OfType<IMethodSymbol>())
{
var getSaveFlagAttribute = m.GetAttribute(serializableFieldSaveFlagAttribute);
var getDefaultValueAttribute = m.GetAttribute(serializableFieldDefaultAttribute);
if (getSaveFlagAttribute == null && getDefaultValueAttribute == null)
{
continue;
}
var attrCtorArgs = getSaveFlagAttribute?.ConstructorArguments ?? getDefaultValueAttribute.ConstructorArguments;
var order = (int)attrCtorArgs[0].Value!;
serializableFieldSaveFlags.TryGetValue(order, out var saveFlagMethods);
serializableFieldSaveFlags[order] = new SerializableFieldSaveFlagMethods
{
DetermineFieldShouldSerialize = getSaveFlagAttribute != null ? m : saveFlagMethods?.DetermineFieldShouldSerialize,
GetFieldDefaultValue = getDefaultValueAttribute != null ? m : saveFlagMethods?.GetFieldDefaultValue
};
}
var namespaceName = classSymbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name;
StringBuilder source = new StringBuilder();
source.AppendLine("#pragma warning disable\n");
source.GenerateNamespaceStart(namespaceName);
var interfaces = !embedded || isRawSerializable
? Array.Empty<ITypeSymbol>()
: new ITypeSymbol[] { compilation.GetTypeByMetadataName(SymbolMetadata.RAW_SERIALIZABLE_INTERFACE) };
var indent = " ";
source.RecursiveGenerateClassStart(classSymbol, interfaces.ToImmutableArray(), ref indent);
source.GenerateClassField(
indent,
Accessibility.Private,
InstanceModifier.Const,
"int",
"_version",
version.ToString()
);
source.AppendLine();
var parentFieldOrProperty = embedded ? fieldsAndProperties.FirstOrDefault(
fieldOrPropertySymbol => fieldOrPropertySymbol.GetAttributes()
.FirstOrDefault(
attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, parentSerializableAttribute)
) != null
) : null;
var serializablePropertySet = new SortedDictionary<SerializableProperty, ISymbol>(new SerializablePropertyComparer());
foreach (var fieldOrPropertySymbol in fieldsAndProperties)
{
var allAttributes = fieldOrPropertySymbol.GetAttributes();
var serializableFieldAttr = allAttributes
.FirstOrDefault(
attr =>
SymbolEqualityComparer.Default.Equals(attr.AttributeClass, serializableFieldAttribute)
);
if (serializableFieldAttr == null)
{
continue;
}
foreach (var attr in allAttributes)
{
if (!SymbolEqualityComparer.Default.Equals(attr.AttributeClass, serializableFieldAttrAttribute))
{
continue;
}
if (attr.AttributeClass == null)
{
continue;
}
var ctorArgs = attr.ConstructorArguments;
var attrTypeArg = ctorArgs[0];
if (attrTypeArg.Kind == TypedConstantKind.Primitive && attrTypeArg.Value is string attrStr)
{
source.AppendLine($"{indent}{attrStr}");
}
else
{
var attrType = (ITypeSymbol)attrTypeArg.Value;
source.GenerateAttribute(indent, attrType?.Name, ctorArgs[1].Values);
}
}
var attrCtorArgs = serializableFieldAttr.ConstructorArguments;
var order = (int)attrCtorArgs[0].Value!;
var getterAccessor = Helpers.GetAccessibility(attrCtorArgs[1].Value?.ToString());
var setterAccessor = Helpers.GetAccessibility(attrCtorArgs[2].Value?.ToString());
var virtualProperty = (bool)attrCtorArgs[3].Value!;
if (fieldOrPropertySymbol is IFieldSymbol fieldSymbol)
{
source.GenerateSerializableProperty(
compilation,
indent,
fieldSymbol,
getterAccessor,
setterAccessor,
virtualProperty,
parentFieldOrProperty
);
source.AppendLine();
}
serializableFieldSaveFlags.TryGetValue(order, out var serializableFieldSaveFlagMethods);
var serializableProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
fieldOrPropertySymbol,
order,
allAttributes,
serializableTypes,
embeddedSerializableTypes,
classSymbol,
serializableFieldSaveFlagMethods
);
serializablePropertySet.Add(serializableProperty, fieldOrPropertySymbol);
}
var serializableFields = serializablePropertySet.Keys.ToImmutableArray();
var serializableProperties = serializablePropertySet.Select(
kvp => kvp.Key with
{
Name = (kvp.Value as IFieldSymbol)?.GetPropertyName() ?? ((IPropertySymbol)kvp.Value).Name
}
).ToImmutableArray();
// If we are not inheriting ISerializable, then we need to define some stuff
if (!(isOverride || embedded))
{
// long ISerializable.SavePosition { get; set; } = -1;
source.GenerateAutoProperty(
Accessibility.NotApplicable,
"long",
"ISerializable.SavePosition",
Accessibility.NotApplicable,
Accessibility.NotApplicable,
indent,
defaultValue: "-1"
);
// BufferWriter ISerializable.SaveBuffer { get; set; }
source.GenerateAutoProperty(
Accessibility.NotApplicable,
"BufferWriter",
"ISerializable.SaveBuffer",
Accessibility.NotApplicable,
Accessibility.NotApplicable,
indent
);
}
if (!embedded)
{
// Serial constructor
source.GenerateSerialCtor(compilation, className, indent, isOverride);
source.AppendLine();
}
if (version > 0)
{
for (var i = 0; i < migrations.Length; i++)
{
var migration = migrations[i];
if (migration.Version < version)
{
source.GenerateMigrationContentStruct(compilation, indent, migration, classSymbol);
source.AppendLine();
}
}
}
// Serialize Method
source.GenerateSerializeMethod(
compilation,
indent,
isOverride,
encodedVersion,
serializableFields,
serializableProperties,
serializableFieldSaveFlags
);
source.AppendLine();
// Deserialize Method
source.GenerateDeserializeMethod(
compilation,
classSymbol,
indent,
isOverride,
version,
encodedVersion,
migrations,
serializableFields,
serializableProperties,
parentFieldOrProperty,
serializableFieldSaveFlags
);
// Serialize SaveFlag enum class
if (serializableFieldSaveFlags.Count > 0)
{
source.AppendLine();
source.GenerateEnumStart(
"SaveFlag",
$"{indent} ",
true,
Accessibility.Private
);
source.GenerateEnumValue($"{indent} ", true, "None", -1);
int index = 0;
foreach (var (order, _) in serializableFieldSaveFlags)
{
source.GenerateEnumValue($"{indent} ", true, serializableProperties[order].Name, index++);
}
source.GenerateEnumEnd($"{indent} ");
}
source.RecursiveGenerateClassEnd(classSymbol, ref indent);
source.GenerateNamespaceEnd();
if (migrationPath != null)
{
// Write the migration file
var newMigration = new SerializableMetadata
{
Version = version,
Type = classSymbol.ToDisplayString(),
Properties = serializableProperties.Length > 0 ? serializableProperties : null
};
WriteMigration(migrationPath, newMigration, jsonSerializerOptions);
}
return source.ToString();
}
private static void WriteMigration(string migrationPath, SerializableMetadata metadata, JsonSerializerOptions options)
{
Directory.CreateDirectory(migrationPath);
var filePath = Path.Combine(migrationPath, $"{metadata.Type}.v{metadata.Version}.json");
File.WriteAllText(filePath, JsonSerializer.Serialize(metadata, options));
}
private static void RecursiveGenerateClassStart(
this StringBuilder source,
INamedTypeSymbol classSymbol,
ImmutableArray<ITypeSymbol> interfaces,
ref string indent
)
{
var containingSymbolList = new List<INamedTypeSymbol>();
do
{
containingSymbolList.Add(classSymbol);
classSymbol = classSymbol.ContainingSymbol as INamedTypeSymbol;
} while (classSymbol != null);
containingSymbolList.Reverse();
for (var i = 0; i < containingSymbolList.Count; i++)
{
var symbol = containingSymbolList[i];
source.GenerateClassStart(symbol, indent, i == containingSymbolList.Count - 1 ? interfaces : ImmutableArray<ITypeSymbol>.Empty);
indent += " ";
}
}
private static void RecursiveGenerateClassEnd(this StringBuilder source, INamedTypeSymbol classSymbol, ref string indent)
{
do
{
indent = indent.Substring(0, indent.Length - 4);
source.GenerateClassEnd(indent);
classSymbol = classSymbol.ContainingSymbol as INamedTypeSymbol;
} while (classSymbol != null);
}
}
}

View file

@ -1,213 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.DeserializeMethod.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateDeserializeMethod(
this StringBuilder source,
Compilation compilation,
INamedTypeSymbol classSymbol,
string indent,
bool isOverride,
int version,
bool encodedVersion,
ImmutableArray<SerializableMetadata> migrations,
ImmutableArray<SerializableProperty> fields,
ImmutableArray<SerializableProperty> properties,
ISymbol parentFieldOrProperty,
SortedDictionary<int, SerializableFieldSaveFlagMethods> serializableFieldSaveFlagMethodsDictionary
)
{
var genericReaderInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_READER_INTERFACE);
source.GenerateMethodStart(
indent,
"Deserialize",
Accessibility.Public,
isOverride,
"void",
ImmutableArray.Create<(ITypeSymbol, string)>((genericReaderInterface, "reader"))
);
var bodyIndent = $"{indent} ";
var innerIndent = $"{bodyIndent} ";
if (isOverride)
{
source.AppendLine($"{bodyIndent}base.Deserialize(reader);");
source.AppendLine();
}
var afterDeserialization = classSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.Select(
m =>
{
if (!m.ReturnsVoid || m.Parameters.Length != 0)
{
return (m, null);
}
return (m, m.GetAttributes()
.FirstOrDefault(
attr => SymbolEqualityComparer.Default.Equals(
attr.AttributeClass,
compilation.GetTypeByMetadataName(SymbolMetadata.AFTERDESERIALIZATION_ATTRIBUTE)
)
));
}
).Where(m => m.Item2 != null).ToList();
// Version
source.AppendLine($"{bodyIndent}var version = reader.{(encodedVersion ? "ReadEncodedInt" : "ReadInt")}();");
if (version > 0)
{
var parent = parentFieldOrProperty?.Name ?? "this";
var nextVersion = 0;
for (var i = 0; i < migrations.Length; i++)
{
var migrationVersion = migrations[i].Version;
if (migrationVersion == nextVersion)
{
nextVersion++;
}
source.AppendLine();
source.AppendLine($"{bodyIndent}if (version == {migrationVersion})");
source.AppendLine($"{bodyIndent}{{");
source.AppendLine($"{bodyIndent} MigrateFrom(new V{migrationVersion}Content(reader, this));");
source.AppendLine($"{bodyIndent} {parent}.MarkDirty();");
source.GenerateAfterDeserialization($"{bodyIndent} ", afterDeserialization);
source.AppendLine($"{bodyIndent} return;");
source.AppendLine($"{bodyIndent}}}");
}
if (nextVersion < version)
{
source.AppendLine();
source.AppendLine($"{bodyIndent}if (version < _version)");
source.AppendLine($"{bodyIndent}{{");
source.AppendLine($"{bodyIndent} Deserialize(reader, version);");
source.AppendLine($"{bodyIndent} {parent}.MarkDirty();");
source.GenerateAfterDeserialization($"{bodyIndent} ", afterDeserialization);
source.AppendLine($"{bodyIndent} return;");
source.AppendLine($"{bodyIndent}}}");
}
}
if (serializableFieldSaveFlagMethodsDictionary.Count > 0)
{
source.AppendLine();
source.AppendLine($"{bodyIndent}var saveFlags = reader.ReadEnum<SaveFlag>();");
}
for (var i = 0; i < properties.Length; i++)
{
var field = fields[i];
var property = properties[i];
var rule = SerializableMigrationRulesEngine.Rules[property.Rule];
if (serializableFieldSaveFlagMethodsDictionary.TryGetValue(
property.Order,
out var serializableFieldSaveFlagMethods
))
{
source.AppendLine();
// Special case
if (property.Type == "bool")
{
source.AppendLine($"{bodyIndent}{field.Name} = (saveFlags & SaveFlag.{property.Name}) != 0;");
}
else
{
source.AppendLine($"{bodyIndent}if ((saveFlags & SaveFlag.{property.Name}) != 0)\n{bodyIndent}{{");
rule.GenerateDeserializationMethod(
source,
innerIndent,
field,
parentFieldOrProperty?.Name ?? "this"
);
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(
source,
innerIndent,
field,
compilation,
classSymbol
);
if (serializableFieldSaveFlagMethods.GetFieldDefaultValue != null)
{
source.AppendLine($"{bodyIndent}}}\n{bodyIndent}else\n{bodyIndent}{{");
source.AppendLine(
$"{bodyIndent} {field.Name} = {serializableFieldSaveFlagMethods.GetFieldDefaultValue.Name}();"
);
}
source.AppendLine($"{bodyIndent}}}");
}
}
else
{
source.AppendLine();
rule.GenerateDeserializationMethod(
source,
bodyIndent,
field,
parentFieldOrProperty?.Name ?? "this"
);
(rule as IPostDeserializeMethod)?.PostDeserializeMethod(
source,
bodyIndent,
field,
compilation,
classSymbol
);
}
}
source.GenerateAfterDeserialization($"{bodyIndent}", afterDeserialization);
source.GenerateMethodEnd(indent);
}
private static void GenerateAfterDeserialization(
this StringBuilder source, string indent, IList<(IMethodSymbol, AttributeData?)> afterDeserialization
)
{
foreach (var (method, attr) in afterDeserialization)
{
if ((bool)attr.ConstructorArguments[0].Value!)
{
source.AppendLine($"{indent}{method.Name}();");
}
else
{
source.AppendLine($"{indent}Timer.DelayCall({method.Name});");
}
}
}
}
}

View file

@ -1,82 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.Property.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateSerializableProperty(
this StringBuilder source,
Compilation compilation,
string indent,
IFieldSymbol fieldSymbol,
Accessibility getter,
Accessibility? setter,
bool isVirtual,
ISymbol? parentFieldOrProperty
)
{
var fieldName = fieldSymbol.Name;
var invalidatePropertiesAttribute = fieldSymbol
.GetAttributes()
.OfType<AttributeData>()
.FirstOrDefault(
attr => attr.AttributeClass?.Equals(
compilation.GetTypeByMetadataName(SymbolMetadata.INVALIDATEPROPERTIES_ATTRIBUTE),
SymbolEqualityComparer.Default
) ?? false
);
var propertyIndent = $"{indent} ";
var innerIndent = $"{propertyIndent} ";
var propertyAccessor = setter > getter ? setter : getter;
var getterAccessor = getter == propertyAccessor ? Accessibility.NotApplicable : getter;
source.GeneratePropertyStart(indent, propertyAccessor.Value, isVirtual, fieldSymbol);
// Getter
source.GeneratePropertyGetterReturnsField(propertyIndent, fieldSymbol, getterAccessor);
if (setter != null && setter != Accessibility.NotApplicable)
{
var setterAccessor = setter == propertyAccessor ? Accessibility.NotApplicable : setter;
var parentSymbol = parentFieldOrProperty?.Name ?? "this";
// Setter
source.GeneratePropertySetterStart(propertyIndent, false, setterAccessor.Value);
source.AppendLine($"{innerIndent}if (value != {fieldName})");
source.AppendLine($"{innerIndent}{{");
source.AppendLine($"{innerIndent} {fieldName} = value;");
source.AppendLine($"{innerIndent} {parentSymbol}.MarkDirty();");
if (invalidatePropertiesAttribute != null)
{
source.AppendLine($"{innerIndent} InvalidateProperties();");
}
source.AppendLine($"{innerIndent}}}");
source.GeneratePropertyGetSetEnd(propertyIndent, false);
}
source.GeneratePropertyEnd(indent);
}
}
}

View file

@ -1,52 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.SerialCtor.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
private static readonly ImmutableArray<string> _baseParameters = new[] { "serial" }.ToImmutableArray();
public static void GenerateSerialCtor(
this StringBuilder source,
Compilation compilation,
string className,
string indent,
bool isOverride
)
{
var serialType = (ITypeSymbol)compilation.GetTypeByMetadataName("Server.Serial");
source.GenerateConstructorStart(
indent,
className,
Accessibility.Public,
new []{ (serialType, "serial") }.ToImmutableArray(),
isOverride ? _baseParameters : ImmutableArray<string>.Empty
);
if (!isOverride)
{
source.AppendLine($"{indent} Serial = serial;");
source.AppendLine($"{indent} SetTypeRef(typeof({className}));");
}
source.GenerateMethodEnd(indent);
}
}
}

View file

@ -1,112 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableEntityGeneration.SerializeMethod.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateSerializeMethod(
this StringBuilder source,
Compilation compilation,
string indent,
bool isOverride,
bool encodedVersion,
ImmutableArray<SerializableProperty> fields,
ImmutableArray<SerializableProperty> properties,
SortedDictionary<int, SerializableFieldSaveFlagMethods> serializableFieldSaveFlagMethodsDictionary
)
{
var genericWriterInterface = compilation.GetTypeByMetadataName(SymbolMetadata.GENERIC_WRITER_INTERFACE);
source.GenerateMethodStart(
indent,
"Serialize",
Accessibility.Public,
isOverride,
"void",
ImmutableArray.Create<(ITypeSymbol, string)>((genericWriterInterface, "writer"))
);
var bodyIndent = $"{indent} ";
var innerIndent = $"{bodyIndent} ";
if (isOverride)
{
source.AppendLine($"{bodyIndent}base.Serialize(writer);");
source.AppendLine();
}
// Version
source.AppendLine($"{bodyIndent}writer.{(encodedVersion ? "WriteEncodedInt" : "Write")}(_version);");
// Let's collect the flags
if (serializableFieldSaveFlagMethodsDictionary.Count > 0)
{
source.AppendLine($"\n{bodyIndent}var saveFlags = SaveFlag.None;");
foreach (var (order, saveFlagMethods) in serializableFieldSaveFlagMethodsDictionary)
{
source.AppendLine($"{bodyIndent}if ({saveFlagMethods.DetermineFieldShouldSerialize!.Name}())\n{bodyIndent}{{");
var propertyName = properties[order].Name;
source.AppendLine($"{innerIndent}saveFlags |= SaveFlag.{propertyName};");
source.AppendLine($"{bodyIndent}}}");
}
source.AppendLine($"{bodyIndent}writer.WriteEnum(saveFlags);");
}
for (var i = 0; i < properties.Length; i++)
{
var field = fields[i];
var property = properties[i];
if (serializableFieldSaveFlagMethodsDictionary.ContainsKey(property.Order))
{
// Special case
if (property.Type != "bool")
{
source.AppendLine($"\n{bodyIndent}if ((saveFlags & SaveFlag.{property.Name}) != 0)\n{bodyIndent}{{");
SerializableMigrationRulesEngine.Rules[property.Rule]
.GenerateSerializationMethod(
source,
innerIndent,
field
);
source.AppendLine($"{bodyIndent}}}");
}
}
else
{
source.AppendLine();
SerializableMigrationRulesEngine.Rules[property.Rule]
.GenerateSerializationMethod(
source,
bodyIndent,
field
);
}
}
source.GenerateMethodEnd(indent);
}
}
}

View file

@ -1,11 +0,0 @@
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public record SerializableFieldSaveFlagMethods
{
public IMethodSymbol? DetermineFieldShouldSerialize { get; init; }
public IMethodSymbol? GetFieldDefaultValue { get; init; }
}
}

View file

@ -1,127 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializationEntityGeneration.ContentStruct.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializableMigration;
namespace SerializationGenerator
{
public static partial class SerializableEntityGeneration
{
public static void GenerateMigrationContentStruct(
this StringBuilder source,
Compilation compilation,
string indent,
SerializableMetadata migration,
INamedTypeSymbol classSymbol
)
{
source.AppendLine($"{indent}ref struct V{migration.Version}Content");
source.AppendLine($"{indent}{{");
var properties = migration.Properties ?? ImmutableArray<SerializableProperty>.Empty;
foreach (var serializableProperty in properties)
{
SerializableMigrationRulesEngine.Rules[serializableProperty.Rule].GenerateMigrationProperty(
source, compilation, $"{indent} ", serializableProperty
);
}
var innerIndent = $"{indent} ";
var usesSaveFlags = properties.Any(p => p.UsesSaveFlag == true);
if (usesSaveFlags)
{
source.AppendLine();
source.GenerateEnumStart(
$"V{migration.Version}SaveFlag",
$"{indent} ",
true,
Accessibility.Private
);
source.GenerateEnumValue(innerIndent, true, "None", -1);
int index = 0;
foreach (var property in properties)
{
if (property.UsesSaveFlag == true)
{
source.GenerateEnumValue(innerIndent, true, property.Name, index++);
}
}
source.GenerateEnumEnd($"{indent} ");
}
source.AppendLine($"{indent} internal V{migration.Version}Content(IGenericReader reader, {classSymbol.ToDisplayString()} entity)");
source.AppendLine($"{indent} {{");
if (usesSaveFlags)
{
source.AppendLine($"{innerIndent}var saveFlags = reader.ReadEnum<V{migration.Version}SaveFlag>();");
}
if (properties.Length > 0)
{
foreach (var property in properties)
{
if (property.UsesSaveFlag == true)
{
source.AppendLine();
// Special case
if (property.Type == "bool")
{
source.AppendLine($"{innerIndent}{property.Name} = (saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0;");
}
else
{
source.AppendLine($"{innerIndent}if ((saveFlags & V{migration.Version}SaveFlag.{property.Name}) != 0)\n{innerIndent}{{");
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod(
source,
$"{innerIndent} ",
property,
"entity",
true
);
source.AppendLine($"{innerIndent}}}\n{innerIndent}else\n{innerIndent}{{");
source.AppendLine($"{innerIndent} {property.Name} = default;");
source.AppendLine($"{innerIndent}}}");
}
}
else
{
SerializableMigrationRulesEngine.Rules[property.Rule].GenerateDeserializationMethod(
source,
innerIndent,
property,
"entity",
true
);
}
}
}
source.AppendLine($"{indent} }}");
source.AppendLine($"{indent}}}");
}
}
}

View file

@ -1,31 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: IPostDeserializeMethod.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public interface IPostDeserializeMethod
{
public void PostDeserializeMethod(
StringBuilder source,
string indent,
SerializableProperty property,
Compilation compilation,
INamedTypeSymbol classSymbol
);
}
}

View file

@ -1,57 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public interface ISerializableMigrationRule
{
string RuleName { get; }
void GenerateMigrationProperty(
StringBuilder source,
Compilation compilation,
string indent,
SerializableProperty serializableProperty
);
bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
);
void GenerateDeserializationMethod(
StringBuilder source,
string indent,
SerializableProperty property,
string? parentReference,
bool isMigration = false
);
void GenerateSerializationMethod(
StringBuilder source,
string indent,
SerializableProperty property
);
}
}

View file

@ -1,135 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ArrayMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializableMigration;
public class ArrayMigrationRule : MigrationRule
{
public override string RuleName => nameof(ArrayMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not IArrayTypeSymbol arrayTypeSymbol)
{
ruleArguments = null;
return false;
}
var serializableArrayType = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"ArrayEntry",
arrayTypeSymbol.ElementType,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var length = serializableArrayType.RuleArguments?.Length?? 0;
ruleArguments = new string[length + 2];
ruleArguments[0] = arrayTypeSymbol.ElementType.ToDisplayString();
ruleArguments[1] = serializableArrayType.Rule;
if (length > 0)
{
Array.Copy(serializableArrayType.RuleArguments!, 0, ruleArguments, 2, length);
}
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var arrayElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments![1]];
var arrayElementRuleArguments = new string[ruleArguments.Length - 2];
Array.Copy(ruleArguments, 2, arrayElementRuleArguments, 0, ruleArguments.Length - 2);
var propertyIndex = $"{property.Name}Index";
source.AppendLine($"{indent}{property.Name} = new {ruleArguments[0]}[reader.ReadEncodedInt()];");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {property.Name}.Length; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableArrayElement = new SerializableProperty
{
Name = $"{property.Name}[{propertyIndex}]",
Type = ruleArguments[0],
Rule = arrayElementRule.RuleName,
RuleArguments = arrayElementRuleArguments
};
arrayElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableArrayElement, parentReference);
source.AppendLine($"{indent}}}");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var arrayElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments![1]];
var arrayElementRuleArguments = new string[ruleArguments.Length - 2];
Array.Copy(ruleArguments, 2, arrayElementRuleArguments, 0, ruleArguments.Length - 2);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyIndex = $"{propertyVarPrefix}Index";
var propertyLength = $"{propertyVarPrefix}Length";
source.AppendLine($"{indent}var {propertyLength} = {property.Name}?.Length ?? 0;");
source.AppendLine($"{indent}writer.WriteEncodedInt({propertyLength});");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyLength}; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableArrayElement = new SerializableProperty
{
Name = $"{property.Name}![{propertyIndex}]",
Type = ruleArguments[0],
Rule = arrayElementRule.RuleName,
RuleArguments = arrayElementRuleArguments
};
arrayElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableArrayElement);
source.AppendLine($"{indent}}}");
}
}

View file

@ -1,250 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: DictionaryMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class DictionaryMigrationRule : MigrationRule
{
public override string RuleName => nameof(DictionaryMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol || !symbol.IsDictionary(compilation))
{
ruleArguments = null;
return false;
}
var keySymbolType = namedTypeSymbol.TypeArguments[0];
var serializableKeyProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"KeyEntry",
keySymbolType,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var valueSymbolType = namedTypeSymbol.TypeArguments[1];
var serializableValueProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"ValueEntry",
valueSymbolType,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var extraOptions = "";
if (attributes.Any(a => a.IsTidy(compilation)))
{
extraOptions += "@Tidy";
}
var keyArgumentsLength = serializableKeyProperty.RuleArguments?.Length ?? 0;
var valueArgumentsLength = serializableValueProperty.RuleArguments?.Length ?? 0;
var index = 0;
ruleArguments = new string[7 + keyArgumentsLength + valueArgumentsLength];
ruleArguments[index++] = extraOptions;
ruleArguments[index++] = keySymbolType.ToDisplayString();
ruleArguments[index++] = serializableKeyProperty.Rule;
ruleArguments[index++] = keyArgumentsLength.ToString();
if (keyArgumentsLength > 0)
{
Array.Copy(serializableKeyProperty.RuleArguments!, 0, ruleArguments, index, keyArgumentsLength);
index += keyArgumentsLength;
}
ruleArguments[index++] = valueSymbolType.ToDisplayString();
ruleArguments[index++] = serializableValueProperty.Rule;
ruleArguments[index++] = valueArgumentsLength.ToString();
if (valueArgumentsLength > 0)
{
Array.Copy(serializableValueProperty.RuleArguments!, 0, ruleArguments, index, valueArgumentsLength);
}
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var index = 1;
var keyType = ruleArguments![index++];
var keyElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[index++]];
var keyRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (keyRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, keyRuleArguments, 0, keyRuleArguments.Length);
index += keyRuleArguments.Length;
}
var valueType = ruleArguments[index++];
var valueElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[index++]];
var valueRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (valueRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, valueRuleArguments, 0, valueRuleArguments.Length);
}
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyIndex = $"{propertyVarPrefix}Index";
var propertyKeyEntry = $"{propertyVarPrefix}Key";
var propertyValueEntry = $"{propertyVarPrefix}Value";
var propertyCount = $"{propertyVarPrefix}Count";
source.AppendLine($"{indent}{ruleArguments[1]} {propertyKeyEntry};");
source.AppendLine($"{indent}{valueType} {propertyValueEntry};");
source.AppendLine($"{indent}var {propertyCount} = reader.ReadEncodedInt();");
source.AppendLine($"{indent}{propertyName} = new System.Collections.Generic.Dictionary<{keyType}, {valueType}>({propertyCount});");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyCount}; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableKeyElement = new SerializableProperty
{
Name = propertyKeyEntry,
Type = keyType,
Rule = keyElementRule.RuleName,
RuleArguments = keyRuleArguments
};
keyElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableKeyElement, parentReference);
var serializableValueElement = new SerializableProperty
{
Name = propertyValueEntry,
Type = valueType,
Rule = valueElementRule.RuleName,
RuleArguments = valueRuleArguments
};
valueElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableValueElement, parentReference);
source.AppendLine($"{indent} {propertyName}.Add({propertyKeyEntry}, {propertyValueEntry});");
source.AppendLine($"{indent}}}");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var index = 0;
var shouldTidy = ruleArguments![index++].Contains("@Tidy");
var keyType = ruleArguments![index++];
var keyElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments![index++]];
var keyRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (keyRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, keyRuleArguments, 0, keyRuleArguments.Length);
index += keyRuleArguments.Length;
}
var valueType = ruleArguments[index++];
var valueElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[index++]];
var valueRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (valueRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, valueRuleArguments, 0, valueRuleArguments.Length);
}
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyKeyEntry = $"{propertyVarPrefix}Key";
var propertyValueEntry = $"{propertyVarPrefix}Value";
var propertyCount = $"{propertyVarPrefix}Count";
if (shouldTidy)
{
source.AppendLine($"{indent}{property.Name}?.Tidy();");
}
source.AppendLine($"{indent}var {propertyCount} = {property.Name}?.Count ?? 0;");
source.AppendLine($"{indent}writer.WriteEncodedInt({propertyCount});");
source.AppendLine($"{indent}if ({propertyCount} > 0)");
source.AppendLine($"{indent}{{");
source.AppendLine($"{indent} foreach (var ({propertyKeyEntry}, {propertyValueEntry}) in {property.Name}!)");
source.AppendLine($"{indent} {{");
var serializableKeyElement = new SerializableProperty
{
Name = propertyKeyEntry,
Type = keyType,
Rule = keyElementRule.RuleName,
RuleArguments = keyRuleArguments
};
keyElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableKeyElement);
var serializableValueElement = new SerializableProperty
{
Name = propertyValueEntry,
Type = valueType,
Rule = valueElementRule.RuleName,
RuleArguments = valueRuleArguments
};
valueElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableValueElement);
source.AppendLine($"{indent} }}");
source.AppendLine($"{indent}}}");
}
}

View file

@ -1,73 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: EnumMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class EnumMigrationRule : MigrationRule
{
public override string RuleName => nameof(EnumMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not ITypeSymbol typeSymbol || !typeSymbol.IsEnum())
{
ruleArguments = null;
return false;
}
ruleArguments = Array.Empty<string>();
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
source.AppendLine($"{indent}{property.Name} = reader.ReadEnum<{property.Type}>();");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
source.AppendLine($"{indent}writer.WriteEnum<{property.Type}>({property.Name});");
}
}

View file

@ -1,171 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: HashSetMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class HashSetMigrationRule : MigrationRule
{
public override string RuleName => nameof(HashSetMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol || !symbol.IsHashSet(compilation))
{
ruleArguments = null;
return false;
}
var setTypeSymbol = namedTypeSymbol.TypeArguments[0];
var serializableSetType = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"SetEntry",
setTypeSymbol,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var extraOptions = "";
if (attributes.Any(a => a.IsTidy(compilation)))
{
extraOptions += "@Tidy";
}
var length = serializableSetType.RuleArguments?.Length ?? 0;
ruleArguments = new string[length + 3];
ruleArguments[0] = extraOptions;
ruleArguments[1] = setTypeSymbol.ToDisplayString();
ruleArguments[2] = serializableSetType.Rule;
if (length > 0)
{
Array.Copy(serializableSetType.RuleArguments, 0, ruleArguments, 3, length);
}
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var argumentsOffset = hasExtraOptions ? 1 : 0;
var setElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1 + argumentsOffset]];
var setElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, setElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyIndex = $"{propertyVarPrefix}Index";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
source.AppendLine($"{indent}{ruleArguments[argumentsOffset]} {propertyEntry};");
source.AppendLine($"{indent}var {propertyCount} = reader.ReadEncodedInt();");
source.AppendLine($"{indent}{property.Name} = new System.Collections.Generic.HashSet<{ruleArguments[argumentsOffset]}>({propertyCount});");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyCount}; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableSetElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = setElementRule.RuleName,
RuleArguments = setElementRuleArguments
};
setElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableSetElement, parentReference);
source.AppendLine($"{indent} {property.Name}.Add({propertyEntry});");
source.AppendLine($"{indent}}}");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var shouldTidy = hasExtraOptions && ruleArguments[0].Contains("@Tidy");
var argumentsOffset = hasExtraOptions ? 1 : 0;
var setElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1 + argumentsOffset]];
var setElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, setElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
if (shouldTidy)
{
source.AppendLine($"{indent}{property.Name}?.Tidy();");
}
source.AppendLine($"{indent}var {propertyCount} = {property.Name}?.Count ?? 0;");
source.AppendLine($"{indent}writer.WriteEncodedInt({propertyCount});");
source.AppendLine($"{indent}if ({propertyCount} > 0)");
source.AppendLine($"{indent}{{");
source.AppendLine($"{indent} foreach (var {propertyEntry} in {property.Name}!)");
source.AppendLine($"{indent} {{");
var serializableSetElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = setElementRule.RuleName,
RuleArguments = setElementRuleArguments
};
setElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableSetElement);
source.AppendLine($"{indent} }}");
source.AppendLine($"{indent}}}");
}
}

View file

@ -1,225 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: KeyValuePairMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class KeyValuePairMigrationRule : MigrationRule
{
public override string RuleName => nameof(KeyValuePairMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol || !symbol.IsKeyValuePair(compilation))
{
ruleArguments = null;
return false;
}
var keySymbolType = namedTypeSymbol.TypeArguments[0];
var keySerializedProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"key",
keySymbolType,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var valueSymbolType = namedTypeSymbol.TypeArguments[1];
var valueSerializedProperty = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"value",
valueSymbolType,
1,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var keyArgumentsLength = keySerializedProperty.RuleArguments?.Length ?? 0;
var valueArgumentsLength = valueSerializedProperty.RuleArguments?.Length ?? 0;
var index = 0;
// Key
ruleArguments = new string[6 + keyArgumentsLength + valueArgumentsLength];
ruleArguments[index++] = ""; // Extra options
ruleArguments[index++] = keySymbolType.ToDisplayString();
ruleArguments[index++] = keySerializedProperty.Rule;
ruleArguments[index++] = keyArgumentsLength.ToString();
if (keyArgumentsLength > 0)
{
Array.Copy(keySerializedProperty.RuleArguments!, 0, ruleArguments, index, keyArgumentsLength);
index += keyArgumentsLength;
}
// Value
ruleArguments[index++] = valueSymbolType.ToDisplayString();
ruleArguments[index++] = valueSerializedProperty.Rule;
if (valueArgumentsLength > 0)
{
Array.Copy(valueSerializedProperty.RuleArguments!, 0, ruleArguments, index, valueArgumentsLength);
}
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var index = 1; // skip extra options
var keyType = ruleArguments![index++];
var keyRule = SerializableMigrationRulesEngine.Rules[ruleArguments[index++]];
var keyRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (keyRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, keyRuleArguments, 0, keyRuleArguments.Length);
index += keyRuleArguments.Length;
}
var serializableKeyProperty = new SerializableProperty
{
Name = "key",
Type = keyType,
Rule = keyRule.RuleName,
RuleArguments = keyRuleArguments
};
keyRule.GenerateDeserializationMethod(
source,
indent,
serializableKeyProperty,
parentReference
);
var valueType = ruleArguments[index++];
var valueRule = SerializableMigrationRulesEngine.Rules[ruleArguments[index++]];
var valueRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (valueRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, valueRuleArguments, 0, valueRuleArguments.Length);
}
var serializableValueProperty = new SerializableProperty
{
Name = "value",
Type = valueType,
Rule = valueRule.RuleName,
RuleArguments = valueRuleArguments
};
valueRule.GenerateDeserializationMethod(
source,
indent,
serializableValueProperty,
parentReference
);
source.AppendLine(
$"{indent}{property.Name} = new {SymbolMetadata.KEYVALUEPAIR_STRUCT}<{keyType}, {valueType}>(key, value);"
);
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var index = 1; // skip extra options
var keyType = ruleArguments![index++];
var keyRule = SerializableMigrationRulesEngine.Rules[ruleArguments[index++]];
var keyRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (keyRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, keyRuleArguments, 0, keyRuleArguments.Length);
index += keyRuleArguments.Length;
}
var serializableKeyProperty = new SerializableProperty
{
Name = $"{property.Name}.Key",
Type = keyType,
Rule = keyRule.RuleName,
RuleArguments = keyRuleArguments
};
keyRule.GenerateSerializationMethod(
source,
indent,
serializableKeyProperty
);
var valueType = ruleArguments[index++];
var valueRule = SerializableMigrationRulesEngine.Rules[ruleArguments[index++]];
var valueRuleArguments = new string[int.Parse(ruleArguments[index++])];
if (valueRuleArguments.Length > 0)
{
Array.Copy(ruleArguments, index, valueRuleArguments, 0, valueRuleArguments.Length);
}
var serializableValueProperty = new SerializableProperty
{
Name = $"{property.Name}.Value",
Type = valueType,
Rule = valueRule.RuleName,
RuleArguments = valueRuleArguments
};
valueRule.GenerateSerializationMethod(
source,
indent,
serializableValueProperty
);
}
}

View file

@ -1,172 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ListMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class ListMigrationRule : MigrationRule
{
public override string RuleName => nameof(ListMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not INamedTypeSymbol namedTypeSymbol || !symbol.IsList(compilation))
{
ruleArguments = null;
return false;
}
var listTypeSymbol = namedTypeSymbol.TypeArguments[0];
var serializableListType = SerializableMigrationRulesEngine.GenerateSerializableProperty(
compilation,
"ListEntry",
listTypeSymbol,
0,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
null
);
var extraOptions = "";
if (attributes.Any(a => a.IsTidy(compilation)))
{
extraOptions += "@Tidy";
}
var length = serializableListType.RuleArguments?.Length ?? 0;
ruleArguments = new string[length + 3];
ruleArguments[0] = extraOptions;
ruleArguments[1] = listTypeSymbol.ToDisplayString();
ruleArguments[2] = serializableListType.Rule;
if (length > 0)
{
Array.Copy(serializableListType.RuleArguments!, 0, ruleArguments, 3, length);
}
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var argumentsOffset = hasExtraOptions ? 1 : 0;
var listElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[argumentsOffset + 1]];
var listElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, listElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyIndex = $"{propertyVarPrefix}Index";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
source.AppendLine($"{indent}{ruleArguments[argumentsOffset]} {propertyEntry};");
source.AppendLine($"{indent}var {propertyCount} = reader.ReadEncodedInt();");
source.AppendLine($"{indent}{propertyName} = new System.Collections.Generic.List<{ruleArguments[argumentsOffset]}>({propertyCount});");
source.AppendLine($"{indent}for (var {propertyIndex} = 0; {propertyIndex} < {propertyCount}; {propertyIndex}++)");
source.AppendLine($"{indent}{{");
var serializableListElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = listElementRule.RuleName,
RuleArguments = listElementRuleArguments
};
listElementRule.GenerateDeserializationMethod(source, $"{indent} ", serializableListElement, parentReference);
source.AppendLine($"{indent} {propertyName}.Add({propertyEntry});");
source.AppendLine($"{indent}}}");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var ruleArguments = property.RuleArguments;
var hasExtraOptions = ruleArguments![0] == "" || ruleArguments[0].StartsWith("@", StringComparison.Ordinal);
var shouldTidy = hasExtraOptions && ruleArguments[0].Contains("@Tidy");
var argumentsOffset = hasExtraOptions ? 1 : 0;
var listElementRule = SerializableMigrationRulesEngine.Rules[ruleArguments[1 + argumentsOffset]];
var listElementRuleArguments = new string[ruleArguments.Length - 2 - argumentsOffset];
Array.Copy(ruleArguments, 2 + argumentsOffset, listElementRuleArguments, 0, ruleArguments.Length - 2 - argumentsOffset);
var propertyName = property.Name;
var propertyVarPrefix = $"{char.ToLower(propertyName[0])}{propertyName.Substring(1, propertyName.Length - 1)}";
var propertyEntry = $"{propertyVarPrefix}Entry";
var propertyCount = $"{propertyVarPrefix}Count";
if (shouldTidy)
{
source.AppendLine($"{indent}{property.Name}?.Tidy();");
}
source.AppendLine($"{indent}var {propertyCount} = {property.Name}?.Count ?? 0;");
source.AppendLine($"{indent}writer.WriteEncodedInt({propertyCount});");
source.AppendLine($"{indent}if ({propertyCount} > 0)");
source.AppendLine($"{indent}{{");
source.AppendLine($"{indent} foreach (var {propertyEntry} in {property.Name}!)");
source.AppendLine($"{indent} {{");
var serializableListElement = new SerializableProperty
{
Name = propertyEntry,
Type = ruleArguments[argumentsOffset],
Rule = listElementRule.RuleName,
RuleArguments = listElementRuleArguments
};
listElementRule.GenerateSerializationMethod(source, $"{indent} ", serializableListElement);
source.AppendLine($"{indent} }}");
source.AppendLine($"{indent}}}");
}
}

View file

@ -1,36 +0,0 @@
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public abstract class MigrationRule : ISerializableMigrationRule
{
public abstract string RuleName { get; }
public virtual void GenerateMigrationProperty(
StringBuilder source, Compilation compilation, string indent, SerializableProperty serializableProperty
)
{
var propertyType = serializableProperty.Type;
var type = compilation.GetTypeByMetadataName(propertyType)?.IsValueType == true
|| SymbolMetadata.IsPrimitiveFromTypeDisplayString(propertyType) && propertyType != "bool"
? $"{propertyType}{(serializableProperty.UsesSaveFlag == true ? "?" : "")}" : propertyType;
source.AppendLine($"{indent}internal readonly {type} {serializableProperty.Name};");
}
public abstract bool GenerateRuleState(
Compilation compilation, ISymbol symbol, ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes, ISymbol? parentSymbol, out string[] ruleArguments
);
public abstract void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
);
public abstract void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property);
}

View file

@ -1,149 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PrimitiveTypeMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class PrimitiveTypeMigrationRule : MigrationRule
{
public override string RuleName => nameof(PrimitiveTypeMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol.IsIpAddress(compilation) || symbol.IsTimeSpan(compilation))
{
ruleArguments = Array.Empty<string>();
return true;
}
if (
symbol is not ITypeSymbol {
SpecialType: not (not
SpecialType.System_Boolean and not
SpecialType.System_SByte and not
SpecialType.System_Int16 and not
SpecialType.System_Int32 and not
SpecialType.System_Int64 and not
SpecialType.System_Byte and not
SpecialType.System_UInt16 and not
SpecialType.System_UInt32 and not
SpecialType.System_UInt64 and not
SpecialType.System_Single and not
SpecialType.System_Double and not
SpecialType.System_String and not
SpecialType.System_Decimal and not
SpecialType.System_DateTime)
} typeSymbol
)
{
ruleArguments = null;
return false;
}
ruleArguments = typeSymbol.SpecialType switch
{
SpecialType.System_Int32 when attributes.Any(a => a.IsEncodedInt(compilation)) =>
new[] { "EncodedInt" },
SpecialType.System_DateTime when attributes.Any(a => a.IsDeltaDateTime(compilation)) =>
new[] { "DeltaTime" },
SpecialType.System_String when attributes.Any(a => a.IsInternString(compilation)) =>
new[] { "InternString" },
_ => new[] { "" }
};
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var argument = property.RuleArguments?.Length >= 1 ? property.RuleArguments[0] : null;
const string ipAddress = SymbolMetadata.IPADDRESS_CLASS;
const string timeSpan = SymbolMetadata.TIMESPAN_STRUCT;
const string date = "System.DateTime";
var readMethod = property.Type switch
{
"bool" => "ReadBool",
"sbyte" => "ReadSByte",
"short" => "ReadShort",
"int" when argument == "EncodedInt" => "ReadEncodedInt",
"int" => "ReadInt",
"long" => "ReadLong",
"byte" => "ReadByte",
"ushort" => "ReadUShort",
"uint" => "ReadUInt",
"ulong" => "ReadULong",
"float" => "ReadFloat",
"double" => "ReadDouble",
"string" => "ReadString",
"decimal" => "ReadDecimal",
date when argument == "DeltaTime" => "ReadDeltaTime",
date => "ReadDateTime",
ipAddress => "ReadIPAddress",
timeSpan => "ReadTimeSpan"
};
var readArgument = readMethod == "ReadString" && argument == "InternString" ? "true" : "";
source.AppendLine($"{indent}{propertyName} = reader.{readMethod}({readArgument});");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var argument = property.RuleArguments?.Length >= 1 ? property.RuleArguments[0] : null;
var writeMethod = property.Type switch
{
"System.DateTime" when argument == "DeltaTime" => "WriteDeltaTime",
"int" when argument == "EncodedInt" => "WriteEncodedInt",
_ => "Write"
};
source.AppendLine($"{indent}writer.{writeMethod}({propertyName});");
}
}

View file

@ -1,80 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PrimitiveUOTypeMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class PrimitiveUOTypeMigrationRule : MigrationRule
{
public override string RuleName => nameof(PrimitiveUOTypeMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
ruleArguments = symbol switch
{
_ when symbol.IsPoint2D(compilation) => new[] { "Point2D" },
_ when symbol.IsPoint3D(compilation) => new[] { "Point3D" },
_ when symbol.IsRectangle2D(compilation) => new[] { "Rect2D" },
_ when symbol.IsRectangle3D(compilation) => new[] { "Rect3D" },
_ when symbol.IsRace(compilation) => new[] { "Race" },
_ when symbol.IsMap(compilation) => new[] { "Map" },
_ when symbol.IsBitArray(compilation) => new[] { "BitArray" },
_ => null
};
return ruleArguments != null;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName} = reader.Read{property.RuleArguments?[0] ?? ""}();");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}writer.Write({propertyName});");
}
}

View file

@ -1,82 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: RawSerializableMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class RawSerializableMigrationRule : MigrationRule
{
public override string RuleName => nameof(RawSerializableMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is not ITypeSymbol typeSymbol)
{
ruleArguments = null;
return false;
}
if (!typeSymbol.HasRawSerializableInterface(compilation, embeddedSerializableTypes))
{
ruleArguments = null;
return false;
}
ruleArguments = new[] { "" };
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName} = new {property.Type}({parentReference ?? "this"});");
source.AppendLine($"{indent}{propertyName}.Deserialize(reader);");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName}.Serialize(writer);");
}
}

View file

@ -1,75 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableInterfaceMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class SerializableInterfaceMigrationRule : MigrationRule
{
public override string RuleName => nameof(SerializableInterfaceMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (symbol is ITypeSymbol typeSymbol && typeSymbol.HasSerializableInterface(compilation, serializableTypes))
{
ruleArguments = Array.Empty<string>();
return true;
}
ruleArguments = null;
return false;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName} = reader.ReadEntity<{property.Type}>();");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}writer.Write({propertyName});");
}
}

View file

@ -1,85 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializationMethodSignatureMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class SerializationMethodSignatureMigrationRule : MigrationRule
{
public override string RuleName => nameof(SerializationMethodSignatureMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if ((symbol as ITypeSymbol)?.HasPublicSerializeMethod(compilation, serializableTypes) != true)
{
ruleArguments = null;
return false;
}
if (symbol is not INamedTypeSymbol namedTypeSymbol ||
!namedTypeSymbol.HasGenericReaderCtor(compilation, parentSymbol, out var requiresParent))
{
ruleArguments = null;
return false;
}
ruleArguments = new[] { requiresParent ? "DeserializationRequiresParent" : "" };
return true;
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var argument = property.RuleArguments?.Length >= 1 &&
property.RuleArguments[0] == "DeserializationRequiresParent" ? ", this" : "";
source.AppendLine($"{indent}{propertyName} = new {property.Type}(reader{argument});");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
source.AppendLine($"{indent}{propertyName}.Serialize(writer);");
}
}

View file

@ -1,136 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: TimerMigrationRule.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration;
public class TimerMigrationRule : MigrationRule, IPostDeserializeMethod
{
public override string RuleName => nameof(TimerMigrationRule);
public override bool GenerateRuleState(
Compilation compilation,
ISymbol symbol,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
out string[] ruleArguments
)
{
if (!(symbol is ITypeSymbol typeSymbol && typeSymbol.IsTimer(compilation)))
{
ruleArguments = null;
return false;
}
ruleArguments = attributes.Any(a => a.IsTimerDrift(compilation))
? new[] { "@TimerDrift" }
: new[] { "" };
return true;
}
public override void GenerateMigrationProperty(
StringBuilder source, Compilation compilation, string indent, SerializableProperty serializableProperty
)
{
source.AppendLine($"{indent}internal readonly System.DateTime {serializableProperty.Name}Next;");
source.AppendLine($"{indent}internal readonly System.TimeSpan {serializableProperty.Name}Delay;");
}
public override void GenerateDeserializationMethod(
StringBuilder source, string indent, SerializableProperty property, string? parentReference, bool isMigration = false
)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var ruleArguments = property.RuleArguments;
var driftTimer = ruleArguments![0].Contains("@TimerDrift");
var readTimer = driftTimer ? "reader.ReadDeltaTime()" : "reader.ReadDateTime()";
var useVar = isMigration ? "" : "var ";
source.AppendLine($"{indent}{useVar}{propertyName}Next = {readTimer};");
source.AppendLine($"{indent}{useVar}{propertyName}Delay = {propertyName}Next == System.DateTime.MinValue ? System.TimeSpan.MinValue : {propertyName}Next - Core.Now;");
}
public override void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property)
{
var expectedRule = RuleName;
var ruleName = property.Rule;
if (expectedRule != ruleName)
{
throw new ArgumentException($"Invalid rule applied to property {ruleName}. Expecting {expectedRule}, but received {ruleName}.");
}
var propertyName = property.Name;
var ruleArguments = property.RuleArguments;
var driftTimer = ruleArguments![0].Contains("@TimerDrift");
var writerMethod = driftTimer ? "WriteDeltaTime" : "Write";
source.AppendLine($"{indent}writer.{writerMethod}({propertyName}?.Next ?? System.DateTime.MinValue);");
}
public void PostDeserializeMethod(
StringBuilder source, string indent, SerializableProperty property, Compilation compilation, INamedTypeSymbol classSymbol
)
{
var deserializeTimerMethod = classSymbol
.GetMembers()
.OfType<IMethodSymbol>()
.FirstOrDefault(
m =>
{
if (!m.ReturnsVoid || m.Parameters.Length != 1 || !m.Parameters[0].Type.IsTimeSpan(compilation))
{
return false;
}
return m.GetAttributes()
.FirstOrDefault(
attr =>
{
if (!SymbolEqualityComparer.Default.Equals(
attr.AttributeClass,
compilation.GetTypeByMetadataName(
SymbolMetadata.DESERIALIZE_TIMER_FIELD_ATTRIBUTE
)
))
{
return false;
}
var order = (int)attr.ConstructorArguments[0].Value!;
return order == property.Order;
}
) != null;
}
) ?? throw new Exception("Serializing a timer requires a method with the DeserializeTimerField attribute to handle creating the timer itself.");
source.AppendLine($"{indent}{deserializeTimerMethod.Name}({property.Name}Delay);");
}
}

View file

@ -1,27 +0,0 @@
using System.Collections.Generic;
namespace SerializableMigration
{
public class SerializableMetadataComparer : IComparer<SerializableMetadata>
{
public int Compare(SerializableMetadata x, SerializableMetadata y)
{
if (ReferenceEquals(x, y))
{
return 0;
}
if (ReferenceEquals(null, y))
{
return 1;
}
if (ReferenceEquals(null, x))
{
return -1;
}
return x.Version.CompareTo(y.Version);
}
}
}

View file

@ -1,133 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableMigrationRulesEngine.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using SerializationGenerator;
namespace SerializableMigration
{
public static class SerializableMigrationRulesEngine
{
public static readonly Dictionary<string, ISerializableMigrationRule> Rules = new();
static SerializableMigrationRulesEngine()
{
var rules = new ISerializableMigrationRule[]
{
new EnumMigrationRule(),
new ListMigrationRule(),
new ArrayMigrationRule(),
new HashSetMigrationRule(),
new DictionaryMigrationRule(),
new KeyValuePairMigrationRule(),
new PrimitiveTypeMigrationRule(),
new PrimitiveUOTypeMigrationRule(),
new SerializableInterfaceMigrationRule(),
new SerializationMethodSignatureMigrationRule(),
new RawSerializableMigrationRule(),
new TimerMigrationRule()
};
foreach (var rule in rules)
{
Rules.Add(rule.RuleName, rule);
}
}
public static SerializableProperty? GenerateSerializableProperty(
Compilation compilation,
ISymbol fieldOrPropertySymbol,
int order,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
SerializableFieldSaveFlagMethods? serializableFieldSaveFlagMethods
)
{
string propertyName;
ITypeSymbol propertyType;
if (fieldOrPropertySymbol is IFieldSymbol fieldSymbol)
{
propertyName = fieldSymbol.Name;
propertyType = fieldSymbol.Type;
}
else if (fieldOrPropertySymbol is IPropertySymbol propertySymbol)
{
propertyName = fieldOrPropertySymbol.Name;
propertyType = propertySymbol.Type;
}
else
{
return null;
}
return GenerateSerializableProperty(
compilation,
propertyName,
propertyType,
order,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
serializableFieldSaveFlagMethods
);
}
public static SerializableProperty GenerateSerializableProperty(
Compilation compilation,
string propertyName,
ISymbol propertyType,
int order,
ImmutableArray<AttributeData> attributes,
ImmutableArray<INamedTypeSymbol> serializableTypes,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes,
ISymbol? parentSymbol,
SerializableFieldSaveFlagMethods? serializableFieldSaveFlagMethods
)
{
foreach (var rule in Rules.Values)
{
if (rule.GenerateRuleState(
compilation,
propertyType,
attributes,
serializableTypes,
embeddedSerializableTypes,
parentSymbol,
out var ruleArguments
))
{
return new SerializableProperty
{
Name = propertyName,
Type = propertyType.ToDisplayString(),
Order = order,
UsesSaveFlag = serializableFieldSaveFlagMethods?.DetermineFieldShouldSerialize != null ? true : null,
Rule = rule.RuleName,
RuleArguments = ruleArguments.Length > 0 ? ruleArguments : null
};
}
}
throw new Exception($"No rule found for property {propertyName} of type {propertyType} ({Rules.Count})");
}
}
}

View file

@ -1,112 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableMigrationSchema.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Text.RegularExpressions;
using Microsoft.CodeAnalysis;
namespace SerializableMigration
{
public static class SerializableMigrationSchema
{
public static JsonSerializerOptions GetJsonSerializerOptions() =>
new()
{
WriteIndented = true,
AllowTrailingCommas = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
ReadCommentHandling = JsonCommentHandling.Skip
};
private static Dictionary<string, SerializableMetadata> _cache = new();
private static readonly Regex _fileRegex = new(@"\S+\.v\d+\.json$");
public static List<SerializableMetadata> GetMigrations(
INamedTypeSymbol typeSymbol,
int version,
string migrationPath,
JsonSerializerOptions options
)
{
var typeName = typeSymbol.ToDisplayString();
var migrations = new SortedSet<SerializableMetadata>(new SerializableMetadataComparer());
var migrationFiles = Directory.GetFiles(migrationPath, $"{typeName}.v*.json");
foreach (var file in migrationFiles)
{
var fi = new FileInfo(file);
if (!_cache.TryGetValue(fi.Name, out var migration))
{
var text = File.ReadAllText(file, Encoding.UTF8);
migration = JsonSerializer.Deserialize<SerializableMetadata>(text, options);
_cache[fi.Name] = migration;
}
if (typeName == migration!.Type && version > migration.Version)
{
migrations.Add(migration);
}
}
return migrations.ToList();
}
public static List<SerializableMetadata> GetMigrationsByAnalyzerConfig(
this GeneratorExecutionContext context,
INamedTypeSymbol typeSymbol,
int version,
JsonSerializerOptions options
)
{
var typeName = typeSymbol.ToDisplayString();
var migrations = new SortedSet<SerializableMetadata>(new SerializableMetadataComparer());
foreach (var additionalText in context.AdditionalFiles)
{
var fi = new FileInfo(additionalText.Path);
if (!_fileRegex.IsMatch(fi.Name))
{
continue;
}
if (!_cache.TryGetValue(fi.Name, out var migration))
{
var text = additionalText.GetText(context.CancellationToken)?.ToString();
if (text == null)
{
continue;
}
migration = JsonSerializer.Deserialize<SerializableMetadata>(text, options);
_cache[fi.Name] = migration;
}
if (typeName == migration!.Type && version > migration.Version)
{
migrations.Add(migration);
}
}
return migrations.ToList();
}
}
}

View file

@ -1,40 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableProperty.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Text.Json.Serialization;
namespace SerializableMigration
{
public record SerializableProperty
{
[JsonPropertyName("name")]
public string Name { get; init; }
[JsonPropertyName("type")]
public string Type { get; init; }
[JsonPropertyName("usesSaveFlag")]
public bool? UsesSaveFlag { get; init; }
[JsonPropertyName("rule")]
public string Rule { get; init; }
[JsonPropertyName("ruleArguments")]
public string[]? RuleArguments { get; init; }
[JsonIgnore]
public int Order { get; init; }
}
}

View file

@ -1,42 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializablePropertyComparer.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
namespace SerializableMigration
{
public class SerializablePropertyComparer : IComparer<SerializableProperty>
{
public int Compare(SerializableProperty x, SerializableProperty y)
{
if (Equals(x, y))
{
return 0;
}
if (Equals(null, y))
{
return 1;
}
if (Equals(null, x))
{
return -1;
}
return x.Order.CompareTo(y.Order);
}
}
}

View file

@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<LangVersion>preview</LangVersion>
<BuildOutputTargetFolder>analyzers</BuildOutputTargetFolder>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="Humanizer.Core" Version="2.13.14" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="System.Text.Json" Version="6.0.0" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="System.Text.Encodings.Web" Version="6.0.0" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" GeneratePathProperty="true" PrivateAssets="all" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="6.0.0" GeneratePathProperty="true" PrivateAssets="all" />
</ItemGroup>
<PropertyGroup>
<GetTargetPathDependsOn>$(GetTargetPathDependsOn);GetDependencyTargetPaths</GetTargetPathDependsOn>
</PropertyGroup>
<Target Name="GetDependencyTargetPaths">
<ItemGroup>
<TargetPathWithTargetPlatformMoniker Include="$(PKGHumanizer_Core)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Json)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Text_Encodings_Web)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGMicrosoft_Bcl_AsyncInterfaces)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
<TargetPathWithTargetPlatformMoniker Include="$(PKGSystem_Runtime_CompilerServices_Unsafe)\lib\netstandard2.0\*.dll" IncludeRuntimeDependency="false" />
</ItemGroup>
</Target>
</Project>

View file

@ -1,133 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SyntaxReceiver.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace SerializationGenerator
{
public class SerializerSyntaxReceiver : ISyntaxContextReceiver
{
#pragma warning disable RS1024
public Dictionary<INamedTypeSymbol, (AttributeData?, List<ISymbol>)> ClassAndFields { get; } = new(SymbolEqualityComparer.Default);
public Dictionary<INamedTypeSymbol, (AttributeData?, List<ISymbol>)> EmbeddedClassAndFields { get; } = new(SymbolEqualityComparer.Default);
#pragma warning restore RS1024
public ImmutableArray<INamedTypeSymbol> SerializableList => ClassAndFields.Keys.ToImmutableArray();
public ImmutableArray<INamedTypeSymbol> EmbeddedSerializableList => EmbeddedClassAndFields.Keys.ToImmutableArray();
public void OnVisitSyntaxNode(SyntaxNode node, SemanticModel semanticModel)
{
var compilation = semanticModel.Compilation;
if (node is ClassDeclarationSyntax { AttributeLists: { Count: > 0 } } classDeclarationSyntax)
{
if (semanticModel.GetDeclaredSymbol(classDeclarationSyntax) is not INamedTypeSymbol classSymbol)
{
return;
}
if (classSymbol.IsEmbeddedSerializable(compilation, out var attrData))
{
if (EmbeddedClassAndFields.TryGetValue(classSymbol, out var value))
{
var (_, fieldsList) = value;
EmbeddedClassAndFields[classSymbol] = (attrData, fieldsList);
}
else
{
EmbeddedClassAndFields.Add(classSymbol, (attrData, new List<ISymbol>()));
}
}
else if (classSymbol.WillBeSerializable(compilation, out attrData))
{
if (ClassAndFields.TryGetValue(classSymbol, out var value))
{
var (_, fieldsList) = value;
ClassAndFields[classSymbol] = (attrData, fieldsList);
}
else
{
ClassAndFields.Add(classSymbol, (attrData, new List<ISymbol>()));
}
}
return;
}
if (node is FieldDeclarationSyntax { AttributeLists: { Count: > 0 } } fieldDeclarationSyntax)
{
foreach (var variable in fieldDeclarationSyntax.Declaration.Variables)
{
if (semanticModel.GetDeclaredSymbol(variable) is IFieldSymbol fieldSymbol)
{
AddFieldOrProperty(fieldSymbol, compilation);
}
}
return;
}
if (node is PropertyDeclarationSyntax { AttributeLists: { Count: > 0 } } propertyDeclarationSyntax)
{
if (semanticModel.GetDeclaredSymbol(propertyDeclarationSyntax) is IPropertySymbol propertySymbol)
{
AddFieldOrProperty(propertySymbol, compilation);
}
}
}
public void OnVisitSyntaxNode(GeneratorSyntaxContext context) =>
OnVisitSyntaxNode(context.Node, context.SemanticModel);
private void AddFieldOrProperty(ISymbol symbol, Compilation compilation)
{
var serializableFieldAttr = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_FIELD_ATTRIBUTE);
var parentAttr = compilation.GetTypeByMetadataName(SymbolMetadata.SERIALIZABLE_PARENT_ATTRIBUTE);
if (symbol.GetAttribute(serializableFieldAttr) == null && symbol.GetAttribute(parentAttr) == null)
{
return;
}
var classSymbol = symbol.ContainingType;
if (ClassAndFields.TryGetValue(classSymbol, out var value))
{
var (_, fieldsList) = value;
fieldsList.Add(symbol);
return;
}
if (EmbeddedClassAndFields.TryGetValue(classSymbol, out value))
{
var (_, fieldsList) = value;
fieldsList.Add(symbol);
return;
}
if (classSymbol.WillBeSerializable(compilation, out var attrData))
{
ClassAndFields.Add(classSymbol, (attrData, new List<ISymbol> { symbol }));
}
else if (classSymbol.IsEmbeddedSerializable(compilation, out attrData))
{
EmbeddedClassAndFields.Add(classSymbol, (attrData, new List<ISymbol> { symbol }));
}
}
}
}

View file

@ -1,65 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Helpers.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace SerializationGenerator
{
public static class Helpers
{
public static bool ContainsInterface(this ITypeSymbol symbol, ISymbol interfaceSymbol) =>
symbol.Interfaces.Any(i => i.ConstructedFrom.Equals(interfaceSymbol, SymbolEqualityComparer.Default)) ||
symbol.AllInterfaces.Any(i => i.ConstructedFrom.Equals(interfaceSymbol, SymbolEqualityComparer.Default));
public static ImmutableArray<IMethodSymbol> GetAllMethods(this ITypeSymbol symbol, string name)
{
var methods = symbol.GetMembers(name).OfType<IMethodSymbol>().ToImmutableArray();
if (symbol.ContainingSymbol is not ITypeSymbol typeSymbol)
{
return methods;
}
var list = new List<IMethodSymbol>();
list.AddRange(methods.ToList());
list.AddRange(GetAllMethods(typeSymbol, name).ToList());
return list.ToImmutableArray();
}
public static string ToFriendlyString(this Accessibility accessibility) => SyntaxFacts.GetText(accessibility);
public static Accessibility GetAccessibility(string? value) =>
value switch
{
"private" => Accessibility.Private,
"protected" => Accessibility.Protected,
"internal" => Accessibility.Internal,
"public" => Accessibility.Public,
"protected internal" => Accessibility.ProtectedOrInternal,
"private protected" => Accessibility.ProtectedAndInternal,
_ => Accessibility.NotApplicable
};
public static bool CanBeConstructedFrom(this ITypeSymbol? symbol, ISymbol classSymbol) =>
symbol is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.ConstructedFrom.Equals(
classSymbol,
SymbolEqualityComparer.Default
) || symbol != null && CanBeConstructedFrom(symbol.BaseType, classSymbol);
}
}

View file

@ -1,125 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Arguments.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GetTypesFromTypedConstant(TypedConstant arg, List<ITypeSymbol> list)
{
if (arg.Kind == TypedConstantKind.Type)
{
list.Add((ITypeSymbol)arg.Value);
}
else if (arg.Kind == TypedConstantKind.Array)
{
for (var i = 0; i < arg.Values.Length; i++)
{
GetTypesFromTypedConstant(arg.Values[i], list);
}
}
}
public static void GenerateSignatureArguments(this StringBuilder source, ImmutableArray<(ITypeSymbol, string)> parameters)
{
for (var i = 0; i < parameters.Length; i++)
{
var (t, v) = parameters[i];
source.AppendFormat("{0} {1}", t.ToDisplayString(), v);
if (i < parameters.Length - 1)
{
source.Append(", ");
}
}
}
public static void GenerateNamedArgument(this StringBuilder source, KeyValuePair<string, TypedConstant> namedArg)
{
source.AppendFormat("{0} = ", namedArg.Key);
source.GenerateTypedConstant(namedArg.Value);
}
public static void GenerateTypedConstants(this StringBuilder source, ImmutableArray<TypedConstant> args)
{
source.Append("new []{");
for (var i = 0; i < args.Length; i++)
{
source.GenerateTypedConstant(args[i]);
if (i < args.Length - 1)
{
source.Append(", ");
}
}
source.Append('}');
}
public static void GenerateTypedConstant(this StringBuilder source, TypedConstant arg)
{
if (arg.IsNull)
{
source.Append("null");
return;
}
switch (arg.Kind)
{
default:
{
return;
}
case TypedConstantKind.Primitive:
{
if (arg.Value is string str)
{
source.AppendFormat("\"{0}\"", str);
}
else
{
source.Append(arg.Value);
}
break;
}
case TypedConstantKind.Enum:
{
if (arg.Type == null || arg.Value == null)
{
source.Append("null");
}
else
{
source.AppendFormat("({0}){1}", arg.Type.ToDisplayString(), arg.Value);
}
break;
}
case TypedConstantKind.Type:
{
source.AppendFormat("typeof({0})", ((ITypeSymbol)arg.Value)?.Name);
break;
}
case TypedConstantKind.Array:
{
source.GenerateTypedConstants(arg.Values);
break;
}
}
}
}
}

View file

@ -1,102 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Attribute.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateAttribute(
this StringBuilder source,
string indent,
string attrClassName,
ImmutableArray<TypedConstant> args
)
{
source.Append($"{indent}[{attrClassName}");
var hasArgs = args.Length > 0;
if (hasArgs)
{
source.Append("(");
}
for (var i = 0; i < args.Length; i++)
{
var arg = args[i];
source.GenerateTypedConstant(arg);
if (i < args.Length - 1)
{
source.Append(", ");
}
}
if (hasArgs)
{
source.Append(")");
}
source.AppendLine("]");
}
public static void GenerateAttribute(this StringBuilder source, AttributeData attr)
{
source.Append($" [{attr.AttributeClass?.Name}");
var ctorArgs = attr.ConstructorArguments;
var namedArgs = attr.NamedArguments;
var hasArgs = ctorArgs.Length + namedArgs.Length > 0;
if (hasArgs)
{
source.Append("(");
}
for (var i = 0; i < ctorArgs.Length; i++)
{
var arg = ctorArgs[i];
source.GenerateTypedConstant(arg);
if (i < ctorArgs.Length - 1)
{
source.Append(", ");
}
}
for (var i = 0; i < namedArgs.Length; i++)
{
var arg = namedArgs[i];
source.GenerateNamedArgument(arg);
if (i < namedArgs.Length - 1)
{
source.Append(", ");
}
}
if (hasArgs)
{
source.Append(")");
}
source.AppendLine("]");
}
public static void AggressiveInline(this StringBuilder source, string indent) =>
source.AppendLine(
$"{indent}[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]"
);
}
}

View file

@ -1,72 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Class.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateClassStart(
this StringBuilder source,
INamedTypeSymbol classSymbol,
string indent,
ImmutableArray<ITypeSymbol> interfaces,
bool isPartial = true
)
{
var accessor = classSymbol.DeclaredAccessibility;
source.Append($"{indent}{accessor.ToFriendlyString()} {(isPartial ? "partial " : "")}class {classSymbol.Name}");
if (!interfaces.IsEmpty)
{
source.Append(" : ");
for (var i = 0; i < interfaces.Length; i++)
{
source.Append(interfaces[i].ToDisplayString());
if (i < interfaces.Length - 1)
{
source.Append(", ");
}
}
}
source.AppendLine($"\n{indent}{{");
}
public static void GenerateClassEnd(this StringBuilder source, string indent)
{
source.AppendLine($"{indent}}}");
}
// TODO: Generalize this to any field using dynamic indentation
public static void GenerateClassField(
this StringBuilder source,
string indent,
Accessibility accessors,
InstanceModifier instance,
string type,
string variableName,
string value
)
{
var instanceStr = instance == InstanceModifier.None ? "" : $"{instance.ToFriendlyString()} ";
var accessorStr = accessors == Accessibility.NotApplicable ? "" : $"{accessors.ToFriendlyString()} ";
var valueStr = value == null ? "" : $" = {value}";
source.AppendLine($"{indent}{accessorStr}{instanceStr}{type} {variableName}{valueStr};");
}
}
}

View file

@ -1,50 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Enum.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateEnumStart(
this StringBuilder source,
string enumName,
string indent,
bool useFlags,
Accessibility accessor = Accessibility.Public
)
{
if (useFlags)
{
source.AppendLine($"{indent}[System.Flags]");
}
source.AppendLine($"{indent}{accessor.ToFriendlyString()} enum {enumName}\n{indent}{{");
}
public static void GenerateEnumValue(this StringBuilder source, string indent, bool isFlag, string name, int value)
{
var number = value < 0 ? 0 : 1 << value;
var valueStr = isFlag ? $"0x{number:X8}" : value.ToString();
source.AppendLine($"{indent}{name} = {valueStr},");
}
public static void GenerateEnumEnd(this StringBuilder source, string indent)
{
source.AppendLine($"{indent}}}");
}
}
}

View file

@ -1,39 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.InstanceModifier.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
namespace SerializationGenerator
{
public enum InstanceModifier
{
None,
Const,
ReadOnly,
Static,
StaticReadOnly
}
public static partial class SourceGeneration
{
public static string ToFriendlyString(this InstanceModifier modifier) =>
modifier switch
{
InstanceModifier.Const => "const",
InstanceModifier.ReadOnly => "readonly",
InstanceModifier.Static => "static",
InstanceModifier.StaticReadOnly => "static readonly",
_ => ""
};
}
}

View file

@ -1,62 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Method.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Text;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateMethodStart(
this StringBuilder source, string indent, string methodName, Accessibility accessors, bool isOverride,
string returnType, ImmutableArray<(ITypeSymbol, string)> parameters
)
{
source.Append($"{indent}{accessors.ToFriendlyString()}{(isOverride ? " override" : "")} {returnType} {methodName}(");
source.GenerateSignatureArguments(parameters);
source.AppendLine($")\n{indent}{{");
}
public static void GenerateMethodEnd(this StringBuilder source, string indent) => source.AppendLine($"{indent}}}");
public static void GenerateConstructorStart(
this StringBuilder source, string indent, string className, Accessibility accessors, ImmutableArray<(ITypeSymbol, string)> parameters,
ImmutableArray<string> baseParameters, bool isOverload = false
)
{
source.Append($"{indent}{accessors.ToFriendlyString()} {className}(");
source.GenerateSignatureArguments(parameters);
source.Append(')');
bool hasBaseParams = baseParameters.Length > 0;
if (hasBaseParams)
{
source.AppendFormat(" : {0}(", isOverload ? "this" : "base");
for (int i = 0; i < baseParameters.Length; i++)
{
source.Append(baseParameters[i]);
if (i < baseParameters.Length - 1)
{
source.Append(',');
}
}
source.Append(')');
}
source.AppendLine($"\n{indent}{{");
}
}
}

View file

@ -1,33 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Namespace.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Text;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static void GenerateNamespaceStart(this StringBuilder source, string namespaceName)
{
source.AppendLine($@"namespace {namespaceName}
{{");
}
public static void GenerateNamespaceEnd(this StringBuilder source)
{
source.AppendLine("}");
}
}
}

View file

@ -1,144 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceGeneration.Property.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Text;
using Humanizer;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SourceGeneration
{
public static string GetPropertyName(this IFieldSymbol fieldSymbol)
{
var fieldName = fieldSymbol.Name;
var propertyName = fieldName;
if (propertyName.StartsWith("m_", StringComparison.OrdinalIgnoreCase))
{
propertyName = propertyName.Substring(2);
}
else if (propertyName.StartsWith("_", StringComparison.OrdinalIgnoreCase))
{
propertyName = propertyName.Substring(1);
}
return propertyName.Dehumanize();
}
public static void GeneratePropertyStart(
this StringBuilder source,
string indent,
Accessibility accessors,
bool isVirtual,
IFieldSymbol fieldSymbol
)
{
var propertyName = fieldSymbol.GetPropertyName();
var virt = isVirtual ? "virtual " : "";
source.AppendLine($"{indent}{accessors.ToFriendlyString()} {virt}{fieldSymbol.Type} {propertyName}");
source.AppendLine($"{indent}{{");
}
public static void GenerateAutoProperty(
this StringBuilder source,
Accessibility accessors,
string type,
string propertyName,
Accessibility? getAccessor,
Accessibility? setAccessor,
string indent,
bool useInit = false,
string defaultValue = null,
bool isOverride = false
)
{
if (getAccessor == null && setAccessor == null)
{
throw new ArgumentNullException($"Must specify a {nameof(getAccessor)} or {nameof(setAccessor)} parameter");
}
var getter = getAccessor == null ?
"" :
$"{(getAccessor != Accessibility.NotApplicable ? $"{getAccessor.Value.ToFriendlyString()} " : "")}get;";
var getterSpace = getAccessor != null ? " " : "";
var setOrInit = useInit ? "init;" : "set;";
var setterAccessor = setAccessor is null or Accessibility.NotApplicable
? ""
: $"{setAccessor.Value.ToFriendlyString() ?? ""} ";
var setter = setAccessor == null ? "" : $"{getterSpace}{setterAccessor}{setOrInit}";
var propertyAccessor = accessors == Accessibility.NotApplicable ? "" : $"{accessors.ToFriendlyString()} ";
var printOverride = isOverride ? "override " : "";
var printDefaultValue = defaultValue != null ? $"{(setAccessor != null ? " =" : "")} {defaultValue};" : "";
var printGetterSetter = setAccessor == null ? "=>" : $"{{ {getter}{setter} }}";
source.AppendLine($"{indent}{propertyAccessor}{printOverride}{type} {propertyName} {printGetterSetter}{printDefaultValue}");
}
public static void GeneratePropertyEnd(this StringBuilder source, string indent) => source.AppendLine($"{indent}}}");
public static void GeneratePropertyGetterReturnsField(
this StringBuilder source,
string indent,
IFieldSymbol fieldSymbol,
Accessibility Accessibility
)
{
var accessor = Accessibility != Accessibility.NotApplicable ? $"{Accessibility.ToFriendlyString()} " : "";
source.AppendLine($"{indent}{accessor}get => {fieldSymbol.Name};");
}
public static void GeneratePropertyGetterStart(
this StringBuilder source,
string indent,
bool useExpression,
Accessibility Accessibility
)
{
var accessor = Accessibility != Accessibility.NotApplicable ? $"{Accessibility.ToFriendlyString()} " : "";
var expression = useExpression ? " => " : $"\n{indent}{{";
source.AppendLine($"{indent}{accessor}get{expression}");
}
public static void GeneratePropertyGetSetEnd(this StringBuilder source, string indent, bool useExpression)
{
if (!useExpression)
{
source.AppendLine($"{indent}}}");
}
}
public static void GeneratePropertySetterStart(
this StringBuilder source,
string indent,
bool useExpression,
Accessibility Accessibility,
bool useInit = false
)
{
var init = useInit ? "init" : "set";
var expression = useExpression ? " => " : $"\n{indent}{{";
var accessor = Accessibility != Accessibility.NotApplicable ? $"{Accessibility.ToFriendlyString()} " : "";
source.AppendLine($"{indent}{accessor}{init}{expression}");
}
}
}

View file

@ -1,69 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SymbolMetadata.Builtin.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SymbolMetadata
{
public const string DICTIONARY_CLASS = "System.Collections.Generic.Dictionary`2";
public const string LIST_CLASS = "System.Collections.Generic.List`1";
public const string HASHSET_CLASS = "System.Collections.Generic.HashSet`1";
public const string IPADDRESS_CLASS = "System.Net.IPAddress";
public const string KEYVALUEPAIR_STRUCT = "System.Collections.Generic.KeyValuePair";
public const string TIMESPAN_STRUCT = "System.TimeSpan";
public static bool IsTimeSpan(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(TIMESPAN_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsIpAddress(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(IPADDRESS_CLASS),
SymbolEqualityComparer.Default
);
public static bool IsKeyValuePair(this ISymbol symbol, Compilation compilation) =>
(symbol as INamedTypeSymbol)?.ConstructedFrom.Equals(
compilation.GetTypeByMetadataName(KEYVALUEPAIR_STRUCT),
SymbolEqualityComparer.Default
) == true;
public static bool IsDictionary(this ISymbol symbol, Compilation compilation) =>
(symbol as INamedTypeSymbol)?.ConstructedFrom.Equals(
compilation.GetTypeByMetadataName(DICTIONARY_CLASS),
SymbolEqualityComparer.Default
) == true;
public static bool IsHashSet(this ISymbol symbol, Compilation compilation) =>
(symbol as INamedTypeSymbol)?.ConstructedFrom.Equals(
compilation.GetTypeByMetadataName(HASHSET_CLASS),
SymbolEqualityComparer.Default
) == true;
public static bool IsList(this ISymbol symbol, Compilation compilation) =>
(symbol as INamedTypeSymbol)?.ConstructedFrom.Equals(
compilation.GetTypeByMetadataName(LIST_CLASS),
SymbolEqualityComparer.Default
) == true;
public static bool IsPrimitiveFromTypeDisplayString(string type) =>
type is "bool" or "sbyte" or "short" or "int" or "long" or "byte" or "ushort"
or "uint" or "ulong" or "float" or "double" or "string" or "decimal";
}
}

View file

@ -1,228 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SymbolMetadata.UO.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
namespace SerializationGenerator
{
public static partial class SymbolMetadata
{
public const string INVALIDATEPROPERTIES_ATTRIBUTE = "Server.InvalidatePropertiesAttribute";
public const string AFTERDESERIALIZATION_ATTRIBUTE = "Server.AfterDeserializationAttribute";
public const string SERIALIZABLE_ATTRIBUTE = "Server.SerializableAttribute";
public const string EMBEDDED_SERIALIZABLE_ATTRIBUTE = "Server.EmbeddedSerializableAttribute";
public const string SERIALIZABLE_PARENT_ATTRIBUTE = "Server.SerializableParentAttribute";
public const string SERIALIZABLE_FIELD_ATTRIBUTE = "Server.SerializableFieldAttribute";
public const string SERIALIZABLE_FIELD_ATTR_ATTRIBUTE = "Server.SerializableFieldAttrAttribute";
public const string SERIALIZABLE_INTERFACE = "Server.ISerializable";
public const string GENERIC_WRITER_INTERFACE = "Server.IGenericWriter";
public const string GENERIC_READER_INTERFACE = "Server.IGenericReader";
public const string DELTA_DATE_TIME_ATTRIBUTE = "Server.DeltaDateTimeAttribute";
public const string INTERN_STRING_ATTRIBUTE = "Server.InternStringAttribute";
public const string ENCODED_INT_ATTRIBUTE = "Server.EncodedIntAttribute";
public const string TIDY_ATTRIBUTE = "Server.TidyAttribute";
public const string POINT2D_STRUCT = "Server.Point2D";
public const string POINT3D_STRUCT = "Server.Point3D";
public const string RECTANGLE2D_STRUCT = "Server.Rectangle2D";
public const string RECTANGLE3D_STRUCT = "Server.Rectangle3D";
public const string RACE_CLASS = "Server.Race";
public const string MAP_CLASS = "Server.Map";
public const string TIMER_CLASS = "Server.Timer";
public const string TIMER_DRIFT_ATTRIBUTE = "Server.TimerDriftAttribute";
public const string DESERIALIZE_TIMER_FIELD_ATTRIBUTE = "Server.DeserializeTimerFieldAttribute";
public const string SERIALIZABLE_FIELD_SAVE_FLAG_ATTRIBUTE = "Server.SerializableFieldSaveFlagAttribute";
public const string SERIALIZABLE_FIELD_DEFAULT_ATTRIBUTE = "Server.SerializableFieldDefaultAttribute";
public const string RAW_SERIALIZABLE_INTERFACE = "Server.IRawSerializable";
// ModernUO modified BitArray
public const string SERVER_BITARRAY_CLASS = "Server.Collections.BitArray";
public static bool IsTimerDrift(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(TIMER_DRIFT_ATTRIBUTE)) == true;
public static bool IsTimer(this ITypeSymbol symbol, Compilation compilation) =>
symbol.CanBeConstructedFrom(compilation.GetTypeByMetadataName(TIMER_CLASS));
public static bool IsEncodedInt(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(ENCODED_INT_ATTRIBUTE)) == true;
public static bool IsDeltaDateTime(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(DELTA_DATE_TIME_ATTRIBUTE)) == true;
public static bool IsInternString(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(INTERN_STRING_ATTRIBUTE)) == true;
public static bool IsTidy(this AttributeData attr, Compilation compilation) =>
attr?.IsAttribute(compilation.GetTypeByMetadataName(TIDY_ATTRIBUTE)) == true;
public static bool IsAttribute(this AttributeData attr, ISymbol symbol) =>
attr?.AttributeClass?.Equals(symbol, SymbolEqualityComparer.Default) == true;
public static bool IsEnum(this ITypeSymbol symbol) =>
symbol.SpecialType == SpecialType.System_Enum || symbol.TypeKind == TypeKind.Enum;
public static bool HasSerializableInterface(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> serializableTypes
) =>
symbol.ContainsInterface(compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE)) ||
serializableTypes.Contains(symbol);
public static bool HasRawSerializableInterface(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> embeddedSerializableTypes
) =>
symbol.ContainsInterface(compilation.GetTypeByMetadataName(RAW_SERIALIZABLE_INTERFACE)) ||
embeddedSerializableTypes.Contains(symbol);
public static bool Contains(this ImmutableArray<INamedTypeSymbol> symbols, ITypeSymbol? symbol) =>
symbol is INamedTypeSymbol namedSymbol &&
symbols.Contains(namedSymbol, SymbolEqualityComparer.Default) || symbols.Contains(symbol?.BaseType);
public static bool HasGenericReaderCtor(
this INamedTypeSymbol symbol,
Compilation compilation,
ISymbol? parentSymbol,
out bool requiresParent
)
{
var genericReaderInterface = compilation.GetTypeByMetadataName(GENERIC_READER_INTERFACE);
var genericCtor = symbol.Constructors.FirstOrDefault(
m => !m.IsStatic &&
m.MethodKind == MethodKind.Constructor &&
m.Parameters.Length >= 1 &&
m.Parameters.Length <= 2 &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericReaderInterface)
);
requiresParent = genericCtor?.Parameters.Length == 2 && SymbolEqualityComparer.Default.Equals(genericCtor.Parameters[1].Type, parentSymbol);
return genericCtor != null;
}
public static bool HasPublicSerializeMethod(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> serializableTypes
)
{
var genericWriterInterface = compilation.GetTypeByMetadataName(GENERIC_WRITER_INTERFACE);
return symbol.GetAllMethods("Serialize")
.Any(
m => !m.IsStatic &&
m.ReturnsVoid &&
m.Parameters.Length == 1 &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericWriterInterface) &&
m.DeclaredAccessibility == Accessibility.Public
);
}
public static bool HasPublicDeserializeMethod(
this ITypeSymbol symbol,
Compilation compilation,
ImmutableArray<INamedTypeSymbol> serializableTypes
)
{
var genericReaderInterface = compilation.GetTypeByMetadataName(GENERIC_READER_INTERFACE);
return symbol.GetAllMethods("Deserialize")
.Any(
m => !m.IsStatic &&
m.ReturnsVoid &&
m.Parameters.Length == 1 &&
SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericReaderInterface) &&
m.DeclaredAccessibility == Accessibility.Public
);
}
public static bool IsPoint2D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(POINT2D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsPoint3D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(POINT3D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsRectangle2D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(RECTANGLE2D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsRectangle3D(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(RECTANGLE3D_STRUCT),
SymbolEqualityComparer.Default
);
public static bool IsRace(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(RACE_CLASS),
SymbolEqualityComparer.Default
);
public static bool IsMap(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(MAP_CLASS),
SymbolEqualityComparer.Default
);
public static bool IsBitArray(this ISymbol symbol, Compilation compilation) =>
symbol.Equals(
compilation.GetTypeByMetadataName(SERVER_BITARRAY_CLASS),
SymbolEqualityComparer.Default
);
public static AttributeData? GetAttribute(this ISymbol symbol, ISymbol attrSymbol) =>
symbol
.GetAttributes()
.FirstOrDefault(
ad => ad.AttributeClass != null && SymbolEqualityComparer.Default.Equals(ad.AttributeClass, attrSymbol)
);
public static bool WillBeSerializable(this INamedTypeSymbol classSymbol, Compilation compilation, out AttributeData? attributeData)
{
var serializableInterface = compilation.GetTypeByMetadataName(SERIALIZABLE_INTERFACE);
if (!classSymbol.ContainsInterface(serializableInterface))
{
attributeData = null;
return false;
}
var serializableEntityAttribute =
compilation.GetTypeByMetadataName(SERIALIZABLE_ATTRIBUTE);
attributeData = classSymbol.GetAttribute(serializableEntityAttribute);
return attributeData != null;
}
public static bool IsEmbeddedSerializable(this INamedTypeSymbol classSymbol, Compilation compilation, out AttributeData? attributeData)
{
var embeddedSerializableEntityAttribute =
compilation.GetTypeByMetadataName(EMBEDDED_SERIALIZABLE_ATTRIBUTE);
attributeData = classSymbol.GetAttribute(embeddedSerializableEntityAttribute);
return attributeData != null;
}
}
}

View file

@ -1,28 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Utility.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Generic;
namespace SerializationGenerator
{
public static class Utility
{
public static void Deconstruct<T1, T2>(this KeyValuePair<T1, T2> tuple, out T1 key, out T2 value)
{
key = tuple.Key;
value = tuple.Value;
}
}
}

View file

@ -1 +0,0 @@
Output/

View file

@ -1,103 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: Application.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Immutable;
using System.IO;
using System.Text.Json;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using SerializationGenerator;
namespace SerializationSchemaGenerator
{
public static class Application
{
public static void Main(string[] args)
{
if (args.Length < 1)
{
throw new ArgumentException("Usage: dotnet SerializationSchemaGenerator.dll <path to solution>");
}
var solutionPath = args[0];
Parallel.ForEach(
SourceCodeAnalysis.GetCompilation(solutionPath),
(projectCompilation) =>
{
var (project, compilation) = projectCompilation;
if (project.Name.EndsWith(".Tests", StringComparison.Ordinal) || project.Name == "Benchmarks")
{
return;
}
var projectFile = new FileInfo(project.FilePath!);
var projectPath = projectFile.Directory?.FullName;
var migrationPath = Path.Join(projectPath, "Migrations");
Directory.CreateDirectory(migrationPath);
var syntaxReceiver = new SerializerSyntaxReceiver();
foreach (var syntaxTree in compilation.SyntaxTrees)
{
var root = syntaxTree.GetRoot();
var syntaxVisitor = new SyntaxVisitor(compilation.GetSemanticModel(syntaxTree), syntaxReceiver);
syntaxVisitor.Visit(root);
}
var jsonOptions = new JsonSerializerOptions
{
WriteIndented = true,
AllowTrailingCommas = true,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
ReadCommentHandling = JsonCommentHandling.Skip
};
var serializableTypes = syntaxReceiver.SerializableList;
var embeddedSerializableTypes = syntaxReceiver.EmbeddedSerializableList;
foreach (var (classSymbol, (attributeData, fieldsList)) in syntaxReceiver.ClassAndFields)
{
var source = compilation.GenerateSerializationPartialClass(
classSymbol,
attributeData,
migrationPath,
false,
jsonOptions,
fieldsList.ToImmutableArray(),
serializableTypes,
embeddedSerializableTypes
);
}
foreach (var (classSymbol, (attributeData, fieldsList)) in syntaxReceiver.EmbeddedClassAndFields)
{
var source = compilation.GenerateSerializationPartialClass(
classSymbol,
attributeData,
migrationPath,
true,
jsonOptions,
fieldsList.ToImmutableArray(),
serializableTypes,
embeddedSerializableTypes
);
}
}
);
}
}
}

View file

@ -1,23 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<OutDir>Output</OutDir>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\SerializationGenerator\SerializationGenerator.csproj">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.13.14" />
<PackageReference Include="Microsoft.Build.Locator" Version="1.4.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.0.1" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.0.1" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
<PackageReference Include="System.Text.Encodings.Web" Version="6.0.0" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="6.0.0" />
</ItemGroup>
</Project>

View file

@ -1,49 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SourceCodeAnalysis.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.Build.Locator;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.MSBuild;
namespace SerializationSchemaGenerator
{
public static class SourceCodeAnalysis
{
public static List<(Project, Compilation)> GetCompilation(string solutionPath)
{
if (!File.Exists(solutionPath) || !solutionPath.EndsWith(".sln", StringComparison.Ordinal))
{
throw new FileNotFoundException($"Could not open a valid solution at location {solutionPath}");
}
MSBuildLocator.RegisterDefaults();
var workspace = MSBuildWorkspace.Create();
var solutionToAnalyze = workspace.OpenSolutionAsync(solutionPath).Result;
var results = solutionToAnalyze.Projects.AsParallel()
.Select((project) => (project, project?.GetCompilationAsync().Result))
.Where((value) => value.Result != null)
.ToList();
return results;
}
}
}

View file

@ -1,52 +0,0 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SyntaxVisitor.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using SerializationGenerator;
namespace SerializationSchemaGenerator
{
public class SyntaxVisitor : CSharpSyntaxWalker
{
private readonly SemanticModel _semanticModel;
private readonly SerializerSyntaxReceiver _syntaxReceiver;
public SyntaxVisitor(SemanticModel semanticModel, SerializerSyntaxReceiver syntaxReceiver)
{
_semanticModel = semanticModel;
_syntaxReceiver = syntaxReceiver;
}
public override void VisitClassDeclaration(ClassDeclarationSyntax node)
{
base.VisitClassDeclaration(node);
_syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel);
}
public override void VisitFieldDeclaration(FieldDeclarationSyntax node)
{
base.VisitFieldDeclaration(node);
_syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel);
}
public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node)
{
base.VisitPropertyDeclaration(node);
_syntaxReceiver.OnVisitSyntaxNode(node, _semanticModel);
}
}
}

View file

@ -193,7 +193,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpAlphaRegion g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpAlphaRegion.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("checkertrans"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -202,7 +202,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpBackground g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpBackground.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("resizepic"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
@ -212,7 +212,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("button"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
@ -224,7 +224,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpCheck g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpButton.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("checkbox"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
@ -237,18 +237,18 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpGroup g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpGroup.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("group"));
disp.AppendLayout(g.Group);
}
public static void AppendTo(this GumpECHandleInput g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpECHandleInput.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("echandleinput"));
}
public static void AppendTo(this GumpHtml g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpHtml.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("htmlgump"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -264,7 +264,7 @@ namespace Server.Tests.Network
{
case GumpHtmlLocalizedType.Plain:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNamePlain);
disp.AppendLayout(Gump.StringToBuffer("xmfhtmlgump"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
@ -279,7 +279,7 @@ namespace Server.Tests.Network
case GumpHtmlLocalizedType.Color:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameColor);
disp.AppendLayout(Gump.StringToBuffer("xmfhtmlgumpcolor"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
@ -295,7 +295,7 @@ namespace Server.Tests.Network
case GumpHtmlLocalizedType.Args:
{
disp.AppendLayout(GumpHtmlLocalized.LayoutNameArgs);
disp.AppendLayout(Gump.StringToBuffer("xmfhtmltok"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
@ -314,27 +314,27 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImage.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("gumppic"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
if (g.Hue != 0)
{
disp.AppendLayout(GumpImage.HueEquals);
disp.AppendLayoutNS(" hue=");
disp.AppendLayoutNS(g.Hue);
}
if (!string.IsNullOrEmpty(g.Class))
{
disp.AppendLayout(GumpImage.ClassEquals);
disp.AppendLayoutNS(g.Class);
disp.AppendLayoutNS(" class=");
disp.AppendLayout(Gump.StringToBuffer(g.Class));
}
}
public static void AppendTo(this GumpImageTileButton g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTileButton.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("buttontileart"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.NormalID);
@ -347,17 +347,11 @@ namespace Server.Tests.Network
disp.AppendLayout(g.Hue);
disp.AppendLayout(g.Width);
disp.AppendLayout(g.Height);
if (g.LocalizedTooltip > 0)
{
disp.AppendLayout(GumpImageTileButton.LayoutTooltip);
disp.AppendLayout(g.LocalizedTooltip);
}
}
public static void AppendTo(this GumpImageTiled g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpImageTiled.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("gumppictiled"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -367,7 +361,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpItem g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(g.Hue == 0 ? GumpItem.LayoutName : GumpItem.LayoutNameHue);
disp.AppendLayout(Gump.StringToBuffer(g.Hue == 0 ? "tilepic" : "tilepichue"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.ItemID);
@ -380,13 +374,13 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpItemProperty g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpItemProperty.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("itemproperty"));
disp.AppendLayout(g.Serial);
}
public static void AppendTo(this GumpLabel g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabel.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("text"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Hue);
@ -395,7 +389,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpLabelCropped g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpLabelCropped.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("croppedtext"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -406,19 +400,19 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpMasterGump g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpMasterGump.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("mastergump"));
disp.AppendLayout(g.GumpID);
}
public static void AppendTo(this GumpPage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpPage.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("page"));
disp.AppendLayout(g.Page);
}
public static void AppendTo(this GumpRadio g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpRadio.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("radio"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.InactiveID);
@ -431,7 +425,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpSpriteImage g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpSpriteImage.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("picinpic"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.GumpID);
@ -443,7 +437,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpTextEntry g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntry.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("textentry"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -457,7 +451,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpTextEntryLimited g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTextEntryLimited.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("textentrylimited"));
disp.AppendLayout(g.X);
disp.AppendLayout(g.Y);
disp.AppendLayout(g.Width);
@ -472,7 +466,7 @@ namespace Server.Tests.Network
public static void AppendTo(this GumpTooltip g, IGumpWriter disp, List<string> strings)
{
disp.AppendLayout(GumpTooltip.LayoutName);
disp.AppendLayout(Gump.StringToBuffer("tooltip"));
disp.AppendLayout(g.Number);
if (!string.IsNullOrEmpty(g.Args))

View file

@ -3,17 +3,13 @@
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.0.0" />
<PackageReference Include="Moq" Version="4.16.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.2.0" />
<PackageReference Include="Moq" Version="4.18.1" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3" />
<PackageReference Include="Zlib.Bindings" Version="1.8.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" />
<ProjectReference Include="..\Server\Server.csproj" />
<ProjectReference Include="..\UOContent\UOContent.csproj" />
<DataFiles Include="$(SolutionDir)\Distribution\Data\**" />
<DataFiles Update="..\..\Distribution\Data\Professions\LBR\prof.txt">
<Link>Data\Professions\LBR\prof.txt</Link>
</DataFiles>
</ItemGroup>
<Target Name="CopyData" AfterTargets="AfterBuild">
<Copy SourceFiles="@(DataFiles)" DestinationFolder="$(OutDir)\Data\%(RecursiveDir)" />

View file

@ -0,0 +1,72 @@
using System;
using Server.Buffers;
using Xunit;
namespace Server.Tests.Tests.Buffers;
[Collection("Sequential Tests")]
public class STArrayPoolTests
{
[Theory]
[InlineData(0, 0)]
[InlineData(2, 16)]
[InlineData(56, 64)]
[InlineData(120, 128)]
[InlineData(65535, 65536)]
[InlineData(1024 * 1024 * 15, 1024 * 1024 * 16)]
public void ValidMinimumLengths(int requestedLength, int expectedLength)
{
var arr = STArrayPool<byte>.Shared.Rent(requestedLength);
Assert.Equal(expectedLength, arr.Length);
}
[Fact]
public void NegativeLengthThrows()
{
Assert.Throws<ArgumentOutOfRangeException>(
() =>
{
var arr = STArrayPool<byte>.Shared.Rent(-1);
}
);
}
[Fact]
public void CachesOnlyUpToCPUCountPerBucket()
{
STArrayPool<byte>.Shared.ResetForTesting();
var cores = Environment.ProcessorCount;
var arrays1 = new byte[cores * 8 + 2][]; // 1 for the cache, and 8 * CPU for the stacks
var weakReferences1 = new WeakReference[cores * 8 + 2];
var arrays2 = new byte[cores * 8 + 2][]; // 1 for the cache, and 8 * CPU for the stacks
var weakReferences2 = new WeakReference[cores * 8 + 2];
for (var i = 0; i < arrays1.Length; i++)
{
arrays1[i] = STArrayPool<byte>.Shared.Rent(32);
weakReferences1[i] = new WeakReference(arrays1[i]);
arrays2[i] = STArrayPool<byte>.Shared.Rent(64);
weakReferences2[i] = new WeakReference(arrays2[i]);
}
for (var i = 0; i < arrays1.Length; i++)
{
STArrayPool<byte>.Shared.Return(arrays1[i]);
arrays1[i] = null;
STArrayPool<byte>.Shared.Return(arrays2[i]);
arrays2[i] = null;
}
GC.Collect();
for (var i = 0; i < weakReferences1.Length; i++)
{
// When the last one is returned, the one right before it is dropped.
Assert.Equal(i != weakReferences1.Length - 2, weakReferences1[i].IsAlive);
Assert.Equal(i != weakReferences2.Length - 2, weakReferences2[i].IsAlive);
}
}
}

View file

@ -0,0 +1,29 @@
using Server.Collections;
using Xunit;
namespace Server.Tests;
public class BitArrayTests
{
[Fact]
public void TestBitArray()
{
var bitArray = new BitArray(700); // Restricted Spells;
bitArray.Set(5, true);
bitArray.Set(39, true);
bitArray.Set(125, true);
// Simulate World Saving
var writer = new BufferWriter(1024, false);
writer.Write(bitArray); // Save it to a file
// Simulate World Loading
var reader = new BufferReader(writer.Buffer);
var bitArrayTest = reader.ReadBitArray();
Assert.Equal(700, bitArrayTest.Length);
for (var i = 0; i < bitArrayTest.Length; i++)
{
Assert.Equal(i is 5 or 39 or 125, bitArrayTest.Get(i));
}
}
}

View file

@ -0,0 +1,102 @@
using System;
using Moq;
using Server.Collections;
using Server.Random;
using Xunit;
namespace Server.Tests;
public sealed class PooledRefQueueTests : IDisposable
{
public void Dispose() => RandomSources.SetRng(null);
private static void PrepareRng(int queueCount, int rngValue)
{
Mock<IRandomSource> mockRng = new Mock<IRandomSource>();
mockRng
.Setup(rng => rng.Next(It.IsAny<int>()))
.Returns(
(int size) =>
{
Assert.Equal(queueCount, size);
return rngValue;
}
);
RandomSources.SetRng(mockRng.Object);
}
[Fact]
public void TestPeekRandom1()
{
// Random value for _head = 0, _tail = 5, _size = 5,
using var queue = PooledRefQueue<int>.Create(10);
queue.Enqueue(0);
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3); // <-----
queue.Enqueue(4);
queue.Enqueue(5);
PrepareRng(6, 3);
Assert.Equal(3, queue.PeekRandom());
}
[Fact]
public void TestPeekRandom2()
{
// Random value for _head = 3, _tail = 10, _size = 7,
using var queue = PooledRefQueue<int>.Create(10);
queue.Enqueue(0);
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
queue.Enqueue(4);
queue.Enqueue(5);
queue.Enqueue(6); // <---
queue.Enqueue(7);
queue.Enqueue(8);
queue.Enqueue(9);
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
PrepareRng(7, 3);
Assert.Equal(6, queue.PeekRandom());
}
[Theory]
[InlineData(3, 6)]
[InlineData(8, 11)]
[InlineData(6, 9)]
[InlineData(7, 10)]
public void TestPeekRandom3(int rngValue, int expectedIndex)
{
// Random value for _head = 3, _tail = 2, _size = 10,
using var queue = PooledRefQueue<int>.Create(10);
queue.Enqueue(0);
queue.Enqueue(1);
queue.Enqueue(2);
queue.Enqueue(3);
queue.Enqueue(4);
queue.Enqueue(5);
queue.Enqueue(6);
queue.Enqueue(7);
queue.Enqueue(8);
queue.Enqueue(9);
queue.Dequeue();
queue.Dequeue();
queue.Dequeue();
queue.Enqueue(10);
queue.Enqueue(11);
queue.Enqueue(12);
PrepareRng(10, rngValue);
Assert.Equal(expectedIndex, queue.PeekRandom());
}
}

View file

@ -0,0 +1,19 @@
using Xunit;
namespace Server.Tests;
public class LocalizationEntryTests
{
[Fact]
public void TestClilocAsParameter()
{
Localization.Add("enu", 500002, "This tests ~1_NUMBER~ as parameters.");
Localization.Add("enu", 500003, "clilocs");
string numericFormatter = Localization.Format(500002, "enu", $"{500003:#}");
string stringParam = Localization.Format(500002, "enu", $"{"#500003"}");
Assert.Equal("This tests clilocs as parameters.", numericFormatter);
Assert.Equal("This tests clilocs as parameters.", stringParam);
}
}

View file

@ -24,9 +24,9 @@ namespace Server.Tests.Network
public void TestDisplaySignGump()
{
Serial gumpSerial = (Serial)0x1000;
var gumpId = 100;
var unknownString = "This is an unknown string";
var caption = "This is a caption";
const int gumpId = 100;
const string unknownString = "This is an unknown string";
const string caption = "This is a caption";
var expected = new DisplaySignGump(gumpSerial, gumpId, unknownString, caption).Compile();

View file

@ -11,7 +11,7 @@ namespace Server.Tests.Network
{
private async void DelayedExecute(Action action)
{
await Task.Delay(5);
await Task.Delay(1);
action();
}
@ -130,8 +130,12 @@ namespace Server.Tests.Network
continue;
}
result.CopyFrom(new[] { expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value, expected_value });
result.CopyFrom(new[] {
expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value,
expected_value, expected_value, expected_value, expected_value
});
writer.Advance(16);
count += 16;

View file

@ -0,0 +1,37 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Server.Network;
using Xunit;
namespace Server.Tests.Network;
public class PollGroupTests
{
[Fact]
public void TestPollGroup()
{
// var group = new KQueuePollGroup();
var nss = new NetState[2048];
var handles = new IntPtr[2048];
for (var i = 0; i < nss.Length; i++)
{
nss[i] = PacketTestUtilities.CreateTestNetState();
handles[i] = (IntPtr)nss[i].Handle;
}
GC.AddMemoryPressure(10000000000);
GC.Collect();
GC.RemoveMemoryPressure(10000000000);
GC.Collect();
Thread.Sleep(1000);
for (var i = 0; i < nss.Length; i++)
{
Assert.Equal(nss[i].Handle, (GCHandle)handles[i]);
}
// group.Dispose();
}
}

View file

@ -0,0 +1,37 @@
using System;
using Xunit;
namespace Server.Tests;
public class SerialTests
{
[Fact]
public void TestSerialTryFormatDefault()
{
var serial = (Serial)0xABCD1234u;
const string serialStr = "0xABCD1234";
Span<char> buffer = stackalloc char[serialStr.Length];
var result = serial.TryFormat(buffer, out var charsWritten, null, null);
Assert.True(result);
Assert.Equal(serialStr.Length, charsWritten);
Assert.Equal(serialStr, buffer.ToString());
var interpolated = $"{serial}";
Assert.Equal(serialStr, interpolated);
}
[Fact]
public void TestSerialTryFormatCustom()
{
var serial = (Serial)0xABCD1234u;
const string serialStr = "2882343476";
Span<char> buffer = stackalloc char[serialStr.Length];
var result = serial.TryFormat(buffer, out var charsWritten, "##", null);
Assert.True(result);
Assert.Equal(serialStr.Length, charsWritten);
Assert.Equal(serialStr, buffer.ToString());
var interpolated = $"{serial:##}";
Assert.Equal(serialStr, interpolated);
}
}

View file

@ -245,7 +245,7 @@ namespace Server.Network
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1)
{
int sizeT = TextEncoding.GetByteLengthForEncoding(encoding);
int byteLength = encoding.GetByteLengthForEncoding();
bool isFixedLength = fixedLength > -1;
@ -254,7 +254,7 @@ namespace Server.Network
if (isFixedLength)
{
size = fixedLength * sizeT;
size = fixedLength * byteLength;
if (size > Remaining)
{
throw new OutOfMemoryException();
@ -262,7 +262,7 @@ namespace Server.Network
}
else
{
size = remaining - (remaining & (sizeT - 1));
size = remaining - (remaining & (byteLength - 1));
}
ReadOnlySpan<byte> span;
@ -273,7 +273,7 @@ namespace Server.Network
var firstLength = Math.Min(_first.Length - Position, size);
// Find terminator
index = _first.Slice(Position, firstLength).IndexOfTerminator(sizeT);
index = _first.Slice(Position, firstLength).IndexOfTerminator(byteLength);
if (index < 0)
{
@ -285,7 +285,7 @@ namespace Server.Network
}
else
{
index = _second[..remaining].IndexOfTerminator(sizeT);
index = _second[..remaining].IndexOfTerminator(byteLength);
int secondLength = index < 0 ? remaining : index;
int length = firstLength + secondLength;
@ -295,7 +295,7 @@ namespace Server.Network
_first[Position..].CopyTo(bytes);
_second[..secondLength].CopyTo(bytes[firstLength..]);
Position += length + (index >= 0 ? sizeT : 0);
Position += length + (index >= 0 ? byteLength : 0);
return TextEncoding.GetString(bytes, encoding, safeString);
}
}
@ -306,7 +306,7 @@ namespace Server.Network
{
size = Math.Min(remaining, size);
span = _second.Slice( Position - _first.Length, size);
index = span.IndexOfTerminator(sizeT);
index = span.IndexOfTerminator(byteLength);
if (index >= 0)
{
@ -318,7 +318,7 @@ namespace Server.Network
}
}
Position += isFixedLength ? size : index + sizeT;
Position += isFixedLength ? size : index + byteLength;
return TextEncoding.GetString(span, encoding, safeString);
}

View file

@ -0,0 +1,70 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: PooledArraySpanFormattable.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
#nullable enable
using System;
namespace Server.Buffers;
public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
{
private char[] _arrayToReturnToPool;
private int _pos;
private string _value;
public PooledArraySpanFormattable(char[] arrayToReturnToPool, int length)
{
_arrayToReturnToPool = arrayToReturnToPool;
_pos = length;
_value = null;
}
public ReadOnlySpan<char> Chars => _arrayToReturnToPool.AsSpan(.._pos);
public static implicit operator string(PooledArraySpanFormattable f) => f.ToString();
public string ToString(string? format = null, IFormatProvider formatProvider = null)
{
_value ??= new string(_arrayToReturnToPool.AsSpan(0, _pos));
STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
_arrayToReturnToPool = null;
return _value;
}
public bool TryFormat(
Span<char> destination, out int charsWritten, ReadOnlySpan<char> format = default,
IFormatProvider provider = null
)
{
if (destination.Length < _pos)
{
charsWritten = 0;
return false;
}
_arrayToReturnToPool.AsSpan(0, _pos).CopyTo(destination);
charsWritten = _pos;
return true;
}
public void Dispose()
{
STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
_arrayToReturnToPool = null;
this = default; // Defensive clear
}
}

View file

@ -0,0 +1,600 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.CompilerServices;
namespace Server.Buffers;
/// <summary>Provides a handler to interpolate strings which UNSAFELY exposes it's internal character span.</summary>
[InterpolatedStringHandler]
public ref struct RawInterpolatedStringHandler
{
// Implementation note:
// As this type lives in CompilerServices and is only intended to be targeted by the compiler,
// public APIs eschew argument validation logic in a variety of places, e.g. allowing a null input
// when one isn't expected to produce a NullReferenceException rather than an ArgumentNullException.
/// <summary>Expected average length of formatted data used for an individual interpolation expression result.</summary>
/// <remarks>
/// This is inherited from string.Format, and could be changed based on further data.
/// string.Format actually uses `format.Length + args.Length * 8`, but format.Length
/// includes the format items themselves, e.g. "{0}", and since it's rare to have double-digit
/// numbers of items, we bump the 8 up to 11 to account for the three extra characters in "{d}",
/// since the compiler-provided base length won't include the equivalent character count.
/// </remarks>
private const int GuessedLengthPerHole = 11;
/// <summary>Minimum size array to rent from the pool.</summary>
/// <remarks>Same as stack-allocation size used today by string.Format.</remarks>
private const int MinimumArrayPoolLength = 256;
/// <summary>Optional provider to pass to IFormattable.ToString or ISpanFormattable.TryFormat calls.</summary>
private readonly IFormatProvider? _provider;
/// <summary>Array rented from the array pool and used to back <see cref="_chars"/>.</summary>
private char[]? _arrayToReturnToPool;
/// <summary>The span to write into.</summary>
private Span<char> _chars;
/// <summary>Position at which to write the next character.</summary>
private int _pos;
/// <summary>Whether <see cref="_provider"/> provides an ICustomFormatter.</summary>
/// <remarks>
/// Custom formatters are very rare. We want to support them, but it's ok if we make them more expensive
/// in order to make them as pay-for-play as possible. So, we avoid adding another reference type field
/// to reduce the size of the handler and to reduce required zero'ing, by only storing whether the provider
/// provides a formatter, rather than actually storing the formatter. This in turn means, if there is a
/// formatter, we pay for the extra interface call on each AppendFormatted that needs it.
/// </remarks>
private readonly bool _hasCustomFormatter;
/// <summary>Creates a handler used to translate an interpolated string into a <see cref="string"/>.</summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
/// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
public RawInterpolatedStringHandler(int literalLength, int formattedCount)
{
_provider = null;
_chars = _arrayToReturnToPool = STArrayPool<char>.Shared.Rent(GetDefaultLength(literalLength, formattedCount));
_pos = 0;
_hasCustomFormatter = false;
}
/// <summary>Creates a handler used to translate an interpolated string into a <see cref="string"/>.</summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
/// <param name="provider">An object that supplies culture-specific formatting information.</param>
/// <remarks>This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.</remarks>
public RawInterpolatedStringHandler(int literalLength, int formattedCount, IFormatProvider? provider)
{
_provider = provider;
_chars = _arrayToReturnToPool = STArrayPool<char>.Shared.Rent(GetDefaultLength(literalLength, formattedCount));
_pos = 0;
_hasCustomFormatter = provider is not null && HasCustomFormatter(provider);
}
/// <summary>Derives a default length with which to seed the handler.</summary>
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // becomes a constant when inputs are constant
internal static int GetDefaultLength(int literalLength, int formattedCount) =>
Math.Max(MinimumArrayPoolLength, literalLength + formattedCount * GuessedLengthPerHole);
/// <summary>Clears the handler, returning any rented array to the pool.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // used only on a few hot paths
public void Clear()
{
char[]? toReturn = _arrayToReturnToPool;
this = default; // defensive clear
if (toReturn is not null)
{
STArrayPool<char>.Shared.Return(toReturn);
}
}
/// <summary>Gets a span of the written characters thus far.</summary>
public ReadOnlySpan<char> Text => _chars[.._pos];
/// <summary>Writes the specified string to the handler.</summary>
/// <param name="value">The string to write.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendLiteral(string value)
{
if (value.Length == 1)
{
Span<char> chars = _chars;
int pos = _pos;
if ((uint)pos < (uint)chars.Length)
{
chars[pos] = value[0];
_pos = pos + 1;
}
else
{
GrowThenCopyString(value);
}
return;
}
AppendStringDirect(value);
}
/// <summary>Writes the specified string to the handler.</summary>
/// <param name="value">The string to write.</param>
private void AppendStringDirect(string value)
{
if (value.TryCopyTo(_chars[_pos..]))
{
_pos += value.Length;
}
else
{
GrowThenCopyString(value);
}
}
#region AppendFormatted
// Design note:
// The compiler requires a AppendFormatted overload for anything that might be within an interpolation expression;
// if it can't find an appropriate overload, for handlers in general it'll simply fail to compile.
// (For target-typing to string where it uses DefaultInterpolatedStringHandler implicitly, it'll instead fall back to
// its other mechanisms, e.g. using string.Format. This fallback has the benefit that if we miss a case,
// interpolated strings will still work, but it has the downside that a developer generally won't know
// if the fallback is happening and they're paying more.)
//
// At a minimum, then, we would need an overload that accepts:
// (object value, int alignment = 0, string? format = null)
// Such an overload would provide the same expressiveness as string.Format. However, this has several
// shortcomings:
// - Every value type in an interpolation expression would be boxed.
// - ReadOnlySpan<char> could not be used in interpolation expressions.
// - Every AppendFormatted call would have three arguments at the call site, bloating the IL further.
// - Every invocation would be more expensive, due to lack of specialization, every call needing to account
// for alignment and format, etc.
//
// To address that, we could just have overloads for T and ReadOnlySpan<char>:
// (T)
// (T, int alignment)
// (T, string? format)
// (T, int alignment, string? format)
// (ReadOnlySpan<char>)
// (ReadOnlySpan<char>, int alignment)
// (ReadOnlySpan<char>, string? format)
// (ReadOnlySpan<char>, int alignment, string? format)
// but this also has shortcomings:
// - Some expressions that would have worked with an object overload will now force a fallback to string.Format
// (or fail to compile if the handler is used in places where the fallback isn't provided), because the compiler
// can't always target type to T, e.g. `b switch { true => 1, false => null }` where `b` is a bool can successfully
// be passed as an argument of type `object` but not of type `T`.
// - Reference types get no benefit from going through the generic code paths, and actually incur some overheads
// from doing so.
// - Nullable value types also pay a heavy price, in particular around interface checks that would generally evaporate
// at compile time for value types but don't (currently) if the Nullable<T> goes through the same code paths
// (see https://github.com/dotnet/runtime/issues/50915).
//
// We could try to take a more elaborate approach for DefaultInterpolatedStringHandler, since it is the most common handler
// and we want to minimize overheads both at runtime and in IL size, e.g. have a complete set of overloads for each of:
// (T, ...) where T : struct
// (T?, ...) where T : struct
// (object, ...)
// (ReadOnlySpan<char>, ...)
// (string, ...)
// but this also has shortcomings, most importantly:
// - If you have an unconstrained T that happens to be a value type, it'll now end up getting boxed to use the object overload.
// This also necessitates the T? overload, since nullable value types don't meet a T : struct constraint, so without those
// they'd all map to the object overloads as well.
// - Any reference type with an implicit cast to ROS<char> will fail to compile due to ambiguities between the overloads. string
// is one such type, hence needing dedicated overloads for it that can be bound to more tightly.
//
// A middle ground we've settled on, which is likely to be the right approach for most other handlers as well, would be the set:
// (T, ...) with no constraint
// (ReadOnlySpan<char>) and (ReadOnlySpan<char>, int)
// (object, int alignment = 0, string? format = null)
// (string) and (string, int)
// This would address most of the concerns, at the expense of:
// - Most reference types going through the generic code paths and so being a bit more expensive.
// - Nullable types being more expensive until https://github.com/dotnet/runtime/issues/50915 is addressed.
// We could choose to add a T? where T : struct set of overloads if necessary.
// Strings don't require their own overloads here, but as they're expected to be very common and as we can
// optimize them in several ways (can copy the contents directly, don't need to do any interface checks, don't
// need to pay the shared generic overheads, etc.) we can add overloads specifically to optimize for them.
//
// Hole values are formatted according to the following policy:
// 1. If an IFormatProvider was supplied and it provides an ICustomFormatter, use ICustomFormatter.Format (even if the value is null).
// 2. If the type implements ISpanFormattable, use ISpanFormattable.TryFormat.
// 3. If the type implements IFormattable, use IFormattable.ToString.
// 4. Otherwise, use object.ToString.
// This matches the behavior of string.Format, StringBuilder.AppendFormat, etc. The only overloads for which this doesn't
// apply is ReadOnlySpan<char>, which isn't supported by either string.Format nor StringBuilder.AppendFormat, but more
// importantly which can't be boxed to be passed to ICustomFormatter.Format.
#region AppendFormatted T
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
public void AppendFormatted<T>(T value)
{
// This method could delegate to AppendFormatted with a null format, but explicitly passing
// default as the format to TryFormat helps to improve code quality in some cases when TryFormat is inlined,
// e.g. for Int32 it enables the JIT to eliminate code in the inlined method based on a length check on the format.
// If there's a custom formatter, always use it.
if (_hasCustomFormatter)
{
AppendCustomFormatter(value, format: null);
return;
}
// Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter
// requires the former. For value types, it won't matter as the type checks devolve into
// JIT-time constants. For reference types, they're more likely to implement IFormattable
// than they are to implement ISpanFormattable: if they don't implement either, we save an
// interface check over first checking for ISpanFormattable and then for IFormattable, and
// if it only implements IFormattable, we come out even: only if it implements both do we
// end up paying for an extra interface check.
string? s;
if (value is IFormattable)
{
// If the value can format itself directly into our buffer, do so.
if (value is ISpanFormattable)
{
int charsWritten;
while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, default, _provider)) // constrained call avoiding boxing for value types
{
Grow();
}
_pos += charsWritten;
return;
}
s = ((IFormattable)value).ToString(format: null, _provider); // constrained call avoiding boxing for value types
}
else
{
s = value?.ToString();
}
if (s is not null)
{
AppendStringDirect(s);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted<T>(T value, string? format)
{
// If there's a custom formatter, always use it.
if (_hasCustomFormatter)
{
AppendCustomFormatter(value, format);
return;
}
// Check first for IFormattable, even though we'll prefer to use ISpanFormattable, as the latter
// requires the former. For value types, it won't matter as the type checks devolve into
// JIT-time constants. For reference types, they're more likely to implement IFormattable
// than they are to implement ISpanFormattable: if they don't implement either, we save an
// interface check over first checking for ISpanFormattable and then for IFormattable, and
// if it only implements IFormattable, we come out even: only if it implements both do we
// end up paying for an extra interface check.
string? s;
if (value is IFormattable)
{
// If the value can format itself directly into our buffer, do so.
if (value is ISpanFormattable)
{
int charsWritten;
while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, format, _provider)) // constrained call avoiding boxing for value types
{
Grow();
}
_pos += charsWritten;
return;
}
s = ((IFormattable)value).ToString(format, _provider); // constrained call avoiding boxing for value types
}
else
{
s = value?.ToString();
}
if (s is not null)
{
AppendStringDirect(s);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
public void AppendFormatted<T>(T value, int alignment)
{
int startingPos = _pos;
AppendFormatted(value);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
public void AppendFormatted<T>(T value, int alignment, string? format)
{
int startingPos = _pos;
AppendFormatted(value, format);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
#endregion
#region AppendFormatted ReadOnlySpan<char>
/// <summary>Writes the specified character span to the handler.</summary>
/// <param name="value">The span to write.</param>
public void AppendFormatted(ReadOnlySpan<char> value)
{
// Fast path for when the value fits in the current buffer
if (value.TryCopyTo(_chars[_pos..]))
{
_pos += value.Length;
}
else
{
GrowThenCopySpan(value);
}
}
/// <summary>Writes the specified string of chars to the handler.</summary>
/// <param name="value">The span to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(ReadOnlySpan<char> value, int alignment = 0, string? format = null)
{
bool leftAlign = false;
if (alignment < 0)
{
leftAlign = true;
alignment = -alignment;
}
int paddingRequired = alignment - value.Length;
if (paddingRequired <= 0)
{
// The value is as large or larger than the required amount of padding,
// so just write the value.
AppendFormatted(value);
return;
}
// Write the value along with the appropriate padding.
EnsureCapacityForAdditionalChars(value.Length + paddingRequired);
if (leftAlign)
{
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
_chars.Slice(_pos, paddingRequired).Fill(' ');
_pos += paddingRequired;
}
else
{
_chars.Slice(_pos, paddingRequired).Fill(' ');
_pos += paddingRequired;
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
}
#endregion
#region AppendFormatted string
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
public void AppendFormatted(string? value)
{
// Fast-path for no custom formatter and a non-null string that fits in the current destination buffer.
if (!_hasCustomFormatter && value?.TryCopyTo(_chars[_pos..]) == true)
{
_pos += value.Length;
}
else
{
AppendFormattedSlow(value);
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <remarks>
/// Slow path to handle a custom formatter, potentially null value,
/// or a string that doesn't fit in the current buffer.
/// </remarks>
[MethodImpl(MethodImplOptions.NoInlining)]
private void AppendFormattedSlow(string? value)
{
if (_hasCustomFormatter)
{
AppendCustomFormatter(value, format: null);
}
else if (value is not null)
{
EnsureCapacityForAdditionalChars(value.Length);
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
}
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(string? value, int alignment = 0, string? format = null) =>
// Format is meaningless for strings and doesn't make sense for someone to specify. We have the overload
// simply to disambiguate between ROS<char> and object, just in case someone does specify a format, as
// string is implicitly convertible to both. Just delegate to the T-based implementation.
AppendFormatted<string?>(value, alignment, format);
#endregion
#region AppendFormatted object
/// <summary>Writes the specified value to the handler.</summary>
/// <param name="value">The value to write.</param>
/// <param name="alignment">Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
/// <param name="format">The format string.</param>
public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
// This overload is expected to be used rarely, only if either a) something strongly typed as object is
// formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It
// exists purely to help make cases from (b) compile. Just delegate to the T-based implementation.
AppendFormatted<object?>(value, alignment, format);
#endregion
#endregion
/// <summary>Gets whether the provider provides a custom formatter.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // only used in a few hot path call sites
internal static bool HasCustomFormatter(IFormatProvider provider)
{
Debug.Assert(provider is not null);
Debug.Assert(provider is not CultureInfo || provider.GetFormat(typeof(ICustomFormatter)) is null, "Expected CultureInfo to not provide a custom formatter");
return
provider.GetType() != typeof(CultureInfo) && // optimization to avoid GetFormat in the majority case
provider.GetFormat(typeof(ICustomFormatter)) != null;
}
/// <summary>Formats the value using the custom formatter from the provider.</summary>
/// <param name="value">The value to write.</param>
/// <param name="format">The format string.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void AppendCustomFormatter<T>(T value, string? format)
{
// This case is very rare, but we need to handle it prior to the other checks in case
// a provider was used that supplied an ICustomFormatter which wanted to intercept the particular value.
// We do the cast here rather than in the ctor, even though this could be executed multiple times per
// formatting, to make the cast pay for play.
Debug.Assert(_hasCustomFormatter);
Debug.Assert(_provider != null);
ICustomFormatter? formatter = (ICustomFormatter?)_provider.GetFormat(typeof(ICustomFormatter));
Debug.Assert(formatter != null, "An incorrectly written provider said it implemented ICustomFormatter, and then didn't");
if (formatter?.Format(format, value, _provider) is string customFormatted)
{
AppendStringDirect(customFormatted);
}
}
/// <summary>Handles adding any padding required for aligning a formatted value in an interpolation expression.</summary>
/// <param name="startingPos">The position at which the written value started.</param>
/// <param name="alignment">Non-zero minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.</param>
private void AppendOrInsertAlignmentIfNeeded(int startingPos, int alignment)
{
Debug.Assert(startingPos >= 0 && startingPos <= _pos);
Debug.Assert(alignment != 0);
int charsWritten = _pos - startingPos;
bool leftAlign = false;
if (alignment < 0)
{
leftAlign = true;
alignment = -alignment;
}
int paddingNeeded = alignment - charsWritten;
if (paddingNeeded > 0)
{
EnsureCapacityForAdditionalChars(paddingNeeded);
if (leftAlign)
{
_chars.Slice(_pos, paddingNeeded).Fill(' ');
}
else
{
_chars.Slice(startingPos, charsWritten).CopyTo(_chars[(startingPos + paddingNeeded)..]);
_chars.Slice(startingPos, paddingNeeded).Fill(' ');
}
_pos += paddingNeeded;
}
}
/// <summary>Ensures <see cref="_chars"/> has the capacity to store <paramref name="additionalChars"/> beyond <see cref="_pos"/>.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void EnsureCapacityForAdditionalChars(int additionalChars)
{
if (_chars.Length - _pos < additionalChars)
{
Grow(additionalChars);
}
}
/// <summary>Fallback for fast path in <see cref="AppendStringDirect"/> when there's not enough space in the destination.</summary>
/// <param name="value">The string to write.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowThenCopyString(string value)
{
Grow(value.Length);
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
/// <summary>Fallback for <see cref="AppendFormatted(ReadOnlySpan{char})"/> for when not enough space exists in the current buffer.</summary>
/// <param name="value">The span to write.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowThenCopySpan(ReadOnlySpan<char> value)
{
Grow(value.Length);
value.CopyTo(_chars[_pos..]);
_pos += value.Length;
}
/// <summary>Grows <see cref="_chars"/> to have the capacity to store at least <paramref name="additionalChars"/> beyond <see cref="_pos"/>.</summary>
[MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
private void Grow(int additionalChars)
{
// This method is called when the remaining space (_chars.Length - _pos) is
// insufficient to store a specific number of additional characters. Thus, we
// need to grow to at least that new total. GrowCore will handle growing by more
// than that if possible.
Debug.Assert(additionalChars > _chars.Length - _pos);
GrowCore((uint)_pos + (uint)additionalChars);
}
/// <summary>Grows the size of <see cref="_chars"/>.</summary>
[MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
private void Grow()
{
// This method is called when the remaining space in _chars isn't sufficient to continue
// the operation. Thus, we need at least one character beyond _chars.Length. GrowCore
// will handle growing by more than that if possible.
GrowCore((uint)_chars.Length + 1);
}
/// <summary>Grow the size of <see cref="_chars"/> to at least the specified <paramref name="requiredMinCapacity"/>.</summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] // but reuse this grow logic directly in both of the above grow routines
private void GrowCore(uint requiredMinCapacity)
{
// We want the max of how much space we actually required and doubling our capacity (without going beyond the max allowed length). We
// also want to avoid asking for small arrays, to reduce the number of times we need to grow, and since we're working with unsigned
// ints that could technically overflow if someone tried to, for example, append a huge string to a huge string, we also clamp to int.MaxValue.
// Even if the array creation fails in such a case, we may later fail in ToStringAndClear.
uint newCapacity = Math.Max(requiredMinCapacity, Math.Min((uint)_chars.Length * 2, 0x3FFFFFDF));
int arraySize = (int)Math.Clamp(newCapacity, MinimumArrayPoolLength, int.MaxValue);
char[] newArray = STArrayPool<char>.Shared.Rent(arraySize);
_chars[.._pos].CopyTo(newArray);
char[]? toReturn = _arrayToReturnToPool;
_chars = _arrayToReturnToPool = newArray;
if (toReturn is not null)
{
STArrayPool<char>.Shared.Return(toReturn);
}
}
}

View file

@ -0,0 +1,337 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.Diagnostics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Threading;
namespace Server.Buffers;
/**
* Adaptation of the ArrayPool<T>.Shared (TlsOverPerCoreLockedStacksArrayPool) for single threaded *unsafe* usage.
*/
public class STArrayPool<T> : ArrayPool<T>
{
private const int StackArraySize = 8;
private const int BucketCount = 27; // SelectBucketIndex(1024 * 1024 * 1024 + 1)
private static readonly STArrayPool<T> _shared = new();
public static STArrayPool<T> Shared => _shared;
private int _trimCallbackCreated;
private static STArray[] _cacheBuckets;
private STArrayStack[] _buckets = new STArrayStack[BucketCount];
private STArrayPool() {}
public override T[] Rent(int minimumLength)
{
T[] buffer;
var bucketIndex = SelectBucketIndex(minimumLength);
var cachedBuckets = _cacheBuckets;
if (cachedBuckets is not null && (uint)bucketIndex < (uint)cachedBuckets.Length)
{
buffer = cachedBuckets[bucketIndex].Array;
if (buffer is not null)
{
cachedBuckets[bucketIndex].Array = null;
return buffer;
}
}
var buckets = _buckets;
if ((uint)bucketIndex < (uint)buckets.Length)
{
var b = buckets[bucketIndex];
if (b is not null)
{
buffer = b.TryPop();
if (buffer is not null)
{
return buffer;
}
}
minimumLength = GetMaxSizeForBucket(bucketIndex);
}
if (minimumLength == 0)
{
// We aren't renting.
return Array.Empty<T>();
}
if (minimumLength < 0)
{
throw new ArgumentOutOfRangeException(nameof(minimumLength));
}
buffer = GC.AllocateUninitializedArray<T>(minimumLength);
return buffer;
}
public override void Return(T[] array, bool clearArray = false)
{
if (array is null)
{
return;
}
var bucketIndex = SelectBucketIndex(array.Length);
var cacheBuckets = _cacheBuckets ?? InitializeBuckets();
if ((uint)bucketIndex < (uint)_cacheBuckets!.Length)
{
if (clearArray)
{
Array.Clear(array);
}
if (array.Length != GetMaxSizeForBucket(bucketIndex))
{
throw new ArgumentException("Buffer is not from the pool", nameof(array));
}
ref var bucketArray = ref cacheBuckets[bucketIndex];
var prev = bucketArray.Array;
bucketArray = new STArray(array);
if (prev is not null)
{
var bucket = _buckets[bucketIndex] ?? CreateBucketStack(bucketIndex);
bucket.TryPush(prev);
}
}
}
public void ResetForTesting()
{
if (Core.IsRunningFromXUnit)
{
_cacheBuckets = null;
_buckets = new STArrayStack[BucketCount];
}
}
public bool Trim()
{
var ticks = Core.TickCount;
var pressure = GetMemoryPressure();
var buckets = _buckets;
for (var i = 0; i < buckets.Length; i++)
{
buckets[i]?.Trim(ticks, pressure, GetMaxSizeForBucket(i));
}
if (_cacheBuckets == null)
{
return true;
}
// Under high pressure, release all cached buckets
if (pressure == MemoryPressure.High)
{
Array.Clear(_cacheBuckets);
}
else
{
uint threshold = pressure switch
{
MemoryPressure.Medium => 10000,
_ => 30000,
};
var cacheBuckets = _cacheBuckets;
for (var i = 0; i < cacheBuckets.Length; i++)
{
ref var b = ref cacheBuckets[i];
if (b.Array is null)
{
continue;
}
var lastSeen = b.Ticks;
if (lastSeen == 0)
{
b.Ticks = ticks;
}
else if (ticks - lastSeen >= threshold)
{
b.Array = null;
}
}
}
return true;
}
private STArrayStack CreateBucketStack(int bucketIndex)
{
return _buckets[bucketIndex] = new STArrayStack();
}
private STArray[] InitializeBuckets()
{
Debug.Assert(_cacheBuckets is null, $"Non-null {nameof(_cacheBuckets)}");
var buckets = new STArray[BucketCount];
if (Interlocked.Exchange(ref _trimCallbackCreated, 1) == 0)
{
Gen2GcCallback.Register(o => ((STArrayPool<T>)o).Trim(), this);
}
return _cacheBuckets = buckets;
}
// Buffers are bucketed so that a request between 2^(n-1) + 1 and 2^n is given a buffer of 2^n
// Bucket index is log2(bufferSize - 1) with the exception that buffers between 1 and 16 bytes
// are combined, and the index is slid down by 3 to compensate.
// Zero is a valid bufferSize, and it is assigned the highest bucket index so that zero-length
// buffers are not retained by the pool. The pool will return the Array.Empty singleton for these.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int SelectBucketIndex(int bufferSize) => BitOperations.Log2((uint)bufferSize - 1 | 15) - 3;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static int GetMaxSizeForBucket(int binIndex)
{
int maxSize = 16 << binIndex;
Debug.Assert(maxSize >= 0);
return maxSize;
}
internal enum MemoryPressure
{
Low,
Medium,
High
}
internal static MemoryPressure GetMemoryPressure()
{
GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo();
if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * 0.90)
{
return MemoryPressure.High;
}
if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * 0.70)
{
return MemoryPressure.Medium;
}
return MemoryPressure.Low;
}
private sealed class STArrayStack
{
// Maximum buffers we will store in our stack
private readonly T[][] _arrays = new T[StackArraySize * Environment.ProcessorCount][];
private int _count;
private long _ticks;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPush(T[] array)
{
var arrays = _arrays;
var count = _count;
if ((uint)count < (uint)_arrays.Length)
{
arrays[count] = array;
_count = count + 1;
return true;
}
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T[] TryPop()
{
var arrays = _arrays;
var count = _count - 1;
if ((uint)count < (uint)arrays.Length)
{
var arr = arrays[count];
arrays[count] = null;
_count = count;
return arr;
}
return null;
}
public void Trim(long now, MemoryPressure pressure, int bucketSize)
{
if (_count == 0)
{
return;
}
// 10 seconds under high pressure, otherwise 60 seconds
var threshold = pressure == MemoryPressure.High ? 10000 : 60000;
if (_ticks == 0)
{
_ticks = now;
return;
}
if (now - _ticks <= threshold)
{
return;
}
int trimCount = 1;
switch (pressure)
{
case MemoryPressure.Medium:
{
trimCount = 2;
break;
}
case MemoryPressure.High:
{
if (bucketSize > 16384)
{
trimCount++;
}
var size = Unsafe.SizeOf<T>();
if (size > 32)
{
trimCount += 2;
}
else if (size > 16)
{
trimCount++;
}
break;
}
}
while (_count > 0 && trimCount-- > 0)
{
_arrays[--_count] = null;
}
}
}
private struct STArray
{
public T[] Array;
public long Ticks;
public STArray(T[] array)
{
Array = array;
Ticks = 0;
}
}
}

View file

@ -166,7 +166,7 @@ namespace System.Buffers
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public string ReadString(Encoding encoding, bool safeString = false, int fixedLength = -1)
{
int sizeT = TextEncoding.GetByteLengthForEncoding(encoding);
int byteLength = encoding.GetByteLengthForEncoding();
bool isFixedLength = fixedLength > -1;
@ -174,7 +174,7 @@ namespace System.Buffers
int size;
if (isFixedLength)
{
size = fixedLength * sizeT;
size = fixedLength * byteLength;
if (size > Remaining)
{
throw new OutOfMemoryException();
@ -183,8 +183,8 @@ namespace System.Buffers
else
{
// In case the remaining is not evenly divisible
size = remaining - (remaining & (sizeT - 1));
int index = _buffer.Slice(Position, size).IndexOfTerminator(sizeT);
size = remaining - (remaining & (byteLength - 1));
int index = _buffer.Slice(Position, size).IndexOfTerminator(byteLength);
size = index < 0 ? size : index;
}

View file

@ -1,6 +1,6 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SpanWriter.cs *
* *
@ -14,7 +14,6 @@
*************************************************************************/
using System.Buffers.Binary;
using System.Data;
using System.Diagnostics;
using System.IO;
using System.Runtime.CompilerServices;
@ -22,40 +21,41 @@ using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Toolkit.HighPerformance;
using Server;
using Server.Buffers;
using Server.Text;
namespace System.Buffers
namespace System.Buffers;
public ref struct SpanWriter
{
public ref struct SpanWriter
private readonly bool _resize;
private byte[] _arrayToReturnToPool;
private Span<byte> _buffer;
private int _position;
public int BytesWritten { get; private set; }
public int Position
{
private readonly bool _resize;
private byte[] _arrayToReturnToPool;
private Span<byte> _buffer;
private int _position;
public int BytesWritten { get; private set; }
public int Position
get => _position;
private set
{
get => _position;
private set
{
_position = value;
_position = value;
if (value > BytesWritten)
{
BytesWritten = value;
}
if (value > BytesWritten)
{
BytesWritten = value;
}
}
}
public int Capacity => _buffer.Length;
public int Capacity => _buffer.Length;
public ReadOnlySpan<byte> Span => _buffer[..Position];
public ReadOnlySpan<byte> Span => _buffer[..Position];
public Span<byte> RawBuffer => _buffer;
public Span<byte> RawBuffer => _buffer;
/**
/**
* Converts the writer to a Span<byte> using a SpanOwner.
* If the buffer was stackalloc, it will be copied to a rented buffer.
* Otherwise the existing rented buffer is used.
@ -64,395 +64,425 @@ namespace System.Buffers
* Do not use the SpanWriter after calling this method.
* This method will effectively dispose of the SpanWriter and is therefore considered terminal.
*/
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanOwner ToSpan()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanOwner ToSpan()
{
var toReturn = _arrayToReturnToPool;
SpanOwner apo;
if (_position == 0)
{
var toReturn = _arrayToReturnToPool;
SpanOwner apo;
if (_position == 0)
{
apo = new SpanOwner(_position, Array.Empty<byte>());
if (toReturn != null)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
}
else if (toReturn != null)
{
apo = new SpanOwner(_position, toReturn);
}
else
{
var buffer = ArrayPool<byte>.Shared.Rent(_position);
_buffer.CopyTo(buffer);
apo = new SpanOwner(_position, buffer);
}
this = default; // Don't allow two references to the same buffer
return apo;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(Span<byte> initialBuffer, bool resize = false)
{
_resize = resize;
_buffer = initialBuffer;
_position = 0;
BytesWritten = 0;
_arrayToReturnToPool = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(int initialCapacity, bool resize = false)
{
_resize = resize;
_arrayToReturnToPool = ArrayPool<byte>.Shared.Rent(initialCapacity);
_buffer = _arrayToReturnToPool;
_position = 0;
BytesWritten = 0;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacity)
{
var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2);
byte[] poolArray = ArrayPool<byte>.Shared.Rent(newSize);
_buffer[..BytesWritten].CopyTo(poolArray);
byte[] toReturn = _arrayToReturnToPool;
_buffer = _arrayToReturnToPool = poolArray;
apo = new SpanOwner(_position, Array.Empty<byte>());
if (toReturn != null)
{
ArrayPool<byte>.Shared.Return(toReturn);
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowIfNeeded(int count)
else if (toReturn != null)
{
if (_position + count > _buffer.Length)
apo = new SpanOwner(_position, toReturn);
}
else
{
var buffer = STArrayPool<byte>.Shared.Rent(_position);
_buffer.CopyTo(buffer);
apo = new SpanOwner(_position, buffer);
}
this = default; // Don't allow two references to the same buffer
return apo;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(Span<byte> initialBuffer, bool resize = false)
{
_resize = resize;
_buffer = initialBuffer;
_position = 0;
BytesWritten = 0;
_arrayToReturnToPool = null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SpanWriter(int initialCapacity, bool resize = false)
{
_resize = resize;
_arrayToReturnToPool = STArrayPool<byte>.Shared.Rent(initialCapacity);
_buffer = _arrayToReturnToPool;
_position = 0;
BytesWritten = 0;
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacity)
{
var newSize = Math.Max(BytesWritten + additionalCapacity, _buffer.Length * 2);
byte[] poolArray = STArrayPool<byte>.Shared.Rent(newSize);
_buffer[..BytesWritten].CopyTo(poolArray);
byte[] toReturn = _arrayToReturnToPool;
_buffer = _arrayToReturnToPool = poolArray;
if (toReturn != null)
{
STArrayPool<byte>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void GrowIfNeeded(int count)
{
if (_position + count > _buffer.Length)
{
if (!_resize)
{
if (!_resize)
{
throw new OutOfMemoryException();
}
Grow(count);
}
}
public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(_buffer);
public void EnsureCapacity(int capacity)
{
if (capacity > _buffer.Length)
{
if (!_resize)
{
throw new OutOfMemoryException();
}
Grow(capacity - BytesWritten);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Write(bool value)
{
GrowIfNeeded(1);
_buffer[Position++] = *(byte*)&value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(byte value)
{
GrowIfNeeded(1);
_buffer[Position++] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(sbyte value)
{
GrowIfNeeded(1);
_buffer[Position++] = (byte)value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(Serial serial) => Write(serial.Value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(long value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteUInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> buffer)
{
var count = buffer.Length;
GrowIfNeeded(count);
buffer.CopyTo(_buffer[_position..]);
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(char chr) => Write((byte)chr);
public void WriteString<T>(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable<T>
{
int sizeT = Unsafe.SizeOf<T>();
if (sizeT > 2)
{
throw new InvalidConstraintException("WriteString only accepts byte, sbyte, char, short, and ushort as a constraint");
throw new OutOfMemoryException();
}
value ??= string.Empty;
Grow(count);
}
}
var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length);
var src = value.AsSpan(0, charLength);
public ref byte GetPinnableReference() => ref MemoryMarshal.GetReference(_buffer);
var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value);
if (byteCount == 0)
public void EnsureCapacity(int capacity)
{
if (capacity > _buffer.Length)
{
if (!_resize)
{
return;
throw new OutOfMemoryException();
}
GrowIfNeeded(byteCount);
Grow(capacity - BytesWritten);
}
}
var bytesWritten = encoding.GetBytes(src, _buffer[_position..]);
Position += bytesWritten;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public unsafe void Write(bool value)
{
GrowIfNeeded(1);
_buffer[Position++] = *(byte*)&value;
}
if (fixedLength > -1)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(byte value)
{
GrowIfNeeded(1);
_buffer[Position++] = value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(sbyte value)
{
GrowIfNeeded(1);
_buffer[Position++] = (byte)value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(short value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16BigEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(ushort value)
{
GrowIfNeeded(2);
BinaryPrimitives.WriteUInt16LittleEndian(_buffer[_position..], value);
Position += 2;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(int value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32BigEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(Serial serial) => Write(serial.Value);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLE(uint value)
{
GrowIfNeeded(4);
BinaryPrimitives.WriteUInt32LittleEndian(_buffer[_position..], value);
Position += 4;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(long value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ulong value)
{
GrowIfNeeded(8);
BinaryPrimitives.WriteUInt64BigEndian(_buffer[_position..], value);
Position += 8;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> buffer)
{
var count = buffer.Length;
GrowIfNeeded(count);
buffer.CopyTo(_buffer[_position..]);
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(char chr) => Write((byte)chr);
public void WriteAscii(
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, Encoding.ASCII);
handler.Clear();
}
public void WriteAscii(
IFormatProvider? formatProvider,
[InterpolatedStringHandlerArgument("formatProvider")]
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, Encoding.ASCII);
handler.Clear();
}
public void Write(
Encoding encoding,
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, encoding);
handler.Clear();
}
public void Write(
Encoding encoding,
IFormatProvider? formatProvider,
[InterpolatedStringHandlerArgument("formatProvider")]
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, encoding);
handler.Clear();
}
public void Write(ReadOnlySpan<char> value, Encoding encoding, int fixedLength = -1)
{
var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length);
var src = value[..charLength];
var byteLength = encoding.GetByteLengthForEncoding();
var byteCount = encoding.GetByteCount(src);
if (fixedLength > src.Length)
{
byteCount += (fixedLength - src.Length) * byteLength;
}
if (byteCount == 0)
{
return;
}
GrowIfNeeded(byteCount);
var bytesWritten = encoding.GetBytes(src, _buffer[_position..]);
Position += bytesWritten;
if (fixedLength > -1)
{
var extra = fixedLength * byteLength - bytesWritten;
if (extra > 0)
{
var extra = fixedLength * sizeT - bytesWritten;
if (extra > 0)
{
Clear(extra);
}
Clear(extra);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value) => WriteString<char>(value, TextEncoding.UnicodeLE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value) => Write(value, TextEncoding.UnicodeLE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUniNull(string value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUniNull(string value)
{
Write(value, TextEncoding.UnicodeLE);
Write((ushort)0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value, int fixedLength) => Write(value, TextEncoding.UnicodeLE, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value) => Write(value, TextEncoding.Unicode);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUniNull(string value)
{
Write(value, TextEncoding.Unicode);
Write((ushort)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value, int fixedLength) => Write(value, TextEncoding.Unicode, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8(string value) => Write(value, TextEncoding.UTF8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8Null(string value)
{
Write(value, TextEncoding.UTF8);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value) => Write(value, Encoding.ASCII);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAsciiNull(string value)
{
Write(value, Encoding.ASCII);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value, int fixedLength) => Write(value, Encoding.ASCII, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(int count)
{
GrowIfNeeded(count);
_buffer.Slice(_position, count).Clear();
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Seek(int offset, SeekOrigin origin)
{
Debug.Assert(
origin != SeekOrigin.End || _resize || offset <= 0,
"Attempting to seek to a position beyond capacity using SeekOrigin.End without resize"
);
Debug.Assert(
origin != SeekOrigin.End || offset >= -_buffer.Length,
"Attempting to seek to a negative position using SeekOrigin.End"
);
Debug.Assert(
origin != SeekOrigin.Begin || offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Begin"
);
Debug.Assert(
origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize"
);
Debug.Assert(
origin != SeekOrigin.Current || _position + offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Current"
);
Debug.Assert(
origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize"
);
var newPosition = Math.Max(0, origin switch
{
WriteString<char>(value, TextEncoding.UnicodeLE);
Write((ushort)0);
SeekOrigin.Current => _position + offset,
SeekOrigin.End => BytesWritten + offset,
_ => offset // Begin
});
if (newPosition >= _buffer.Length)
{
Grow(newPosition - _buffer.Length + 1);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value, int fixedLength) => WriteString<char>(value, TextEncoding.UnicodeLE, fixedLength);
return Position = newPosition;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value) => WriteString<char>(value, TextEncoding.Unicode);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUniNull(string value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
byte[] toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
{
WriteString<char>(value, TextEncoding.Unicode);
Write((ushort)0); // '\0'
STArrayPool<byte>.Shared.Return(toReturn);
}
}
public struct SpanOwner : IDisposable
{
private readonly int _length;
private readonly byte[] _arrayToReturnToPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal SpanOwner(int length, byte[] buffer)
{
_length = length;
_arrayToReturnToPool = buffer;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value, int fixedLength) => WriteString<char>(value, TextEncoding.Unicode, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8(string value) => WriteString<byte>(value, TextEncoding.UTF8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8Null(string value)
public Span<byte> Span
{
WriteString<byte>(value, TextEncoding.UTF8);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value) => WriteString<byte>(value, Encoding.ASCII);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAsciiNull(string value)
{
WriteString<byte>(value, Encoding.ASCII);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value, int fixedLength) => WriteString<byte>(value, Encoding.ASCII, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(int count)
{
GrowIfNeeded(count);
_buffer.Slice(_position, count).Clear();
Position += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public int Seek(int offset, SeekOrigin origin)
{
Debug.Assert(
origin != SeekOrigin.End || _resize || offset <= 0,
"Attempting to seek to a position beyond capacity using SeekOrigin.End without resize"
);
Debug.Assert(
origin != SeekOrigin.End || offset >= -_buffer.Length,
"Attempting to seek to a negative position using SeekOrigin.End"
);
Debug.Assert(
origin != SeekOrigin.Begin || offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Begin"
);
Debug.Assert(
origin != SeekOrigin.Begin || _resize || offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Begin without resize"
);
Debug.Assert(
origin != SeekOrigin.Current || _position + offset >= 0,
"Attempting to seek to a negative position using SeekOrigin.Current"
);
Debug.Assert(
origin != SeekOrigin.Current || _resize || _position + offset <= _buffer.Length,
"Attempting to seek to a position beyond the capacity using SeekOrigin.Current without resize"
);
var newPosition = Math.Max(0, origin switch
{
SeekOrigin.Current => _position + offset,
SeekOrigin.End => BytesWritten + offset,
_ => offset // Begin
});
if (newPosition >= _buffer.Length)
{
Grow(newPosition - _buffer.Length + 1);
}
return Position = newPosition;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => MemoryMarshal.CreateSpan(ref _arrayToReturnToPool.DangerousGetReference(), _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
byte[] toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
this = default;
if (_length > 0)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
}
public struct SpanOwner : IDisposable
{
private readonly int _length;
private readonly byte[] _arrayToReturnToPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal SpanOwner(int length, byte[] buffer)
{
_length = length;
_arrayToReturnToPool = buffer;
}
public Span<byte> Span
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => MemoryMarshal.CreateSpan(ref _arrayToReturnToPool.DangerousGetReference(), _length);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
byte[] toReturn = _arrayToReturnToPool;
this = default;
if (_length > 0)
{
ArrayPool<byte>.Shared.Return(toReturn);
}
STArrayPool<byte>.Shared.Return(toReturn);
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,8 +1,8 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2021 - ModernUO Development Team *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SerializableMigration.cs *
* File: ValueStringBuilderExtensions.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
@ -13,20 +13,19 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Collections.Immutable;
using System.Text.Json.Serialization;
using System.Runtime.CompilerServices;
namespace SerializableMigration
namespace Server.Buffers;
public static class ValueStringBuilderExtensions
{
public record SerializableMetadata
// Compiler generated
public static void Append(
this ref ValueStringBuilder stringBuilder,
[InterpolatedStringHandlerArgument("stringBuilder")]
ref ValueStringBuilder.AppendInterpolatedStringHandler handler)
{
[JsonPropertyName("version")]
public int Version { get; init; }
[JsonPropertyName("type")]
public string Type { get; init; }
[JsonPropertyName("properties")]
public ImmutableArray<SerializableProperty>? Properties { get; init; }
// Reassign since the string builder stored on the interpolated handler is by-value
stringBuilder = handler._stringBuilder;
}
}

View file

@ -0,0 +1,262 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: ClientVersion.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Server.Buffers;
namespace Server;
public enum ClientType
{
Regular,
UOTD,
God,
SA
}
public class ClientVersion : IComparable<ClientVersion>, IComparer<ClientVersion>
{
public static readonly ClientVersion Version400a = new("4.0.0a");
public static readonly ClientVersion Version407a = new("4.0.7a");
public static readonly ClientVersion Version500a = new("5.0.0a");
public static readonly ClientVersion Version502b = new("5.0.2b");
public static readonly ClientVersion Version6000 = new("6.0.0.0");
public static readonly ClientVersion Version6017 = new("6.0.1.7");
public static readonly ClientVersion Version60142 = new("6.0.14.2");
public static readonly ClientVersion Version7000 = new("7.0.0.0");
public static readonly ClientVersion Version7090 = new("7.0.9.0");
public static readonly ClientVersion Version70120 = new("7.0.12.0"); // Plant localization change
public static readonly ClientVersion Version70130 = new("7.0.13.0");
public static readonly ClientVersion Version70160 = new("7.0.16.0");
public static readonly ClientVersion Version70300 = new("7.0.30.0");
public static readonly ClientVersion Version70331 = new("7.0.33.1");
public static readonly ClientVersion Version704565 = new("7.0.45.65");
public static readonly ClientVersion Version70500 = new("7.0.50.0");
public static readonly ClientVersion Version70610 = new("7.0.61.0");
public ClientVersion(int maj, int min, int rev, int pat, ClientType type = ClientType.Regular)
{
Major = maj;
Minor = min;
Revision = rev;
Patch = pat;
Type = type;
SourceString = Utility.Intern(ToStringImpl());
}
public ClientVersion(string fmt)
{
fmt = fmt.ToLower();
SourceString = Utility.Intern(fmt);
try
{
var br1 = fmt.IndexOfOrdinal('.');
var br2 = fmt.IndexOf('.', br1 + 1);
var br3 = br2 + 1;
while (br3 < fmt.Length && char.IsDigit(fmt, br3))
{
br3++;
}
Major = Utility.ToInt32(fmt.AsSpan()[..br1]);
Minor = Utility.ToInt32(fmt.AsSpan(br1 + 1, br2 - br1 - 1));
Revision = Utility.ToInt32(fmt.AsSpan(br2 + 1, br3 - br2 - 1));
if (br3 < fmt.Length)
{
if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7
{
if (!char.IsWhiteSpace(fmt, br3))
{
Patch = fmt[br3] - 'a' + 1;
}
}
else
{
Patch = Utility.ToInt32(fmt.AsSpan(br3 + 1, fmt.Length - br3 - 1));
}
}
if (fmt.InsensitiveContains("god") || fmt.InsensitiveContains("gq"))
{
Type = ClientType.God;
}
else if (fmt.InsensitiveContains("third dawn") ||
fmt.InsensitiveContains("uo:td") ||
fmt.InsensitiveContains("uotd") ||
fmt.InsensitiveContains("uo3d") ||
fmt.InsensitiveContains("uo:3d"))
{
Type = ClientType.UOTD;
}
else
{
Type = ClientType.Regular;
}
}
catch
{
Major = 0;
Minor = 0;
Revision = 0;
Patch = 0;
Type = ClientType.Regular;
}
}
public int Major { get; }
public int Minor { get; }
public int Revision { get; }
public int Patch { get; }
public ClientType Type { get; }
public string SourceString { get; }
public int CompareTo(ClientVersion o)
{
if (o == null)
{
return 1;
}
if (Major > o.Major)
{
return 1;
}
if (Major < o.Major)
{
return -1;
}
if (Minor > o.Minor)
{
return 1;
}
if (Minor < o.Minor)
{
return -1;
}
if (Revision > o.Revision)
{
return 1;
}
if (Revision < o.Revision)
{
return -1;
}
if (Patch > o.Patch)
{
return 1;
}
if (Patch < o.Patch)
{
return -1;
}
return 0;
}
int IComparer<ClientVersion>.Compare(ClientVersion x, ClientVersion y) => Compare(x, y);
public static bool operator ==(ClientVersion l, ClientVersion r) => Compare(l, r) == 0;
public static bool operator !=(ClientVersion l, ClientVersion r) => Compare(l, r) != 0;
public static bool operator >=(ClientVersion l, ClientVersion r) => Compare(l, r) >= 0;
public static bool operator >(ClientVersion l, ClientVersion r) => Compare(l, r) > 0;
public static bool operator <=(ClientVersion l, ClientVersion r) => Compare(l, r) <= 0;
public static bool operator <(ClientVersion l, ClientVersion r) => Compare(l, r) < 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(Major, Minor, Revision, Patch, Type);
public override bool Equals(object obj)
{
var v = obj as ClientVersion;
return Major == v?.Major
&& Minor == v.Minor
&& Revision == v.Revision
&& Patch == v.Patch
&& Type == v.Type;
}
private string ToStringImpl()
{
using var builder = new ValueStringBuilder(stackalloc char[32]);
if (Major > 5 || Minor > 0 || Revision > 6)
{
builder.Append($"{Major}.{Minor}.{Revision}.{Patch}");
}
else if (Patch > 0)
{
builder.Append($"{Major}.{Minor}.{Revision}{(char)('a' + (Patch - 1))}");
}
else
{
builder.Append($"{Major}.{Minor}.{Revision}");
}
if (Type == ClientType.UOTD)
{
builder.Append(" uotd");
}
return builder.ToString();
}
public override string ToString() => SourceString;
public static bool IsNull(object x) => ReferenceEquals(x, null);
public static int Compare(ClientVersion a, ClientVersion b)
{
if (IsNull(a) && IsNull(b))
{
return 0;
}
if (IsNull(a))
{
return -1;
}
if (IsNull(b))
{
return 1;
}
return a.CompareTo(b);
}
}

View file

@ -0,0 +1,135 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2022 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: UOClient.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers.Binary;
using System.IO;
using System.Text.Json.Serialization;
using Server.Json;
using Server.Logging;
namespace Server;
public static class UOClient
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(UOClient));
private static bool _automaticallyDetected;
public static CUOSettings CuoSettings { get; private set; }
public static ClientVersion ServerClientVersion { get; private set; }
public static void Load()
{
ServerClientVersion = ServerConfiguration.GetSetting("clientData.clientVersion", (ClientVersion)null);
if (ServerClientVersion == null)
{
ServerClientVersion = DetectCUOClient() ?? DetectClassicClient();
_automaticallyDetected = true;
}
}
public static void Configure()
{
if (ServerClientVersion == null)
{
logger.Warning("Could not detect client version. This may cause data files to load improperly.");
return;
}
if (_automaticallyDetected)
{
logger.Information(
CuoSettings?.ClientVersion == ServerClientVersion
? "Automatically detected client version {ServerClientVersion} from CUO settings."
: "Automatically detected client version {ServerClientVersion}",
ServerClientVersion
);
return;
}
logger.Information("Manually configured to use client version {ServerClientVersion}", ServerClientVersion);
}
private static ClientVersion DetectCUOClient()
{
var path = Core.FindDataFile("settings.json", false);
if (File.Exists(path))
{
var settings = JsonConfig.Deserialize<CUOSettings>(path);
var file = new FileInfo(path);
if (settings.UltimaOnlineDirectory != null)
{
settings.UltimaOnlineDirectory = PathUtility.GetFullPath(settings.UltimaOnlineDirectory, file.DirectoryName);
if (Directory.Exists(settings.UltimaOnlineDirectory))
{
CuoSettings = settings;
}
}
return settings.ClientVersion;
}
return null;
}
private static ClientVersion DetectClassicClient()
{
var path = Core.FindDataFile("client.exe", false);
if (File.Exists(path))
{
using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
var buffer = GC.AllocateUninitializedArray<byte>((int)fs.Length, true);
fs.Read(buffer);
// VS_VERSION_INFO (unicode)
Span<byte> vsVersionInfo = stackalloc byte[]
{
0x56, 0x00, 0x53, 0x00, 0x5F, 0x00, 0x56, 0x00,
0x45, 0x00, 0x52, 0x00, 0x53, 0x00, 0x49, 0x00,
0x4F, 0x00, 0x4E, 0x00, 0x5F, 0x00, 0x49, 0x00,
0x4E, 0x00, 0x46, 0x00, 0x4F, 0x00
};
for (var i = 0; i < buffer.Length; i++)
{
if (vsVersionInfo.SequenceEqual(buffer.AsSpan(i, 30)))
{
var offset = i + 42; // 30 + 12
var minorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset));
var majorPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 2));
var privatePart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 4));
var buildPart = BinaryPrimitives.ReadUInt16LittleEndian(buffer.AsSpan(offset + 6));
return new ClientVersion(majorPart, minorPart, buildPart, privatePart);
}
}
}
return null;
}
public record CUOSettings
{
[JsonPropertyName("clientversion")]
public ClientVersion ClientVersion { get; set; }
[JsonPropertyName("ultimaonlinedirectory")]
public string UltimaOnlineDirectory { get; set; }
}
}

View file

@ -1,237 +0,0 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Server.Buffers;
namespace Server
{
public enum ClientType
{
Regular,
UOTD,
God,
SA
}
public class ClientVersion : IComparable<ClientVersion>, IComparer<ClientVersion>
{
public ClientVersion(int maj, int min, int rev, int pat, ClientType type = ClientType.Regular)
{
Major = maj;
Minor = min;
Revision = rev;
Patch = pat;
Type = type;
SourceString = Utility.Intern(ToStringImpl());
}
public ClientVersion(string fmt)
{
fmt = fmt.ToLower();
SourceString = Utility.Intern(fmt);
try
{
var br1 = fmt.IndexOfOrdinal('.');
var br2 = fmt.IndexOf('.', br1 + 1);
var br3 = br2 + 1;
while (br3 < fmt.Length && char.IsDigit(fmt, br3))
{
br3++;
}
Major = Utility.ToInt32(fmt.AsSpan()[..br1]);
Minor = Utility.ToInt32(fmt.Substring(br1 + 1, br2 - br1 - 1));
Revision = Utility.ToInt32(fmt.Substring(br2 + 1, br3 - br2 - 1));
if (br3 < fmt.Length)
{
if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7
{
if (!char.IsWhiteSpace(fmt, br3))
{
Patch = fmt[br3] - 'a' + 1;
}
}
else
{
Patch = Utility.ToInt32(fmt.Substring(br3 + 1, fmt.Length - br3 - 1));
}
}
if (fmt.InsensitiveContains("god") || fmt.InsensitiveContains("gq"))
{
Type = ClientType.God;
}
else if (fmt.InsensitiveContains("third dawn") ||
fmt.InsensitiveContains("uo:td") ||
fmt.InsensitiveContains("uotd") ||
fmt.InsensitiveContains("uo3d") ||
fmt.InsensitiveContains("uo:3d"))
{
Type = ClientType.UOTD;
}
else
{
Type = ClientType.Regular;
}
}
catch
{
Major = 0;
Minor = 0;
Revision = 0;
Patch = 0;
Type = ClientType.Regular;
}
}
public int Major { get; }
public int Minor { get; }
public int Revision { get; }
public int Patch { get; }
public ClientType Type { get; }
public string SourceString { get; }
public int CompareTo(ClientVersion o)
{
if (o == null)
{
return 1;
}
if (Major > o.Major)
{
return 1;
}
if (Major < o.Major)
{
return -1;
}
if (Minor > o.Minor)
{
return 1;
}
if (Minor < o.Minor)
{
return -1;
}
if (Revision > o.Revision)
{
return 1;
}
if (Revision < o.Revision)
{
return -1;
}
if (Patch > o.Patch)
{
return 1;
}
if (Patch < o.Patch)
{
return -1;
}
return 0;
}
int IComparer<ClientVersion>.Compare(ClientVersion x, ClientVersion y) => Compare(x, y);
public static bool operator ==(ClientVersion l, ClientVersion r) => Compare(l, r) == 0;
public static bool operator !=(ClientVersion l, ClientVersion r) => Compare(l, r) != 0;
public static bool operator >=(ClientVersion l, ClientVersion r) => Compare(l, r) >= 0;
public static bool operator >(ClientVersion l, ClientVersion r) => Compare(l, r) > 0;
public static bool operator <=(ClientVersion l, ClientVersion r) => Compare(l, r) <= 0;
public static bool operator <(ClientVersion l, ClientVersion r) => Compare(l, r) < 0;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override int GetHashCode() => HashCode.Combine(Major, Minor, Revision, Patch, Type);
public override bool Equals(object obj)
{
var v = obj as ClientVersion;
return Major == v?.Major
&& Minor == v.Minor
&& Revision == v.Revision
&& Patch == v.Patch
&& Type == v.Type;
}
private string ToStringImpl()
{
using var builder = new ValueStringBuilder(stackalloc char[32]);
builder.Append(Major.ToString());
builder.Append('.');
builder.Append(Minor.ToString());
builder.Append('.');
builder.Append(Revision.ToString());
if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7
{
if (Patch > 0)
{
builder.Append((char)('a' + (Patch - 1)));
}
}
else
{
builder.Append('.');
builder.Append(Patch.ToString());
}
if (Type != ClientType.Regular)
{
builder.Append(' ');
builder.Append(Type.ToString().ToLower());
}
return builder.ToString();
}
public override string ToString() => SourceString;
public static bool IsNull(object x) => ReferenceEquals(x, null);
public static int Compare(ClientVersion a, ClientVersion b)
{
if (IsNull(a) && IsNull(b))
{
return 0;
}
if (IsNull(a))
{
return -1;
}
if (IsNull(b))
{
return 1;
}
return a.CompareTo(b);
}
}
}

View file

@ -15,7 +15,7 @@ namespace Server.Collections;
// A vector of bits. Use this to store bits efficiently, without having to do bit
// shifting yourself.
[System.Serializable]
[Serializable]
public sealed class BitArray : ICollection, ICloneable
{
private int[] m_array; // Do not rename (binary serialization)
@ -63,7 +63,7 @@ public sealed class BitArray : ICollection, ICloneable
**
** Exceptions: ArgumentException if bytes == null.
=========================================================================*/
public BitArray(byte[] bytes)
public BitArray(ReadOnlySpan<byte> bytes, int length = -1)
{
if (bytes == null)
{
@ -79,7 +79,7 @@ public sealed class BitArray : ICollection, ICloneable
}
m_array = new int[GetInt32ArrayLengthFromByteLength(bytes.Length)];
m_length = bytes.Length * BitsPerByte;
m_length = length == -1 ? bytes.Length * BitsPerByte : length;
uint totalCount = (uint)bytes.Length / 4;
@ -96,16 +96,22 @@ public sealed class BitArray : ICollection, ICloneable
switch (byteSpan.Length)
{
case 3:
last = byteSpan[2] << 16;
goto case 2;
{
last = byteSpan[2] << 16;
goto case 2;
}
// fall through
case 2:
last |= byteSpan[1] << 8;
goto case 1;
{
last |= byteSpan[1] << 8;
goto case 1;
}
// fall through
case 1:
m_array[totalCount] = last | byteSpan[0];
break;
{
m_array[totalCount] = last | byteSpan[0];
break;
}
}
_version = 0;
@ -119,63 +125,7 @@ public sealed class BitArray : ICollection, ICloneable
**
** Exceptions: ArgumentException if bytes == null.
=========================================================================*/
public BitArray(ReadOnlySpan<byte> bytes)
{
if (bytes == null)
{
throw new ArgumentNullException(nameof(bytes));
}
// this value is chosen to prevent overflow when computing m_length.
// m_length is of type int32 and is exposed as a property, so
// type of m_length can't be changed to accommodate.
if (bytes.Length > int.MaxValue / BitsPerByte)
{
throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_ArrayTooLarge, BitsPerByte), nameof(bytes));
}
m_array = new int[GetInt32ArrayLengthFromByteLength(bytes.Length)];
m_length = bytes.Length * BitsPerByte;
uint totalCount = (uint)bytes.Length / 4;
ReadOnlySpan<byte> byteSpan = bytes;
for (int i = 0; i < totalCount; i++)
{
m_array[i] = BinaryPrimitives.ReadInt32LittleEndian(byteSpan);
byteSpan = byteSpan[4..];
}
Debug.Assert(byteSpan.Length >= 0 && byteSpan.Length < 4);
int last = 0;
switch (byteSpan.Length)
{
case 3:
last = byteSpan[2] << 16;
goto case 2;
// fall through
case 2:
last |= byteSpan[1] << 8;
goto case 1;
// fall through
case 1:
m_array[totalCount] = last | byteSpan[0];
break;
}
_version = 0;
}
/*=========================================================================
** Allocates space to hold the bit values in bytes. bytes[0] represents
** bits 0 - 7, bytes[1] represents bits 8 - 15, etc. The LSB of each byte
** represents the lowest index value; bytes[0] & 1 represents bit 0,
** bytes[0] & 2 represents bit 1, bytes[0] & 4 represents bit 2, etc.
**
** Exceptions: ArgumentException if bytes == null.
=========================================================================*/
public BitArray(BinaryReader reader, int length)
public BitArray(BinaryReader reader, int bitLength)
{
if (reader == null)
{
@ -185,13 +135,15 @@ public sealed class BitArray : ICollection, ICloneable
// this value is chosen to prevent overflow when computing m_length.
// m_length is of type int32 and is exposed as a property, so
// type of m_length can't be changed to accommodate.
if (length > int.MaxValue / BitsPerByte)
if (bitLength > int.MaxValue / BitsPerByte)
{
throw new ArgumentException(string.Format(CollectionThrowStrings.Argument_ArrayTooLarge, BitsPerByte), nameof(reader));
}
var length = GetByteArrayLengthFromBitLength(bitLength);
m_array = new int[GetInt32ArrayLengthFromByteLength(length)];
m_length = length * BitsPerByte;
m_length = length;
uint totalCount = (uint)length / 4;
@ -207,16 +159,22 @@ public sealed class BitArray : ICollection, ICloneable
switch (length)
{
case 3:
last = reader.ReadInt16();
goto case 2;
{
last = reader.ReadInt16();
goto case 2;
}
// fall through
case 2:
last |= reader.ReadByte();
goto case 1;
{
last |= reader.ReadByte();
goto case 1;
}
// fall through
case 1:
m_array[totalCount] = last | reader.ReadByte();
break;
{
m_array[totalCount] = last | reader.ReadByte();
break;
}
}
_version = 0;
@ -499,14 +457,38 @@ public sealed class BitArray : ICollection, ICloneable
// Unroll loop for count less than Vector256 size.
switch (count)
{
case 7: thisArray[6] &= valueArray[6]; goto case 6;
case 6: thisArray[5] &= valueArray[5]; goto case 5;
case 5: thisArray[4] &= valueArray[4]; goto case 4;
case 4: thisArray[3] &= valueArray[3]; goto case 3;
case 3: thisArray[2] &= valueArray[2]; goto case 2;
case 2: thisArray[1] &= valueArray[1]; goto case 1;
case 1: thisArray[0] &= valueArray[0]; goto Done;
case 0: goto Done;
case 7:
{
thisArray[6] &= valueArray[6]; goto case 6;
}
case 6:
{
thisArray[5] &= valueArray[5]; goto case 5;
}
case 5:
{
thisArray[4] &= valueArray[4]; goto case 4;
}
case 4:
{
thisArray[3] &= valueArray[3]; goto case 3;
}
case 3:
{
thisArray[2] &= valueArray[2]; goto case 2;
}
case 2:
{
thisArray[1] &= valueArray[1]; goto case 1;
}
case 1:
{
thisArray[0] &= valueArray[0]; goto Done;
}
case 0:
{
goto Done;
}
}
uint i = 0;
@ -597,14 +579,38 @@ public sealed class BitArray : ICollection, ICloneable
// Unroll loop for count less than Vector256 size.
switch (count)
{
case 7: thisArray[6] |= valueArray[6]; goto case 6;
case 6: thisArray[5] |= valueArray[5]; goto case 5;
case 5: thisArray[4] |= valueArray[4]; goto case 4;
case 4: thisArray[3] |= valueArray[3]; goto case 3;
case 3: thisArray[2] |= valueArray[2]; goto case 2;
case 2: thisArray[1] |= valueArray[1]; goto case 1;
case 1: thisArray[0] |= valueArray[0]; goto Done;
case 0: goto Done;
case 7:
{
thisArray[6] |= valueArray[6]; goto case 6;
}
case 6:
{
thisArray[5] |= valueArray[5]; goto case 5;
}
case 5:
{
thisArray[4] |= valueArray[4]; goto case 4;
}
case 4:
{
thisArray[3] |= valueArray[3]; goto case 3;
}
case 3:
{
thisArray[2] |= valueArray[2]; goto case 2;
}
case 2:
{
thisArray[1] |= valueArray[1]; goto case 1;
}
case 1:
{
thisArray[0] |= valueArray[0]; goto Done;
}
case 0:
{
goto Done;
}
}
uint i = 0;
@ -695,14 +701,38 @@ public sealed class BitArray : ICollection, ICloneable
// Unroll loop for count less than Vector256 size.
switch (count)
{
case 7: thisArray[6] ^= valueArray[6]; goto case 6;
case 6: thisArray[5] ^= valueArray[5]; goto case 5;
case 5: thisArray[4] ^= valueArray[4]; goto case 4;
case 4: thisArray[3] ^= valueArray[3]; goto case 3;
case 3: thisArray[2] ^= valueArray[2]; goto case 2;
case 2: thisArray[1] ^= valueArray[1]; goto case 1;
case 1: thisArray[0] ^= valueArray[0]; goto Done;
case 0: goto Done;
case 7:
{
thisArray[6] ^= valueArray[6]; goto case 6;
}
case 6:
{
thisArray[5] ^= valueArray[5]; goto case 5;
}
case 5:
{
thisArray[4] ^= valueArray[4]; goto case 4;
}
case 4:
{
thisArray[3] ^= valueArray[3]; goto case 3;
}
case 3:
{
thisArray[2] ^= valueArray[2]; goto case 2;
}
case 2:
{
thisArray[1] ^= valueArray[1]; goto case 1;
}
case 1:
{
thisArray[0] ^= valueArray[0]; goto Done;
}
case 0:
{
goto Done;
}
}
uint i = 0;
@ -781,14 +811,38 @@ public sealed class BitArray : ICollection, ICloneable
// Unroll loop for count less than Vector256 size.
switch (count)
{
case 7: thisArray[6] = ~thisArray[6]; goto case 6;
case 6: thisArray[5] = ~thisArray[5]; goto case 5;
case 5: thisArray[4] = ~thisArray[4]; goto case 4;
case 4: thisArray[3] = ~thisArray[3]; goto case 3;
case 3: thisArray[2] = ~thisArray[2]; goto case 2;
case 2: thisArray[1] = ~thisArray[1]; goto case 1;
case 1: thisArray[0] = ~thisArray[0]; goto Done;
case 0: goto Done;
case 7:
{
thisArray[6] = ~thisArray[6]; goto case 6;
}
case 6:
{
thisArray[5] = ~thisArray[5]; goto case 5;
}
case 5:
{
thisArray[4] = ~thisArray[4]; goto case 4;
}
case 4:
{
thisArray[3] = ~thisArray[3]; goto case 3;
}
case 3:
{
thisArray[2] = ~thisArray[2]; goto case 2;
}
case 2:
{
thisArray[1] = ~thisArray[1]; goto case 1;
}
case 1:
{
thisArray[0] = ~thisArray[0]; goto Done;
}
case 0:
{
goto Done;
}
}
uint i = 0;
@ -964,10 +1018,7 @@ public sealed class BitArray : ICollection, ICloneable
public int Length
{
get
{
return m_length;
}
get => m_length;
set
{
if (value < 0)
@ -1035,16 +1086,22 @@ public sealed class BitArray : ICollection, ICloneable
switch (remainder)
{
case 3:
span[2] = (byte)(m_array[quotient] >> 16);
goto case 2;
{
span[2] = (byte)(m_array[quotient] >> 16);
goto case 2;
}
// fall through
case 2:
span[1] = (byte)(m_array[quotient] >> 8);
goto case 1;
{
span[1] = (byte)(m_array[quotient] >> 8);
goto case 1;
}
// fall through
case 1:
span[0] = (byte)m_array[quotient];
break;
{
span[0] = (byte)m_array[quotient];
break;
}
}
}
@ -1084,54 +1141,6 @@ public sealed class BitArray : ICollection, ICloneable
intArray[index + last] = m_array[last] & unchecked((1 << extraBits) - 1);
}
}
else if (array is byte[] byteArray)
{
int arrayLength = GetByteArrayLengthFromBitLength(m_length);
if (array.Length - index < arrayLength)
{
throw new ArgumentException(CollectionThrowStrings.Argument_InvalidOffLen);
}
// equivalent to m_length % BitsPerByte, since BitsPerByte is a power of 2
uint extraBits = (uint)m_length & (BitsPerByte - 1);
if (extraBits > 0)
{
// last byte is not aligned, we will directly copy one less byte
arrayLength -= 1;
}
Span<byte> span = byteArray.AsSpan(index);
int quotient = Div4Rem(arrayLength, out int remainder);
for (int i = 0; i < quotient; i++)
{
BinaryPrimitives.WriteInt32LittleEndian(span, m_array[i]);
span = span[4..];
}
if (extraBits > 0)
{
Debug.Assert(span.Length > 0);
Debug.Assert(m_array.Length > quotient);
// mask the final byte
span[remainder] = (byte)((m_array[quotient] >> (remainder * 8)) & ((1 << (int)extraBits) - 1));
}
switch (remainder)
{
case 3:
span[2] = (byte)(m_array[quotient] >> 16);
goto case 2;
// fall through
case 2:
span[1] = (byte)(m_array[quotient] >> 8);
goto case 1;
// fall through
case 1:
span[0] = (byte)m_array[quotient];
break;
}
}
else if (array is bool[] boolArray)
{
if (array.Length - index < m_length)

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