test new scanner on user obj
This commit is contained in:
parent
820bc87418
commit
58fe999675
@ -6,128 +6,77 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"dk/internal/database"
|
"dk/internal/database"
|
||||||
|
"dk/internal/utils/scanner"
|
||||||
|
|
||||||
"zombiezen.com/go/sqlite"
|
"zombiezen.com/go/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
// User represents a user in the database
|
// User represents a user in the database
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int `json:"id"`
|
ID int `db:"id" json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `db:"username" json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `db:"password" json:"password"`
|
||||||
Email string `json:"email"`
|
Email string `db:"email" json:"email"`
|
||||||
Verified int `json:"verified"`
|
Verified int `db:"verified" json:"verified"`
|
||||||
Token string `json:"token"`
|
Token string `db:"token" json:"token"`
|
||||||
Registered int64 `json:"registered"`
|
Registered int64 `db:"registered" json:"registered"`
|
||||||
LastOnline int64 `json:"last_online"`
|
LastOnline int64 `db:"last_online" json:"last_online"`
|
||||||
Auth int `json:"auth"`
|
Auth int `db:"auth" json:"auth"`
|
||||||
X int `json:"x"`
|
X int `db:"x" json:"x"`
|
||||||
Y int `json:"y"`
|
Y int `db:"y" json:"y"`
|
||||||
ClassID int `json:"class_id"`
|
ClassID int `db:"class_id" json:"class_id"`
|
||||||
Currently string `json:"currently"`
|
Currently string `db:"currently" json:"currently"`
|
||||||
Fighting int `json:"fighting"`
|
Fighting int `db:"fighting" json:"fighting"`
|
||||||
MonsterID int `json:"monster_id"`
|
MonsterID int `db:"monster_id" json:"monster_id"`
|
||||||
MonsterHP int `json:"monster_hp"`
|
MonsterHP int `db:"monster_hp" json:"monster_hp"`
|
||||||
MonsterSleep int `json:"monster_sleep"`
|
MonsterSleep int `db:"monster_sleep" json:"monster_sleep"`
|
||||||
MonsterImmune int `json:"monster_immune"`
|
MonsterImmune int `db:"monster_immune" json:"monster_immune"`
|
||||||
UberDamage int `json:"uber_damage"`
|
UberDamage int `db:"uber_damage" json:"uber_damage"`
|
||||||
UberDefense int `json:"uber_defense"`
|
UberDefense int `db:"uber_defense" json:"uber_defense"`
|
||||||
HP int `json:"hp"`
|
HP int `db:"hp" json:"hp"`
|
||||||
MP int `json:"mp"`
|
MP int `db:"mp" json:"mp"`
|
||||||
TP int `json:"tp"`
|
TP int `db:"tp" json:"tp"`
|
||||||
MaxHP int `json:"max_hp"`
|
MaxHP int `db:"max_hp" json:"max_hp"`
|
||||||
MaxMP int `json:"max_mp"`
|
MaxMP int `db:"max_mp" json:"max_mp"`
|
||||||
MaxTP int `json:"max_tp"`
|
MaxTP int `db:"max_tp" json:"max_tp"`
|
||||||
Level int `json:"level"`
|
Level int `db:"level" json:"level"`
|
||||||
Gold int `json:"gold"`
|
Gold int `db:"gold" json:"gold"`
|
||||||
Exp int `json:"exp"`
|
Exp int `db:"exp" json:"exp"`
|
||||||
GoldBonus int `json:"gold_bonus"`
|
GoldBonus int `db:"gold_bonus" json:"gold_bonus"`
|
||||||
ExpBonus int `json:"exp_bonus"`
|
ExpBonus int `db:"exp_bonus" json:"exp_bonus"`
|
||||||
Strength int `json:"strength"`
|
Strength int `db:"strength" json:"strength"`
|
||||||
Dexterity int `json:"dexterity"`
|
Dexterity int `db:"dexterity" json:"dexterity"`
|
||||||
Attack int `json:"attack"`
|
Attack int `db:"attack" json:"attack"`
|
||||||
Defense int `json:"defense"`
|
Defense int `db:"defense" json:"defense"`
|
||||||
WeaponID int `json:"weapon_id"`
|
WeaponID int `db:"weapon_id" json:"weapon_id"`
|
||||||
ArmorID int `json:"armor_id"`
|
ArmorID int `db:"armor_id" json:"armor_id"`
|
||||||
ShieldID int `json:"shield_id"`
|
ShieldID int `db:"shield_id" json:"shield_id"`
|
||||||
Slot1ID int `json:"slot_1_id"`
|
Slot1ID int `db:"slot_1_id" json:"slot_1_id"`
|
||||||
Slot2ID int `json:"slot_2_id"`
|
Slot2ID int `db:"slot_2_id" json:"slot_2_id"`
|
||||||
Slot3ID int `json:"slot_3_id"`
|
Slot3ID int `db:"slot_3_id" json:"slot_3_id"`
|
||||||
WeaponName string `json:"weapon_name"`
|
WeaponName string `db:"weapon_name" json:"weapon_name"`
|
||||||
ArmorName string `json:"armor_name"`
|
ArmorName string `db:"armor_name" json:"armor_name"`
|
||||||
ShieldName string `json:"shield_name"`
|
ShieldName string `db:"shield_name" json:"shield_name"`
|
||||||
Slot1Name string `json:"slot_1_name"`
|
Slot1Name string `db:"slot_1_name" json:"slot_1_name"`
|
||||||
Slot2Name string `json:"slot_2_name"`
|
Slot2Name string `db:"slot_2_name" json:"slot_2_name"`
|
||||||
Slot3Name string `json:"slot_3_name"`
|
Slot3Name string `db:"slot_3_name" json:"slot_3_name"`
|
||||||
DropCode int `json:"drop_code"`
|
DropCode int `db:"drop_code" json:"drop_code"`
|
||||||
Spells string `json:"spells"`
|
Spells string `db:"spells" json:"spells"`
|
||||||
Towns string `json:"towns"`
|
Towns string `db:"towns" json:"towns"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// userColumns returns the column list for user queries (excluding id for inserts)
|
var userScanner = scanner.New[User]()
|
||||||
|
|
||||||
|
// userColumns returns the column list for user queries
|
||||||
func userColumns() string {
|
func userColumns() string {
|
||||||
return `id, username, password, email, verified, token, registered, last_online, auth,
|
return userScanner.Columns()
|
||||||
x, y, class_id, currently, fighting, monster_id, monster_hp, monster_sleep, monster_immune,
|
|
||||||
uber_damage, uber_defense, hp, mp, tp, max_hp, max_mp, max_tp, level, gold, exp,
|
|
||||||
gold_bonus, exp_bonus, strength, dexterity, attack, defense, weapon_id, armor_id, shield_id,
|
|
||||||
slot_1_id, slot_2_id, slot_3_id, weapon_name, armor_name, shield_name,
|
|
||||||
slot_1_name, slot_2_name, slot_3_name, drop_code, spells, towns`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// scanUser populates a User struct from a sqlite.Stmt
|
// scanUser populates a User struct using the fast scanner
|
||||||
func scanUser(stmt *sqlite.Stmt) *User {
|
func scanUser(stmt *sqlite.Stmt) *User {
|
||||||
return &User{
|
user := &User{}
|
||||||
ID: stmt.ColumnInt(0),
|
userScanner.Scan(stmt, user)
|
||||||
Username: stmt.ColumnText(1),
|
return user
|
||||||
Password: stmt.ColumnText(2),
|
|
||||||
Email: stmt.ColumnText(3),
|
|
||||||
Verified: stmt.ColumnInt(4),
|
|
||||||
Token: stmt.ColumnText(5),
|
|
||||||
Registered: stmt.ColumnInt64(6),
|
|
||||||
LastOnline: stmt.ColumnInt64(7),
|
|
||||||
Auth: stmt.ColumnInt(8),
|
|
||||||
X: stmt.ColumnInt(9),
|
|
||||||
Y: stmt.ColumnInt(10),
|
|
||||||
ClassID: stmt.ColumnInt(11),
|
|
||||||
Currently: stmt.ColumnText(12),
|
|
||||||
Fighting: stmt.ColumnInt(13),
|
|
||||||
MonsterID: stmt.ColumnInt(14),
|
|
||||||
MonsterHP: stmt.ColumnInt(15),
|
|
||||||
MonsterSleep: stmt.ColumnInt(16),
|
|
||||||
MonsterImmune: stmt.ColumnInt(17),
|
|
||||||
UberDamage: stmt.ColumnInt(18),
|
|
||||||
UberDefense: stmt.ColumnInt(19),
|
|
||||||
HP: stmt.ColumnInt(20),
|
|
||||||
MP: stmt.ColumnInt(21),
|
|
||||||
TP: stmt.ColumnInt(22),
|
|
||||||
MaxHP: stmt.ColumnInt(23),
|
|
||||||
MaxMP: stmt.ColumnInt(24),
|
|
||||||
MaxTP: stmt.ColumnInt(25),
|
|
||||||
Level: stmt.ColumnInt(26),
|
|
||||||
Gold: stmt.ColumnInt(27),
|
|
||||||
Exp: stmt.ColumnInt(28),
|
|
||||||
GoldBonus: stmt.ColumnInt(29),
|
|
||||||
ExpBonus: stmt.ColumnInt(30),
|
|
||||||
Strength: stmt.ColumnInt(31),
|
|
||||||
Dexterity: stmt.ColumnInt(32),
|
|
||||||
Attack: stmt.ColumnInt(33),
|
|
||||||
Defense: stmt.ColumnInt(34),
|
|
||||||
WeaponID: stmt.ColumnInt(35),
|
|
||||||
ArmorID: stmt.ColumnInt(36),
|
|
||||||
ShieldID: stmt.ColumnInt(37),
|
|
||||||
Slot1ID: stmt.ColumnInt(38),
|
|
||||||
Slot2ID: stmt.ColumnInt(39),
|
|
||||||
Slot3ID: stmt.ColumnInt(40),
|
|
||||||
WeaponName: stmt.ColumnText(41),
|
|
||||||
ArmorName: stmt.ColumnText(42),
|
|
||||||
ShieldName: stmt.ColumnText(43),
|
|
||||||
Slot1Name: stmt.ColumnText(44),
|
|
||||||
Slot2Name: stmt.ColumnText(45),
|
|
||||||
Slot3Name: stmt.ColumnText(46),
|
|
||||||
DropCode: stmt.ColumnInt(47),
|
|
||||||
Spells: stmt.ColumnText(48),
|
|
||||||
Towns: stmt.ColumnText(49),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find retrieves a user by ID
|
// Find retrieves a user by ID
|
||||||
@ -515,19 +464,19 @@ func (u *User) ToMap() map[string]any {
|
|||||||
"DropCode": u.DropCode,
|
"DropCode": u.DropCode,
|
||||||
"Spells": u.Spells,
|
"Spells": u.Spells,
|
||||||
"Towns": u.Towns,
|
"Towns": u.Towns,
|
||||||
|
|
||||||
// Computed values
|
// Computed values
|
||||||
"IsVerified": u.IsVerified(),
|
"IsVerified": u.IsVerified(),
|
||||||
"IsAdmin": u.IsAdmin(),
|
"IsAdmin": u.IsAdmin(),
|
||||||
"IsModerator": u.IsModerator(),
|
"IsModerator": u.IsModerator(),
|
||||||
"IsFighting": u.IsFighting(),
|
"IsFighting": u.IsFighting(),
|
||||||
"IsAlive": u.IsAlive(),
|
"IsAlive": u.IsAlive(),
|
||||||
"RegisteredTime": u.RegisteredTime(),
|
"RegisteredTime": u.RegisteredTime(),
|
||||||
"LastOnlineTime": u.LastOnlineTime(),
|
"LastOnlineTime": u.LastOnlineTime(),
|
||||||
"Equipment": u.GetEquipment(),
|
"Equipment": u.GetEquipment(),
|
||||||
"Stats": u.GetStats(),
|
"Stats": u.GetStats(),
|
||||||
"Position": map[string]int{"X": u.X, "Y": u.Y},
|
"Position": map[string]int{"X": u.X, "Y": u.Y},
|
||||||
"SpellIDs": u.GetSpellIDs(),
|
"SpellIDs": u.GetSpellIDs(),
|
||||||
"TownIDs": u.GetTownIDs(),
|
"TownIDs": u.GetTownIDs(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
106
internal/utils/scanner/scanner.go
Normal file
106
internal/utils/scanner/scanner.go
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
// Package scanner provides fast struct scanning for SQLite results without runtime reflection
|
||||||
|
package scanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"zombiezen.com/go/sqlite"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScanFunc defines how to scan a column into a field
|
||||||
|
type ScanFunc func(stmt *sqlite.Stmt, colIndex int, fieldPtr unsafe.Pointer)
|
||||||
|
|
||||||
|
// Scanner holds pre-compiled scanning information for a struct type
|
||||||
|
type Scanner struct {
|
||||||
|
scanners []ScanFunc
|
||||||
|
offsets []uintptr
|
||||||
|
columns []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Predefined scan functions for common types
|
||||||
|
func scanInt(stmt *sqlite.Stmt, colIndex int, fieldPtr unsafe.Pointer) {
|
||||||
|
*(*int)(fieldPtr) = stmt.ColumnInt(colIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanInt64(stmt *sqlite.Stmt, colIndex int, fieldPtr unsafe.Pointer) {
|
||||||
|
*(*int64)(fieldPtr) = stmt.ColumnInt64(colIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanString(stmt *sqlite.Stmt, colIndex int, fieldPtr unsafe.Pointer) {
|
||||||
|
*(*string)(fieldPtr) = stmt.ColumnText(colIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanFloat64(stmt *sqlite.Stmt, colIndex int, fieldPtr unsafe.Pointer) {
|
||||||
|
*(*float64)(fieldPtr) = stmt.ColumnFloat(colIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
func scanBool(stmt *sqlite.Stmt, colIndex int, fieldPtr unsafe.Pointer) {
|
||||||
|
*(*bool)(fieldPtr) = stmt.ColumnInt(colIndex) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a scanner for the given struct type using reflection once at creation time
|
||||||
|
func New[T any]() *Scanner {
|
||||||
|
var zero T
|
||||||
|
typ := reflect.TypeOf(zero)
|
||||||
|
|
||||||
|
var scanners []ScanFunc
|
||||||
|
var offsets []uintptr
|
||||||
|
var columns []string
|
||||||
|
|
||||||
|
for i := 0; i < typ.NumField(); i++ {
|
||||||
|
field := typ.Field(i)
|
||||||
|
|
||||||
|
// Skip fields without db tag or with "-"
|
||||||
|
dbTag := field.Tag.Get("db")
|
||||||
|
if dbTag == "" || dbTag == "-" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
columns = append(columns, dbTag)
|
||||||
|
offsets = append(offsets, field.Offset)
|
||||||
|
|
||||||
|
// Map field types to scan functions
|
||||||
|
switch field.Type.Kind() {
|
||||||
|
case reflect.Int:
|
||||||
|
scanners = append(scanners, scanInt)
|
||||||
|
case reflect.Int64:
|
||||||
|
scanners = append(scanners, scanInt64)
|
||||||
|
case reflect.String:
|
||||||
|
scanners = append(scanners, scanString)
|
||||||
|
case reflect.Float64:
|
||||||
|
scanners = append(scanners, scanFloat64)
|
||||||
|
case reflect.Bool:
|
||||||
|
scanners = append(scanners, scanBool)
|
||||||
|
default:
|
||||||
|
// Fallback to string for unknown types
|
||||||
|
scanners = append(scanners, scanString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Scanner{
|
||||||
|
scanners: scanners,
|
||||||
|
offsets: offsets,
|
||||||
|
columns: columns,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Columns returns the comma-separated column list for SQL queries
|
||||||
|
func (s *Scanner) Columns() string {
|
||||||
|
return strings.Join(s.columns, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan fills the destination struct with data from the SQLite statement
|
||||||
|
// This method uses no reflection and operates at near-native performance
|
||||||
|
func (s *Scanner) Scan(stmt *sqlite.Stmt, dest any) {
|
||||||
|
// Get pointer to the struct data
|
||||||
|
ptr := (*[2]uintptr)(unsafe.Pointer(&dest))
|
||||||
|
structPtr := unsafe.Pointer(ptr[1])
|
||||||
|
|
||||||
|
// Scan each field using pre-compiled function pointers and offsets
|
||||||
|
for i := 0; i < len(s.scanners); i++ {
|
||||||
|
fieldPtr := unsafe.Add(structPtr, s.offsets[i])
|
||||||
|
s.scanners[i](stmt, i, fieldPtr)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user