Skip to content

Commit

Permalink
Improve morph algorithm
Browse files Browse the repository at this point in the history
- Add benchmack to verify if new algorithm is faster or slower.
  • Loading branch information
niktekusho committed Jun 29, 2024
1 parent 8983d50 commit 9aa467f
Show file tree
Hide file tree
Showing 8 changed files with 637 additions and 12 deletions.
38 changes: 38 additions & 0 deletions src/morph/benchmark/benchmark.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { join } from "node:path";
import { test } from "vitest";
import { validateActionInstance } from "../actions";
import { validateRule } from "../rule";
import { Ruleset, applyRuleset, applyRulesetNew } from "../ruleset";
import { GOOD } from "@/good/good_spec";

test("validate 10.000 action instances", () => {
const jsonFile = readFileSync(
Expand Down Expand Up @@ -49,3 +51,39 @@ test("validate 10.000 rules", () => {
console.timeEnd("validation of 10.000 rules");
console.log(`Found ${foundErrs} errors`);
});

test("morph 10.000 artifacts with 100 rules", () => {
const rulesetFile = readFileSync(
join(import.meta.dirname, "ruleset-apply-rules-bench-data.json"),
"utf8"
);
const ruleset = JSON.parse(rulesetFile) as Ruleset;

const goodFile = readFileSync(
join(import.meta.dirname, "ruleset-good-bench-data.json"),
"utf8"
);

const good = JSON.parse(goodFile) as GOOD;

const oldStart = Date.now();
applyRuleset(ruleset, good);
const oldEnd = Date.now();

const newStart = Date.now();
applyRulesetNew(ruleset, good);
const newEnd = Date.now();

const oldTime = oldEnd - oldStart;
const newTime = newEnd - newStart;

const speedup = (Math.max(newTime, oldTime)) / Math.min(newTime, oldTime);
const stats = {
oldTime,
newTime,
fastest: newTime < oldTime ? 'new' : 'old',
speedup: `${speedup.toFixed(2)}x`
}
console.table(stats);

});
297 changes: 297 additions & 0 deletions src/morph/benchmark/ruleset-apply-rules-bench-data-gen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
// @ts-check

// TODO: use objects from good_spec

const allLocationCharacterKeys = [
"Albedo",
"Alhaitham",
"Aloy",
"Amber",
"AratakiItto",
"Baizhu",
"Barbara",
"Beidou",
"Bennett",
"Candace",
"Charlotte",
"Chevreuse",
"Chiori",
"Chongyun",
"Collei",
"Cyno",
"Dehya",
"Diluc",
"Diona",
"Dori",
"Eula",
"Faruzan",
"Fischl",
"Freminet",
"Furina",
"Gaming",
"Ganyu",
"Gorou",
"HuTao",
"Jean",
"KaedeharaKazuha",
"Kaeya",
"KamisatoAyaka",
"KamisatoAyato",
"Kaveh",
"Keqing",
"Kirara",
"Klee",
"KujouSara",
"KukiShinobu",
"Layla",
"Lisa",
"Lynette",
"Lyney",
"Mika",
"Mona",
"Nahida",
"Navia",
"Neuvillette",
"Nilou",
"Ningguang",
"Noelle",
"Qiqi",
"RaidenShogun",
"Razor",
"Rosaria",
"SangonomiyaKokomi",
"Sayu",
"Shenhe",
"ShikanoinHeizou",
"Sucrose",
"Tartaglia",
"Thoma",
"Tighnari",
"Venti",
"Wanderer",
"Wriothesley",
"Xiangling",
"Xianyun",
"Xiao",
"Xingqiu",
"Xinyan",
"YaeMiko",
"Yanfei",
"Yaoyao",
"Yelan",
"Yoimiya",
"YunJin",
"Zhongli",
"Traveler",
]

const artifactSandsStatKeys = [
"hp_",
"def_",
"atk_",
"eleMas",
"enerRech_",
]

const artifactGobletStatKeys = [
"hp_",
"def_",
"atk_",
"eleMas",
"physical_dmg_",
"anemo_dmg_",
"geo_dmg_",
"electro_dmg_",
"hydro_dmg_",
"pyro_dmg_",
"cryo_dmg_",
"dendro_dmg_",
]

const artifactCircletStatKeys = [
"hp_",
"def_",
"atk_",
"eleMas",
"critRate_",
"critDMG_",
"heal_",
]

const allArtifactSetKeys = [
"Adventurer",
"ArchaicPetra",
"Berserker",
"BlizzardStrayer",
"BloodstainedChivalry",
"BraveHeart",
"CrimsonWitchOfFlames",
"DeepwoodMemories",
"DefendersWill",
"DesertPavilionChronicle",
"EchoesOfAnOffering",
"EmblemOfSeveredFate",
"FlowerOfParadiseLost",
"Gambler",
"GildedDreams",
"GladiatorsFinale",
"GoldenTroupe",
"HeartOfDepth",
"HuskOfOpulentDreams",
"Instructor",
"Lavawalker",
"LuckyDog",
"MaidenBeloved",
"MarechausseeHunter",
"MartialArtist",
"NighttimeWhispersInTheEchoingWoods",
"NoblesseOblige",
"NymphsDream",
"OceanHuedClam",
"PaleFlame",
"PrayersForDestiny",
"PrayersForIllumination",
"PrayersForWisdom",
"PrayersToSpringtime",
"ResolutionOfSojourner",
"RetracingBolide",
"Scholar",
"ShimenawasReminiscence",
"SongOfDaysPast",
"TenacityOfTheMillelith",
"TheExile",
"ThunderingFury",
"Thundersoother",
"TinyMiracle",
"TravelingDoctor",
"VermillionHereafter",
"ViridescentVenerer",
"VourukashasGlow",
"WanderersTroupe",
]

const allArtifactSlotKeys = [
"flower",
"plume",
"sands",
"goblet",
"circlet",
]

function random(max) {
return Math.floor(Math.random() * max)
}

function selectRandomCharacter(excludedCharacters = []) {
const charactersToPick = allLocationCharacterKeys.filter(char => !excludedCharacters.includes(char));
const randomIdx = random(charactersToPick.length);
return charactersToPick[randomIdx];
}

function createRandomArtifact(artifactId) {

const setKey = allArtifactSetKeys[random(allArtifactSetKeys.length)]

const level = random(20)

const slotKey = allArtifactSlotKeys[random(allArtifactSlotKeys.length)]

let mainStatKey;
switch (slotKey) {
case 'flower':
mainStatKey = 'hp';
break;
case 'plume':
mainStatKey = 'atk';
break;
case 'sands':
mainStatKey = artifactSandsStatKeys[random(artifactSandsStatKeys.length)]
break;
case 'goblet':
mainStatKey = artifactGobletStatKeys[random(artifactGobletStatKeys.length)]
break;
case 'circlet':
mainStatKey = artifactCircletStatKeys[random(artifactCircletStatKeys.length)]
break;
default:
throw new Error(`SlotKey not found: ${slotKey}`)
}

const location = selectRandomCharacter()

return {
"setKey": setKey,
"rarity": 5,
"level": level,
"slotKey": slotKey,
"mainStatKey": mainStatKey,
"substats": [
// { "key": "hp_", "value": 4.7 },
// { "key": "critDMG_", "value": 14.8 },
// { "key": "hp", "value": 538 },
// { "key": "critRate_", "value": 14.4 }
],
"location": location,
"lock": true,
"id": `artifact_${artifactId}`
}
}

// Function to generate random objects
function generateRandomRule(id) {
const actionType = Math.random() < 0.5 ? "equip" : "unequip";
const filterCharacterName = selectRandomCharacter();

const obj = {
id,
action:
actionType === "equip"
? { type: "equip", to: selectRandomCharacter([filterCharacterName]) }
: { type: "unequip" },
filter: {
type: "equippingCharacter",
characterName: filterCharacterName,
},
};

return obj;
}

// Generate 100 rules
const rulesToGenerate = 100
const rules = [];
for (let i = 0; i < rulesToGenerate; i++) {
rules.push(generateRandomRule(i));
}

const ruleset = {
name: 'bench ruleset',
rules
}

import { writeFileSync } from "node:fs";
import { join } from "node:path";

const rulesetFilePath = join(import.meta.dirname, "ruleset-apply-rules-bench-data.json");

writeFileSync(rulesetFilePath, JSON.stringify(ruleset));

// Generate 10.000 artifacts
const artifactsToGenerate = 10_000;

const artifacts = []
for (let i = 0; i < artifactsToGenerate; i++) {
artifacts.push(createRandomArtifact(i));
}

const goodFile = {
format: "GOOD",
dbVersion: 1,
source: "Bench data",
version: 1,
artifacts,
}

const goodFilePath = join(import.meta.dirname, "ruleset-good-bench-data.json");

writeFileSync(goodFilePath, JSON.stringify(goodFile));
1 change: 1 addition & 0 deletions src/morph/benchmark/ruleset-apply-rules-bench-data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"bench ruleset","rules":[{"id":0,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Thoma"}},{"id":1,"action":{"type":"equip","to":"Sayu"},"filter":{"type":"equippingCharacter","characterName":"Bennett"}},{"id":2,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Xinyan"}},{"id":3,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Yoimiya"}},{"id":4,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Zhongli"}},{"id":5,"action":{"type":"equip","to":"Jean"},"filter":{"type":"equippingCharacter","characterName":"Yaoyao"}},{"id":6,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Tartaglia"}},{"id":7,"action":{"type":"equip","to":"Aloy"},"filter":{"type":"equippingCharacter","characterName":"Tighnari"}},{"id":8,"action":{"type":"equip","to":"Xingqiu"},"filter":{"type":"equippingCharacter","characterName":"Sayu"}},{"id":9,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Wriothesley"}},{"id":10,"action":{"type":"equip","to":"Lyney"},"filter":{"type":"equippingCharacter","characterName":"Jean"}},{"id":11,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Chongyun"}},{"id":12,"action":{"type":"equip","to":"Mika"},"filter":{"type":"equippingCharacter","characterName":"Tartaglia"}},{"id":13,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"KaedeharaKazuha"}},{"id":14,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Aloy"}},{"id":15,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Alhaitham"}},{"id":16,"action":{"type":"equip","to":"Mona"},"filter":{"type":"equippingCharacter","characterName":"Collei"}},{"id":17,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Razor"}},{"id":18,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Yelan"}},{"id":19,"action":{"type":"equip","to":"Klee"},"filter":{"type":"equippingCharacter","characterName":"Baizhu"}},{"id":20,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Klee"}},{"id":21,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Ningguang"}},{"id":22,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Wanderer"}},{"id":23,"action":{"type":"equip","to":"Sucrose"},"filter":{"type":"equippingCharacter","characterName":"Thoma"}},{"id":24,"action":{"type":"equip","to":"KamisatoAyaka"},"filter":{"type":"equippingCharacter","characterName":"Fischl"}},{"id":25,"action":{"type":"equip","to":"Thoma"},"filter":{"type":"equippingCharacter","characterName":"Cyno"}},{"id":26,"action":{"type":"equip","to":"Chongyun"},"filter":{"type":"equippingCharacter","characterName":"Ningguang"}},{"id":27,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Cyno"}},{"id":28,"action":{"type":"equip","to":"Gaming"},"filter":{"type":"equippingCharacter","characterName":"KamisatoAyaka"}},{"id":29,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Sucrose"}},{"id":30,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Alhaitham"}},{"id":31,"action":{"type":"equip","to":"Xinyan"},"filter":{"type":"equippingCharacter","characterName":"Tartaglia"}},{"id":32,"action":{"type":"equip","to":"Mika"},"filter":{"type":"equippingCharacter","characterName":"Kaveh"}},{"id":33,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Candace"}},{"id":34,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Chiori"}},{"id":35,"action":{"type":"equip","to":"YunJin"},"filter":{"type":"equippingCharacter","characterName":"Tighnari"}},{"id":36,"action":{"type":"equip","to":"Yaoyao"},"filter":{"type":"equippingCharacter","characterName":"KujouSara"}},{"id":37,"action":{"type":"equip","to":"HuTao"},"filter":{"type":"equippingCharacter","characterName":"Yaoyao"}},{"id":38,"action":{"type":"equip","to":"Xiao"},"filter":{"type":"equippingCharacter","characterName":"Beidou"}},{"id":39,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Mona"}},{"id":40,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Yoimiya"}},{"id":41,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Aloy"}},{"id":42,"action":{"type":"equip","to":"Klee"},"filter":{"type":"equippingCharacter","characterName":"Keqing"}},{"id":43,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Wanderer"}},{"id":44,"action":{"type":"equip","to":"Zhongli"},"filter":{"type":"equippingCharacter","characterName":"AratakiItto"}},{"id":45,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"AratakiItto"}},{"id":46,"action":{"type":"equip","to":"Eula"},"filter":{"type":"equippingCharacter","characterName":"Lyney"}},{"id":47,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"YaeMiko"}},{"id":48,"action":{"type":"equip","to":"Yanfei"},"filter":{"type":"equippingCharacter","characterName":"Diluc"}},{"id":49,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Xianyun"}},{"id":50,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"RaidenShogun"}},{"id":51,"action":{"type":"equip","to":"Navia"},"filter":{"type":"equippingCharacter","characterName":"Kirara"}},{"id":52,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Lynette"}},{"id":53,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Eula"}},{"id":54,"action":{"type":"equip","to":"Collei"},"filter":{"type":"equippingCharacter","characterName":"SangonomiyaKokomi"}},{"id":55,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"KukiShinobu"}},{"id":56,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Lynette"}},{"id":57,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Noelle"}},{"id":58,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Yanfei"}},{"id":59,"action":{"type":"equip","to":"Kirara"},"filter":{"type":"equippingCharacter","characterName":"AratakiItto"}},{"id":60,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Faruzan"}},{"id":61,"action":{"type":"equip","to":"Chiori"},"filter":{"type":"equippingCharacter","characterName":"Noelle"}},{"id":62,"action":{"type":"equip","to":"Lyney"},"filter":{"type":"equippingCharacter","characterName":"Baizhu"}},{"id":63,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"KamisatoAyato"}},{"id":64,"action":{"type":"equip","to":"Xingqiu"},"filter":{"type":"equippingCharacter","characterName":"Tighnari"}},{"id":65,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Wriothesley"}},{"id":66,"action":{"type":"equip","to":"Rosaria"},"filter":{"type":"equippingCharacter","characterName":"Navia"}},{"id":67,"action":{"type":"equip","to":"Traveler"},"filter":{"type":"equippingCharacter","characterName":"Yanfei"}},{"id":68,"action":{"type":"equip","to":"Eula"},"filter":{"type":"equippingCharacter","characterName":"Razor"}},{"id":69,"action":{"type":"equip","to":"Zhongli"},"filter":{"type":"equippingCharacter","characterName":"Mona"}},{"id":70,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Razor"}},{"id":71,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Charlotte"}},{"id":72,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Fischl"}},{"id":73,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"ShikanoinHeizou"}},{"id":74,"action":{"type":"equip","to":"Albedo"},"filter":{"type":"equippingCharacter","characterName":"Nahida"}},{"id":75,"action":{"type":"equip","to":"KujouSara"},"filter":{"type":"equippingCharacter","characterName":"Collei"}},{"id":76,"action":{"type":"equip","to":"Sucrose"},"filter":{"type":"equippingCharacter","characterName":"Chiori"}},{"id":77,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Gorou"}},{"id":78,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Dehya"}},{"id":79,"action":{"type":"equip","to":"Lisa"},"filter":{"type":"equippingCharacter","characterName":"Yaoyao"}},{"id":80,"action":{"type":"equip","to":"Wriothesley"},"filter":{"type":"equippingCharacter","characterName":"Kirara"}},{"id":81,"action":{"type":"equip","to":"KujouSara"},"filter":{"type":"equippingCharacter","characterName":"Dori"}},{"id":82,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Wriothesley"}},{"id":83,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Navia"}},{"id":84,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Chiori"}},{"id":85,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Wanderer"}},{"id":86,"action":{"type":"equip","to":"Alhaitham"},"filter":{"type":"equippingCharacter","characterName":"Dehya"}},{"id":87,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Xiao"}},{"id":88,"action":{"type":"equip","to":"KaedeharaKazuha"},"filter":{"type":"equippingCharacter","characterName":"Mika"}},{"id":89,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Lisa"}},{"id":90,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Lisa"}},{"id":91,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Chevreuse"}},{"id":92,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"AratakiItto"}},{"id":93,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Mona"}},{"id":94,"action":{"type":"equip","to":"Cyno"},"filter":{"type":"equippingCharacter","characterName":"Chiori"}},{"id":95,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Ningguang"}},{"id":96,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"RaidenShogun"}},{"id":97,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Aloy"}},{"id":98,"action":{"type":"equip","to":"Fischl"},"filter":{"type":"equippingCharacter","characterName":"Dehya"}},{"id":99,"action":{"type":"unequip"},"filter":{"type":"equippingCharacter","characterName":"Bennett"}}]}
1 change: 1 addition & 0 deletions src/morph/benchmark/ruleset-good-bench-data.json

Large diffs are not rendered by default.

Loading

0 comments on commit 9aa467f

Please sign in to comment.