mirror of
https://github.com/Zagrios/bs-manager.git
synced 2026-07-03 14:08:25 +02:00
[feature-107] use size optimized song details cache
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache.model";
|
||||
import { SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache/song-details-cache.model";
|
||||
|
||||
export interface BsvMapDetail {
|
||||
automapper: boolean;
|
||||
@@ -142,11 +142,57 @@ export interface BsvMapParitySummary {
|
||||
warns: number;
|
||||
}
|
||||
|
||||
export type BsvMapCharacteristic = "Standard" | "OneSaber" | "NoArrows" | "90Degree" | "360Degree" | "Lightshow" | "Lawless";
|
||||
export type BsvMapDifficultyType = "Easy" | "Normal" | "Hard" | "Expert" | "ExpertPlus";
|
||||
export enum MapStyle {
|
||||
Dance = "dance",
|
||||
Swing = "swing",
|
||||
Nightcore = "nightcore",
|
||||
Folk = "folk",
|
||||
Family = "family",
|
||||
Ambient = "ambient",
|
||||
Funk = "funk",
|
||||
Jazz = "jazz",
|
||||
Soul = "soul",
|
||||
Speedcore = "speedcore",
|
||||
Punk = "punk",
|
||||
Rb = "rb",
|
||||
Holiday = "holiday",
|
||||
Vocaloid = "vocaloid",
|
||||
JRock = "j-rock",
|
||||
Trance = "trance",
|
||||
DrumBass = "drumbass",
|
||||
Comedy = "comedy",
|
||||
Instrumental = "instrumental",
|
||||
Hardcore = "hardcore",
|
||||
KPop = "k-pop",
|
||||
Indie = "indie",
|
||||
Techno = "techno",
|
||||
House = "house",
|
||||
Game = "game",
|
||||
Film = "film",
|
||||
Alt = "alt",
|
||||
Dubstep = "dubstep",
|
||||
Metal = "metal",
|
||||
Anime = "anime",
|
||||
Hiphop = "hiphop",
|
||||
JPop = "j-pop",
|
||||
Rock = "rock",
|
||||
Pop = "pop",
|
||||
Electronic = "electronic",
|
||||
ClassicalOrchestral = "classical-orchestral"
|
||||
}
|
||||
|
||||
export type MapStyle = "dance" | "swing" | "nightcore" | "folk" | "family" | "ambient" | "funk" | "jazz" | "soul" | "speedcore" | "punk" | "rb" | "holiday" | "vocaloid" | "j-rock" | "trance" | "drumbass" | "comedy" | "instrumental" | "hardcore" | "k-pop" | "indie" | "techno" | "house" | "game" | "film" | "alt" | "dubstep" | "metal" | "anime" | "hiphop" | "j-pop" | "rock" | "pop" | "electronic" | "classical-orchestral";
|
||||
export type MapType = "accuracy" | "balanced" | "challenge" | "dancestyle" | "fitness" | "speed" | "tech";
|
||||
// MapType Enum
|
||||
export enum MapType {
|
||||
Accuracy = "accuracy",
|
||||
Balanced = "balanced",
|
||||
Challenge = "challenge",
|
||||
Dancestyle = "dancestyle",
|
||||
Fitness = "fitness",
|
||||
Speed = "speed",
|
||||
Tech = "tech"
|
||||
}
|
||||
|
||||
export const MapTags = { ...MapStyle, ...MapType };
|
||||
export type MapTag = MapStyle | MapType;
|
||||
|
||||
export type MapRequirement = "chroma" | "noodle" | "me" | "cinema";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { RawMapInfoData } from "./raw-map.model";
|
||||
import { SongDetails } from "./song-details-cache.model";
|
||||
import { SongDetails } from "./song-details-cache/song-details-cache.model";
|
||||
|
||||
export interface BsmLocalMap {
|
||||
hash: string;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { RawMapInfoData, RawMapDifficulty, RawDifficultySet } from "./raw-map.model";
|
||||
export { BsvInstant, BsvMapDetail, BsvMapDetailMetadata, BsvMapDifficulty, BsvMapParitySummary, BsvMapStats, BsvMapTestplay, BsvMapVersion, BsvUserDetail } from "./beat-saver.model";
|
||||
export { SongDetailsCache, SongDetails, Difficulty, MapDetailMetadata, Uploader, SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache.model";
|
||||
export { SongDetails, SongDifficulty, SongUploader, SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache/song-details-cache.model";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache.model";
|
||||
import { SongDetailDiffCharactertistic, SongDiffName } from "./song-details-cache/song-details-cache.model";
|
||||
|
||||
export interface RawMapInfoData<T = unknown> {
|
||||
_version: string;
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
|
||||
|
||||
export interface SongDetailsCache {
|
||||
songs: SongDetails[];
|
||||
lastUpdated: number; // int64 in protobuf is represented as number in TypeScript, but consider using BigInt for precise representation
|
||||
total: number; // uint32
|
||||
}
|
||||
|
||||
export interface SongDetails {
|
||||
id: string;
|
||||
hash: string;
|
||||
name: string;
|
||||
metadata: MapDetailMetadata;
|
||||
uploader: Uploader;
|
||||
uploadedAt: number; // int32
|
||||
tags: string[];
|
||||
bpm: number; // float
|
||||
ranked: boolean;
|
||||
qualified: boolean;
|
||||
curated: boolean;
|
||||
rankedBL: boolean;
|
||||
nominatedBL: boolean;
|
||||
qualifiedBL: boolean;
|
||||
upVotes: number; // int32
|
||||
downVotes: number; // int32
|
||||
downloads: number; // int32
|
||||
duration: number; // int32
|
||||
automapper: boolean;
|
||||
difficulties: Difficulty[];
|
||||
}
|
||||
|
||||
export interface Difficulty {
|
||||
difficulty: SongDiffName;
|
||||
characteristic: SongDetailDiffCharactertistic;
|
||||
label: string;
|
||||
stars: number; // float
|
||||
starsBL: number; // float
|
||||
njs: number; // float
|
||||
nps: number; // float
|
||||
offset: number; // float
|
||||
chroma: boolean;
|
||||
cinema: boolean;
|
||||
me: boolean;
|
||||
ne: boolean;
|
||||
bombs: number; // int32
|
||||
notes: number; // int32
|
||||
obstacles: number; // int32
|
||||
}
|
||||
|
||||
export interface Uploader {
|
||||
name: string;
|
||||
id: number; // int32
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export interface MapDetailMetadata {
|
||||
bpm: number; // float
|
||||
duration: number; // int32
|
||||
levelAuthorName: string;
|
||||
songAuthorName: string;
|
||||
songName: string;
|
||||
songSubName: string;
|
||||
}
|
||||
|
||||
export enum SongDiffName {
|
||||
Easy = "Easy",
|
||||
Normal = "Normal",
|
||||
Hard = "Hard",
|
||||
Expert = "Expert",
|
||||
ExpertPlus = "ExpertPlus",
|
||||
}
|
||||
|
||||
export enum SongDetailDiffCharactertistic {
|
||||
Standard = "Standard",
|
||||
OneSaber = "OneSaber",
|
||||
NoArrows = "NoArrows",
|
||||
Lawless = "Lawless",
|
||||
Lightshow = "Lightshow",
|
||||
Legacy = "Legacy",
|
||||
_90Degree = "90Degree",
|
||||
_360Degree = "360Degree",
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
|
||||
// SongDetailsCache Message
|
||||
export interface RawSongDetailsCache {
|
||||
songs: RawSongDetails[];
|
||||
lastUpdated: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
// SongDetails Message
|
||||
export interface RawSongDetails {
|
||||
idInt: number;
|
||||
hash: string;
|
||||
duration: number;
|
||||
uploader: RawUploader;
|
||||
uploadedAt: number;
|
||||
tags: RawMapTag[];
|
||||
ranked: boolean;
|
||||
qualified: boolean;
|
||||
curated: boolean;
|
||||
rankedBL: boolean;
|
||||
nominatedBL: boolean;
|
||||
qualifiedBL: boolean;
|
||||
upVotes: number;
|
||||
downVotes: number;
|
||||
downloads: number;
|
||||
automapper: boolean;
|
||||
difficulties: RawDifficulty[];
|
||||
}
|
||||
|
||||
// Difficulty Message
|
||||
export interface RawDifficulty {
|
||||
difficulty: RawDifficultyLabel;
|
||||
characteristic: RawDifficultyCharacteristic;
|
||||
label: string;
|
||||
starsT100: number;
|
||||
starsBlT100: number;
|
||||
njsT100: number;
|
||||
npsT100: number;
|
||||
offsetT100: number;
|
||||
chroma: boolean;
|
||||
cinema: boolean;
|
||||
me: boolean;
|
||||
ne: boolean;
|
||||
bombs: number;
|
||||
notes: number;
|
||||
obstacles: number;
|
||||
}
|
||||
|
||||
// Uploader Message
|
||||
export interface RawUploader {
|
||||
name: string;
|
||||
id: number;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
// DifficultyLabel Enum
|
||||
export enum RawDifficultyLabel {
|
||||
UNKNOWN_LABEL,
|
||||
EASY,
|
||||
NORMAL,
|
||||
HARD,
|
||||
EXPERT,
|
||||
EXPERT_PLUS
|
||||
}
|
||||
|
||||
// DifficultyCharacteristic Enum
|
||||
export enum RawDifficultyCharacteristic {
|
||||
UNKNOWN_CHARACTERISTIC,
|
||||
STANDARD,
|
||||
ONE_SABER,
|
||||
NO_ARROWS,
|
||||
LAWLESS,
|
||||
LIGHTSHOW,
|
||||
LEGACY,
|
||||
NINETY_DEGREE,
|
||||
THREESIXTY_DEGREE
|
||||
}
|
||||
|
||||
// MapTag Enum
|
||||
export enum RawMapTag {
|
||||
UNKNOWN_TAG,
|
||||
DANCE,
|
||||
SWING,
|
||||
NIGHTCORE,
|
||||
FOLK,
|
||||
FAMILY,
|
||||
AMBIENT,
|
||||
FUNK,
|
||||
JAZZ,
|
||||
SOUL,
|
||||
SPEEDCORE,
|
||||
PUNK,
|
||||
RB,
|
||||
HOLIDAY,
|
||||
VOCALOID,
|
||||
J_ROCK,
|
||||
TRANCE,
|
||||
DRUMBASS,
|
||||
COMEDY,
|
||||
INSTRUMENTAL,
|
||||
HARDCORE,
|
||||
K_POP,
|
||||
INDIE,
|
||||
TECHNO,
|
||||
HOUSE,
|
||||
GAME,
|
||||
FILM,
|
||||
ALT,
|
||||
DUBSTEP,
|
||||
METAL,
|
||||
ANIME,
|
||||
HIPHOP,
|
||||
J_POP,
|
||||
ROCK,
|
||||
POP,
|
||||
ELECTRONIC,
|
||||
CLASSICAL_ORCHESTRAL,
|
||||
ACCURACY,
|
||||
BALANCED,
|
||||
CHALLENGE,
|
||||
DANCESTYLE,
|
||||
FITNESS,
|
||||
SPEED,
|
||||
TECH
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { MapStyle, MapTag, MapType } from "../beat-saver.model";
|
||||
import { RawDifficulty, RawDifficultyCharacteristic, RawDifficultyLabel, RawMapTag, RawSongDetails, RawUploader } from "./raw-song-details-cache.model";
|
||||
import { SongDetailDiffCharactertistic, SongDetails, SongDiffName, SongDifficulty, SongUploader } from "./song-details-cache.model";
|
||||
|
||||
export abstract class RawSongDetailsDeserializer {
|
||||
|
||||
private constructor() {}
|
||||
|
||||
private static deserializeRawTag(rawTag: RawMapTag): MapTag {
|
||||
switch (rawTag) {
|
||||
case RawMapTag.DANCE: return MapStyle.Dance;
|
||||
case RawMapTag.SWING: return MapStyle.Swing;
|
||||
case RawMapTag.NIGHTCORE: return MapStyle.Nightcore;
|
||||
case RawMapTag.FOLK: return MapStyle.Folk;
|
||||
case RawMapTag.FAMILY: return MapStyle.Family;
|
||||
case RawMapTag.AMBIENT: return MapStyle.Ambient;
|
||||
case RawMapTag.FUNK: return MapStyle.Funk;
|
||||
case RawMapTag.JAZZ: return MapStyle.Jazz;
|
||||
case RawMapTag.SOUL: return MapStyle.Soul;
|
||||
case RawMapTag.SPEEDCORE: return MapStyle.Speedcore;
|
||||
case RawMapTag.PUNK: return MapStyle.Punk;
|
||||
case RawMapTag.RB: return MapStyle.Rb;
|
||||
case RawMapTag.HOLIDAY: return MapStyle.Holiday;
|
||||
case RawMapTag.VOCALOID: return MapStyle.Vocaloid;
|
||||
case RawMapTag.J_ROCK: return MapStyle.JRock;
|
||||
case RawMapTag.TRANCE: return MapStyle.Trance;
|
||||
case RawMapTag.DRUMBASS: return MapStyle.DrumBass;
|
||||
case RawMapTag.COMEDY: return MapStyle.Comedy;
|
||||
case RawMapTag.INSTRUMENTAL: return MapStyle.Instrumental;
|
||||
case RawMapTag.HARDCORE: return MapStyle.Hardcore;
|
||||
case RawMapTag.K_POP: return MapStyle.KPop;
|
||||
case RawMapTag.INDIE: return MapStyle.Indie;
|
||||
case RawMapTag.TECHNO: return MapStyle.Techno;
|
||||
case RawMapTag.HOUSE: return MapStyle.House;
|
||||
case RawMapTag.GAME: return MapStyle.Game;
|
||||
case RawMapTag.FILM: return MapStyle.Film;
|
||||
case RawMapTag.ALT: return MapStyle.Alt;
|
||||
case RawMapTag.DUBSTEP: return MapStyle.Dubstep;
|
||||
case RawMapTag.METAL: return MapStyle.Metal;
|
||||
case RawMapTag.ANIME: return MapStyle.Anime;
|
||||
case RawMapTag.HIPHOP: return MapStyle.Hiphop;
|
||||
case RawMapTag.J_POP: return MapStyle.JPop;
|
||||
case RawMapTag.ROCK: return MapStyle.Rock;
|
||||
case RawMapTag.POP: return MapStyle.Pop;
|
||||
case RawMapTag.ELECTRONIC: return MapStyle.Electronic;
|
||||
case RawMapTag.CLASSICAL_ORCHESTRAL: return MapStyle.ClassicalOrchestral;
|
||||
case RawMapTag.ACCURACY: return MapType.Accuracy;
|
||||
case RawMapTag.BALANCED: return MapType.Balanced;
|
||||
case RawMapTag.CHALLENGE: return MapType.Challenge;
|
||||
case RawMapTag.DANCESTYLE: return MapType.Dancestyle;
|
||||
case RawMapTag.FITNESS: return MapType.Fitness;
|
||||
case RawMapTag.SPEED: return MapType.Speed;
|
||||
case RawMapTag.TECH: return MapType.Tech;
|
||||
default: return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private static deserializeRawTags(rawTags: RawMapTag[] = []): MapTag[] {
|
||||
return rawTags.map(rawTag => this.deserializeRawTag(rawTag)).filter(Boolean);
|
||||
}
|
||||
|
||||
private static deserializeRawDifficultyCharacteristic(rawCharacteristic: RawDifficultyCharacteristic): SongDetailDiffCharactertistic {
|
||||
switch (rawCharacteristic) {
|
||||
case RawDifficultyCharacteristic.STANDARD: return SongDetailDiffCharactertistic.Standard;
|
||||
case RawDifficultyCharacteristic.ONE_SABER: return SongDetailDiffCharactertistic.OneSaber;
|
||||
case RawDifficultyCharacteristic.NO_ARROWS: return SongDetailDiffCharactertistic.NoArrows;
|
||||
case RawDifficultyCharacteristic.LAWLESS: return SongDetailDiffCharactertistic.Lawless;
|
||||
case RawDifficultyCharacteristic.LIGHTSHOW: return SongDetailDiffCharactertistic.Lightshow;
|
||||
case RawDifficultyCharacteristic.LEGACY: return SongDetailDiffCharactertistic.Legacy;
|
||||
case RawDifficultyCharacteristic.NINETY_DEGREE: return SongDetailDiffCharactertistic._90Degree;
|
||||
case RawDifficultyCharacteristic.THREESIXTY_DEGREE: return SongDetailDiffCharactertistic._360Degree;
|
||||
default: return SongDetailDiffCharactertistic.Standard;
|
||||
}
|
||||
}
|
||||
|
||||
private static deserializeRawDifficultyLabel(rawLabel: RawDifficultyLabel): SongDiffName {
|
||||
switch (rawLabel) {
|
||||
case RawDifficultyLabel.EASY: return SongDiffName.Easy;
|
||||
case RawDifficultyLabel.NORMAL: return SongDiffName.Normal;
|
||||
case RawDifficultyLabel.HARD: return SongDiffName.Hard;
|
||||
case RawDifficultyLabel.EXPERT: return SongDiffName.Expert;
|
||||
case RawDifficultyLabel.EXPERT_PLUS: return SongDiffName.ExpertPlus;
|
||||
default: return SongDiffName.ExpertPlus;
|
||||
}
|
||||
}
|
||||
|
||||
private static deserializeRawDifficulty(rawDifficulty: RawDifficulty): SongDifficulty {
|
||||
return {
|
||||
difficulty: this.deserializeRawDifficultyLabel(rawDifficulty.difficulty),
|
||||
characteristic: this.deserializeRawDifficultyCharacteristic(rawDifficulty.characteristic),
|
||||
label: rawDifficulty.label,
|
||||
stars: Math.round(rawDifficulty.starsT100) / 100,
|
||||
starsBL: Math.round(rawDifficulty.starsBlT100) / 100,
|
||||
njs: Math.round(rawDifficulty.njsT100) / 100,
|
||||
nps: Math.round(rawDifficulty.npsT100) / 100,
|
||||
offset: Math.round(rawDifficulty.offsetT100) / 100,
|
||||
chroma: rawDifficulty.chroma,
|
||||
cinema: rawDifficulty.cinema,
|
||||
me: rawDifficulty.me,
|
||||
ne: rawDifficulty.ne,
|
||||
bombs: rawDifficulty.bombs,
|
||||
notes: rawDifficulty.notes,
|
||||
obstacles: rawDifficulty.obstacles
|
||||
};
|
||||
}
|
||||
|
||||
private static deserializeRawDifficulties(rawDifficulties: RawDifficulty[] = []): SongDifficulty[] {
|
||||
return rawDifficulties.map(rawDifficulty => this.deserializeRawDifficulty(rawDifficulty));
|
||||
}
|
||||
|
||||
private static deserializeRawUploader(rawUploader: RawUploader): SongUploader {
|
||||
return {
|
||||
name: rawUploader.name,
|
||||
id: rawUploader.id,
|
||||
verified: rawUploader.verified
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The map ID has been serialized as a number from its HEX value, so we need to deserialize it back to a string.
|
||||
*/
|
||||
private static deserializeMapId(rawMapId: number): string {
|
||||
return rawMapId.toString(16);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize the raw song details to the song details model.
|
||||
* @param {RawSongDetails} rawSongDetails the raw song details to deserialize (normally get from the proto message)
|
||||
* @returns {SongDetails} the deserialized song details
|
||||
* @throws Can throw an error if the raw song details is invalid or missing required fields
|
||||
*/
|
||||
public static deserialize(rawSongDetails: RawSongDetails): SongDetails {
|
||||
if (!rawSongDetails) {
|
||||
throw new Error("Invalid raw song details");
|
||||
}
|
||||
|
||||
return {
|
||||
id: this.deserializeMapId(rawSongDetails.idInt),
|
||||
hash: rawSongDetails.hash,
|
||||
duration: rawSongDetails.duration,
|
||||
uploader: this.deserializeRawUploader(rawSongDetails.uploader),
|
||||
uploadedAt: rawSongDetails.uploadedAt,
|
||||
tags: this.deserializeRawTags(rawSongDetails.tags),
|
||||
ranked: rawSongDetails.ranked,
|
||||
qualified: rawSongDetails.qualified,
|
||||
curated: rawSongDetails.curated,
|
||||
rankedBL: rawSongDetails.rankedBL,
|
||||
nominatedBL: rawSongDetails.nominatedBL,
|
||||
qualifiedBL: rawSongDetails.qualifiedBL,
|
||||
upVotes: rawSongDetails.upVotes,
|
||||
downVotes: rawSongDetails.downVotes,
|
||||
downloads: rawSongDetails.downloads,
|
||||
automapper: rawSongDetails.automapper,
|
||||
difficulties: this.deserializeRawDifficulties(rawSongDetails.difficulties)
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { MapTag } from "../beat-saver.model";
|
||||
|
||||
export interface SongDetails {
|
||||
id: string;
|
||||
hash: string;
|
||||
duration: number;
|
||||
uploader: SongUploader;
|
||||
uploadedAt: number;
|
||||
tags: MapTag[];
|
||||
ranked: boolean;
|
||||
qualified: boolean;
|
||||
curated: boolean;
|
||||
rankedBL: boolean;
|
||||
nominatedBL: boolean;
|
||||
qualifiedBL: boolean;
|
||||
upVotes: number;
|
||||
downVotes: number;
|
||||
downloads: number;
|
||||
automapper: boolean;
|
||||
difficulties: SongDifficulty[];
|
||||
}
|
||||
|
||||
export interface SongDifficulty {
|
||||
difficulty: SongDiffName;
|
||||
characteristic: SongDetailDiffCharactertistic;
|
||||
label: string;
|
||||
stars: number;
|
||||
starsBL: number;
|
||||
njs: number;
|
||||
nps: number;
|
||||
offset: number;
|
||||
chroma: boolean;
|
||||
cinema: boolean;
|
||||
me: boolean;
|
||||
ne: boolean;
|
||||
bombs: number;
|
||||
notes: number;
|
||||
obstacles: number;
|
||||
}
|
||||
|
||||
export interface SongUploader {
|
||||
name: string;
|
||||
id: number;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export enum SongDiffName {
|
||||
Easy = "Easy",
|
||||
Normal = "Normal",
|
||||
Hard = "Hard",
|
||||
Expert = "Expert",
|
||||
ExpertPlus = "ExpertPlus",
|
||||
}
|
||||
|
||||
export enum SongDetailDiffCharactertistic {
|
||||
Standard = "Standard",
|
||||
OneSaber = "OneSaber",
|
||||
NoArrows = "NoArrows",
|
||||
Lawless = "Lawless",
|
||||
Lightshow = "Lightshow",
|
||||
Legacy = "Legacy",
|
||||
_90Degree = "90Degree",
|
||||
_360Degree = "360Degree",
|
||||
}
|
||||
Reference in New Issue
Block a user