tswow/tswow-scripts/data/dbc/DBCFile.ts
ihm-tswow 2634da63cd datascripts: fix and improve DBCFile#fastSearch
- fix issue with reading custom values lower than base height

- fix binary search upper boundary

- change linear/binary search order

- add source linear search with performance warning
2022-06-28 12:34:27 +02:00

253 lines
7.4 KiB
TypeScript

/*
* This file is part of tswow (https://github.com/tswow)
*
* Copyright (C) 2020 tswow <https://github.com/tswow/>
* 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, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
import * as fs from 'fs';
import * as path from 'path';
import { GetStage } from '..';
import { inMemory } from '../query/Query';
import { dataset } from '../Settings';
import { Table } from '../table/Table';
import { DBCBuffer } from './DBCBuffer';
import { DBCRow } from './DBCRow';
export type AnyFile = DBCFile<any, any, any>;
export type DBCRowCreator<C, Q, R extends DBCRow<C, Q>> = (table: DBCFile<C, Q, R>, buffer: DBCBuffer, offset: number) => R;
/**
* Represents an entire DBC file loaded in memory, with all rows fully loaded.
*/
export class DBCFile<C, Q, R extends DBCRow<C, Q>> extends Table<C, Q, R> {
private loaded = false;
protected buffer: DBCBuffer;
protected rowMaker: DBCRowCreator<C, Q, R>;
constructor(name: string, rowMaker: DBCRowCreator<C, Q, R>) {
super(name);
this.rowMaker = rowMaker;
this.buffer = new DBCBuffer();
}
private checkSort() {
if(GetStage() !== 'SORT') {
throw new Error(
`Attempting to sort DBC before SORT stage. `
+ `Please register a callback like this to sort:\n\n`
+ `import { sort } from "wotlkdata"\n`
+ `\n`
+ `sort('my-sort-name',()=>{\n`
+ ` // do your sort here\n`
+ `});\n\n`
)
}
}
binarySort(minVal: number, scorer: (row: R)=>number) {
this.checkSort();
const binSearch = (low: number, high: number, x: number): number => {
if(high<=low) {
return x > scorer(this.getRow(low)) ? (low+1) : low;
}
let mid = Math.floor((low + high) / 2);
let midval = scorer(this.getRow(mid));
return x === midval ? mid+1
: x > midval
? binSearch(mid+1,high,x)
: binSearch(low,mid-1,x)
}
let last = minVal;
for(let i=0;i<this.rowCount;++i) {
let cur = scorer(this.getRow(i));
if(cur < last) {
let j = binSearch(0,i-1,cur);
this.buffer.move(i,j);
} else {
last = cur;
}
}
}
quickSort(comparator: (row1: R, row2: R)=>number) {
this.checkSort();
if(GetStage() !== 'SORT') {
throw new Error(
`Trying to sort before SORT stage:`
+ ` register a callback to the 'sort'`
+ ` function exported from 'wotlkdata'`
)
}
const partition = (left: number, right: number) => {
let i = left-1;
for(let j=left;j<right;++j) {
if(comparator(this.getRow(j),this.getRow(right))<0) {
++i;
this.buffer.swap(i,j);
}
}
this.buffer.swap(i+1,right)
return i+1;
}
const quicksort = (left: number, right: number) => {
if(left < right) {
let pi = partition(left,right);
quicksort(left,pi-1);
quicksort(pi+1,right);
}
}
if(this.rowCount > 1) quicksort(0, this.rowCount-1);
}
swap(index1: number, index2: number) {
this.checkSort();
this.buffer.swap(index1,index2);
}
move(fromIndex: number, toIndex: number) {
this.checkSort();
this.buffer.move(fromIndex,toIndex);
}
static getBuffer(file: DBCFile<any, any, any>) {
return file.buffer;
}
static createRow(file: DBCFile<any, any, any>, offset: number) {
return file.rowMaker(file, file.buffer, offset);
}
get rowCount() {
this.load();
return this.buffer.rowCount;
}
isLoaded() {
return this.loaded;
}
write(filePath: string) {
if (!this.loaded) {
this.load();
}
fs.writeFileSync(filePath, this.buffer.write());
}
private defaultPath() {
return path.join(dataset.dbc_source.get(), this.name + '.dbc');
}
first(): R {
return this.getRow(0);
}
read(dbcpath: string = this.defaultPath()) {
this.loaded = false;
this.load(dbcpath);
return this;
}
private load(filePath: string = this.defaultPath()) {
if (!this.loaded) {
this.buffer.read(filePath);
this.loaded = true;
}
}
protected makeRow(index: number) {
this.load();
return this.rowMaker(this, this.buffer, index);
}
highest(callback: (row: R) => number) {
return this.queryAll({} as any)
.sort((a, b) => callback(b) > callback(a) ? 1 : -1)[0];
}
lowest(callback: (row: R) => number) {
return this.queryAll({} as any)
.sort((a, b) => callback(a) > callback(b) ? 1 : -1)[0];
}
protected fastSearch(value: number): R {
// loading row first ensures the buffer is loaded before we calculate buffer.baseRowCount
const row = this.getRow(0);
let low = 0;
let high = this.buffer.baseRowCount - 1;
// Binary search
while (true) {
if (high < low) {
break;
}
const mid = Math.floor(low + (high - low) / 2);
DBCRow.setIndex(row, mid);
const cur = this.buffer.readuint(DBCRow.getOffset(row));
if (cur < value) {
low = mid + 1;
} else if (cur > value) {
high = mid - 1;
} else {
return row;
}
}
// Linear search (custom values)
for (let i = this.buffer.baseRowCount; i < this.buffer.rowCount; ++i) {
DBCRow.setIndex(row, i);
if (this.buffer.readuint(DBCRow.getOffset(row)) === value) {
return row;
}
}
// Linear search (source values)
for (let i = 0; i < this.buffer.baseRowCount; ++i) {
DBCRow.setIndex(row, i);
if (this.buffer.readuint(DBCRow.getOffset(row)) === value) {
console.log(`Warning: Found base row ${value} only from linear search in ${this.name}.dbc, input dbc is not correctly sorted!`)
return row;
}
}
return undefined;
}
getName() {
return this.name;
}
getRow(index: number) {
this.load();
return this.rowMaker(this, this.buffer, index);
}
queryAll(predicate: Q): R[] {
this.load();
const arr: R[] = [];
const row = this.getRow(0);
for (let i = 0; i < this.buffer.rowCount; ++i) {
DBCRow.setIndex(row, i);
if (inMemory(predicate, row)) {
arr.push(this.getRow(i));
}
}
return arr;
}
}