mirror of
https://github.com/WiiLink24/wfc-server.git
synced 2026-04-25 16:23:38 -05:00
Race results API integration
This commit is contained in:
parent
000aac92ec
commit
997f2fb6cd
58
api/mkw_rr.go
Normal file
58
api/mkw_rr.go
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"wwfc/qr2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RaceResultInfo struct {
|
||||||
|
Results map[int][]qr2.RaceResult `json:"results"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleMKWRR(w http.ResponseWriter, r *http.Request) {
|
||||||
|
u, err := url.Parse(r.URL.String())
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
query, err := url.ParseQuery(u.RawQuery)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groupNames := query["id"]
|
||||||
|
if len(groupNames) != 1 {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
results := qr2.GetRaceResultsForGroup(query["id"][0])
|
||||||
|
if results == nil {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonData []byte
|
||||||
|
if len(results) == 0 {
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
jsonData, err = json.Marshal(RaceResultInfo{
|
||||||
|
Results: results,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
w.Header().Set("Content-Length", strconv.Itoa(len(jsonData)))
|
||||||
|
w.Write(jsonData)
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,11 @@ package gpcm
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
"wwfc/common"
|
"wwfc/common"
|
||||||
"wwfc/logging"
|
"wwfc/logging"
|
||||||
"wwfc/qr2"
|
"wwfc/qr2"
|
||||||
|
"wwfc/race"
|
||||||
|
|
||||||
"github.com/logrusorgru/aurora/v3"
|
"github.com/logrusorgru/aurora/v3"
|
||||||
)
|
)
|
||||||
|
|
@ -96,13 +98,55 @@ func (g *GameSpySession) handleWWFCReport(command common.GameSpyCommand) {
|
||||||
logging.Info(g.ModuleName, "Race result version:", aurora.Yellow(raceResult.ClientReportVersion))
|
logging.Info(g.ModuleName, "Race result version:", aurora.Yellow(raceResult.ClientReportVersion))
|
||||||
|
|
||||||
player := raceResult.Player
|
player := raceResult.Player
|
||||||
|
|
||||||
logging.Info(g.ModuleName,
|
logging.Info(g.ModuleName,
|
||||||
"Player",
|
"Player",
|
||||||
"- PID:", aurora.Cyan(strconv.Itoa(player.Pid)),
|
"- PID:", aurora.Cyan(strconv.Itoa(player.Pid)),
|
||||||
"Time:", aurora.Cyan(strconv.Itoa(player.FinishTimeMs)), "ms",
|
"Time:", aurora.Cyan(strconv.Itoa(player.FinishTimeMs)), "ms",
|
||||||
"Char:", aurora.Cyan(strconv.Itoa(player.CharacterId)),
|
"Char:", aurora.Cyan(strconv.Itoa(player.CharacterId)),
|
||||||
"Kart:", aurora.Cyan(strconv.Itoa(player.KartId)))
|
"Kart:", aurora.Cyan(strconv.Itoa(player.KartId)))
|
||||||
//TODO : Hand off to qr2 for processing instead of logging each field here
|
|
||||||
|
// Hand off to qr2 for processing
|
||||||
|
qr2.ProcessMKWRaceResult(g.User.ProfileId, player.Pid, player.FinishTimeMs, player.CharacterId, player.KartId)
|
||||||
|
case "wl:mkw_race_start_time":
|
||||||
|
serverTime := time.Now().UnixMilli()
|
||||||
|
logging.Info(g.ModuleName,
|
||||||
|
"Race start time:", aurora.Yellow(value),
|
||||||
|
"Server time:", aurora.Yellow(strconv.FormatInt(serverTime, 10)))
|
||||||
|
|
||||||
|
// Parse client timestamp
|
||||||
|
clientTime, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error(g.ModuleName, "Failed to parse client timestamp:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize progress timing data for this race
|
||||||
|
race.RaceProgressTimings[g.User.ProfileId] = &race.RaceProgressTiming{
|
||||||
|
ClientStartTime: clientTime,
|
||||||
|
ServerStartTime: serverTime,
|
||||||
|
RecentDelays: make([]float64, 0, race.MaxDelays),
|
||||||
|
}
|
||||||
|
|
||||||
|
case "wl:mkw_race_progress_time":
|
||||||
|
race.HandleRaceProgressTime(g.User.ProfileId, value)
|
||||||
|
|
||||||
|
case "wl:mkw_race_finish_time":
|
||||||
|
serverTime := time.Now().UnixMilli()
|
||||||
|
logging.Info(g.ModuleName,
|
||||||
|
"Race finish time:", aurora.Yellow(value),
|
||||||
|
"Server time:", aurora.Yellow(strconv.FormatInt(serverTime, 10)))
|
||||||
|
|
||||||
|
// Parse client timestamp
|
||||||
|
clientTime, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error(g.ModuleName, "Failed to parse client finish timestamp:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate and log final race delays
|
||||||
|
race.LogRaceProgressDelay(g.User.ProfileId, clientTime, serverTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,6 +177,12 @@ func handleRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for /api/mkw_rr
|
||||||
|
if r.URL.Path == "/api/mkw_rr" {
|
||||||
|
api.HandleMKWRR(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
logging.Info("NAS", aurora.Yellow(r.Method), aurora.Cyan(r.URL), "via", aurora.Cyan(r.Host), "from", aurora.BrightCyan(r.RemoteAddr))
|
||||||
replyHTTPError(w, 404, "404 Not Found")
|
replyHTTPError(w, 404, "404 Not Found")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
109
qr2/group.go
109
qr2/group.go
|
|
@ -5,12 +5,14 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"wwfc/common"
|
"wwfc/common"
|
||||||
"wwfc/logging"
|
"wwfc/logging"
|
||||||
|
"wwfc/race"
|
||||||
|
|
||||||
"github.com/logrusorgru/aurora/v3"
|
"github.com/logrusorgru/aurora/v3"
|
||||||
)
|
)
|
||||||
|
|
@ -31,6 +33,28 @@ type Group struct {
|
||||||
MKWEngineClassID int
|
MKWEngineClassID int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RaceResultPlayer struct {
|
||||||
|
Pid int `json:"pid"`
|
||||||
|
FinishTimeMs int `json:"finish_time_ms"`
|
||||||
|
CharacterId int `json:"character_id"`
|
||||||
|
KartId int `json:"kart_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RaceResult struct {
|
||||||
|
ProfileID uint32
|
||||||
|
PlayerID int
|
||||||
|
FinishTime uint32
|
||||||
|
CharacterID uint32
|
||||||
|
VehicleID uint32
|
||||||
|
PlayerCount uint32
|
||||||
|
FinishPos int
|
||||||
|
CourseID int
|
||||||
|
EngineClassID int
|
||||||
|
Delta int
|
||||||
|
}
|
||||||
|
|
||||||
|
var raceResults = map[string]map[int][]RaceResult{} // GroupName -> RaceNumber -> []RaceResult
|
||||||
|
|
||||||
var groups = map[string]*Group{}
|
var groups = map[string]*Group{}
|
||||||
|
|
||||||
func processResvOK(moduleName string, matchVersion int, reservation common.MatchCommandDataReservation, resvOK common.MatchCommandDataResvOK, sender, destination *Session) bool {
|
func processResvOK(moduleName string, matchVersion int, reservation common.MatchCommandDataReservation, resvOK common.MatchCommandDataResvOK, sender, destination *Session) bool {
|
||||||
|
|
@ -525,6 +549,91 @@ func ProcessMKWSelectRecord(profileId uint32, key string, value string) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ProcessMKWRaceResult(profileId uint32, playerPid int, finishTimeMs int, characterId int, kartId int) {
|
||||||
|
moduleName := "QR2:MKWRaceResult:" + strconv.FormatUint(uint64(profileId), 10)
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
login := logins[profileId]
|
||||||
|
if login == nil {
|
||||||
|
mutex.Unlock()
|
||||||
|
logging.Warn(moduleName, "Received race result from non-existent profile ID", aurora.Cyan(profileId))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session := login.session
|
||||||
|
if session == nil {
|
||||||
|
mutex.Unlock()
|
||||||
|
logging.Warn(moduleName, "Received race result from profile ID", aurora.Cyan(profileId), "but no session exists")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
mutex.Unlock()
|
||||||
|
|
||||||
|
group := session.groupPointer
|
||||||
|
if group == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if group.MKWRaceNumber == 0 {
|
||||||
|
logging.Error(moduleName, "Received race result but no races have been started")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the accumulated lag from race progress timing
|
||||||
|
var delta int
|
||||||
|
if timing, exists := race.RaceProgressTimings[profileId]; exists && len(timing.RecentDelays) > 0 {
|
||||||
|
// Get the final smoothed delay and convert to int
|
||||||
|
finalDelay := timing.RecentDelays[len(timing.RecentDelays)-1]
|
||||||
|
delta = int(math.Round(finalDelay))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert race result data to internal format
|
||||||
|
raceResultData := RaceResult{
|
||||||
|
ProfileID: profileId,
|
||||||
|
PlayerID: playerPid,
|
||||||
|
FinishTime: uint32(finishTimeMs),
|
||||||
|
CharacterID: uint32(characterId),
|
||||||
|
VehicleID: uint32(kartId),
|
||||||
|
PlayerCount: 12, // Default value, could be extracted from race data if available
|
||||||
|
FinishPos: 0, // Default value, could be calculated from finish times
|
||||||
|
CourseID: group.MKWCourseID,
|
||||||
|
EngineClassID: group.MKWEngineClassID,
|
||||||
|
Delta: delta,
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
if raceResults[group.GroupName] == nil {
|
||||||
|
raceResults[group.GroupName] = map[int][]RaceResult{}
|
||||||
|
}
|
||||||
|
|
||||||
|
raceResults[group.GroupName][group.MKWRaceNumber] = append(raceResults[group.GroupName][group.MKWRaceNumber], raceResultData)
|
||||||
|
|
||||||
|
logging.Info(moduleName, "Stored race result for profile", aurora.BrightCyan(strconv.FormatUint(uint64(profileId), 10)),
|
||||||
|
"Race #:", aurora.Cyan(strconv.Itoa(group.MKWRaceNumber)),
|
||||||
|
"Course:", aurora.Cyan(strconv.Itoa(group.MKWCourseID)),
|
||||||
|
"Delta:", aurora.Cyan(strconv.Itoa(delta)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetRaceResultsForGroup(groupName string) map[int][]RaceResult {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
|
||||||
|
groupResults, ok := raceResults[groupName]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a copy to prevent external modification
|
||||||
|
copiedRaceResults := make(map[int][]RaceResult)
|
||||||
|
for raceNumber, results := range groupResults {
|
||||||
|
copiedRaceResults[raceNumber] = make([]RaceResult, len(results))
|
||||||
|
copy(copiedRaceResults[raceNumber], results)
|
||||||
|
}
|
||||||
|
|
||||||
|
return copiedRaceResults
|
||||||
|
}
|
||||||
|
|
||||||
// saveGroups saves the current groups state to disk.
|
// saveGroups saves the current groups state to disk.
|
||||||
// Expects the mutex to be locked.
|
// Expects the mutex to be locked.
|
||||||
func saveGroups() error {
|
func saveGroups() error {
|
||||||
|
|
|
||||||
199
race/race_progress.go
Normal file
199
race/race_progress.go
Normal file
|
|
@ -0,0 +1,199 @@
|
||||||
|
package race
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
"wwfc/logging"
|
||||||
|
|
||||||
|
"github.com/logrusorgru/aurora/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxDelays = 20 // Maximum delays to track in rolling window
|
||||||
|
SmoothingFactor = 0.3 // For exponential smoothing algorithm
|
||||||
|
)
|
||||||
|
|
||||||
|
type DelayMeasurement struct {
|
||||||
|
Timestamp int64 // Server timestamp when delay was measured
|
||||||
|
RawDelay float64 // Raw delay measurement
|
||||||
|
SmoothedDelay float64 // Smoothed delay using exponential smoothing
|
||||||
|
}
|
||||||
|
|
||||||
|
type RaceProgressTiming struct {
|
||||||
|
ClientStartTime int64 // Client's race start time (absolute timestamp)
|
||||||
|
ServerStartTime int64 // Server's race start time (absolute timestamp)
|
||||||
|
RecentDelays []float64 // Rolling window of recent delays
|
||||||
|
DelayData []DelayMeasurement // All delay measurements for this race
|
||||||
|
}
|
||||||
|
|
||||||
|
// Global map to track race progress timing data by profile ID
|
||||||
|
var RaceProgressTimings = make(map[uint32]*RaceProgressTiming)
|
||||||
|
|
||||||
|
// logFinalRaceDelay logs the final smoothed and unsmoothed delays for a race to a separate file
|
||||||
|
func logFinalRaceDelay(pid uint32, finalSmoothedDelay float64, finalUnsmoothedDelay float64) {
|
||||||
|
file, err := os.OpenFile("delay_logs/race_progress_delays.txt", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("race", "Failed to open race progress delays log file:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Write to file: PID,Final_Smoothed_Delay,Final_Unsmoothed_Delay
|
||||||
|
logEntry := fmt.Sprintf("%d,%.2f,%.2f\n", pid, finalSmoothedDelay, finalUnsmoothedDelay)
|
||||||
|
_, err = file.WriteString(logEntry)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("race", "Failed to write to race progress delays log file:", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// logRaceProgressDelay stores delay measurements in memory for later batch writing
|
||||||
|
func logRaceProgressDelay(pid uint32, timestamp int64, rawDelay float64, smoothedDelay float64) {
|
||||||
|
// Get the race start time to use as the race identifier
|
||||||
|
if timing, exists := RaceProgressTimings[pid]; exists {
|
||||||
|
// Store delay data in memory for batch writing at race end
|
||||||
|
// We'll add a field to RaceProgressTiming to store the delay data
|
||||||
|
if timing.DelayData == nil {
|
||||||
|
timing.DelayData = make([]DelayMeasurement, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
measurement := DelayMeasurement{
|
||||||
|
Timestamp: timestamp,
|
||||||
|
RawDelay: rawDelay,
|
||||||
|
SmoothedDelay: smoothedDelay,
|
||||||
|
}
|
||||||
|
timing.DelayData = append(timing.DelayData, measurement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// addDelayToWindow adds a new delay to the rolling window and returns the smoothed delay
|
||||||
|
func addDelayToWindow(timing *RaceProgressTiming, newDelay float64) float64 {
|
||||||
|
// Add new delay to the window
|
||||||
|
timing.RecentDelays = append(timing.RecentDelays, newDelay)
|
||||||
|
|
||||||
|
// Maintain rolling window size
|
||||||
|
if len(timing.RecentDelays) > MaxDelays {
|
||||||
|
timing.RecentDelays = timing.RecentDelays[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate smoothed delay using exponential smoothing
|
||||||
|
var smoothedDelay float64
|
||||||
|
if len(timing.RecentDelays) == 1 {
|
||||||
|
// First delay, no smoothing needed
|
||||||
|
smoothedDelay = newDelay
|
||||||
|
} else {
|
||||||
|
// Apply exponential smoothing
|
||||||
|
previousSmoothed := timing.RecentDelays[len(timing.RecentDelays)-2]
|
||||||
|
smoothedDelay = SmoothingFactor*newDelay + (1-SmoothingFactor)*previousSmoothed
|
||||||
|
}
|
||||||
|
|
||||||
|
return smoothedDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRaceProgressTime handles the wl:mkw_race_progress_time report case
|
||||||
|
func HandleRaceProgressTime(pid uint32, value string) {
|
||||||
|
// Parse client absolute timestamp (this is the client's current time)
|
||||||
|
clientTimestamp, err := strconv.ParseInt(value, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("race", "Failed to parse client progress timestamp:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
serverTime := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
// logging.Info("race", "Race progress time:", aurora.Yellow(value), "Server time:", aurora.Yellow(strconv.FormatInt(serverTime, 10)))
|
||||||
|
|
||||||
|
// Get or create progress timing for this player
|
||||||
|
timing, exists := RaceProgressTimings[pid]
|
||||||
|
if !exists {
|
||||||
|
logging.Warn("race", "No race start timing found for progress report from profile", aurora.BrightCyan(strconv.FormatUint(uint64(pid), 10)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate client elapsed time: current client timestamp - client start timestamp
|
||||||
|
clientElapsedTime := clientTimestamp - timing.ClientStartTime
|
||||||
|
|
||||||
|
// Calculate server elapsed time: current server time - server start time
|
||||||
|
serverElapsedTime := serverTime - timing.ServerStartTime
|
||||||
|
|
||||||
|
// Calculate delay: client elapsed time - server elapsed time
|
||||||
|
// This gives us the time difference between what the client thinks has passed vs what the server thinks has passed
|
||||||
|
delay := float64(clientElapsedTime - serverElapsedTime)
|
||||||
|
|
||||||
|
// Add to rolling window and get smoothed delay
|
||||||
|
smoothedDelay := addDelayToWindow(timing, delay)
|
||||||
|
|
||||||
|
// Log this delay measurement to per-race CSV file
|
||||||
|
logRaceProgressDelay(pid, serverTime, delay, smoothedDelay)
|
||||||
|
|
||||||
|
// logging.Info("race",
|
||||||
|
// "Progress delay:", aurora.Cyan(fmt.Sprintf("%.2f", delay)),
|
||||||
|
// "Smoothed delay:", aurora.Cyan(fmt.Sprintf("%.2f", smoothedDelay)),
|
||||||
|
// "Delays tracked:", aurora.Cyan(strconv.Itoa(len(timing.RecentDelays))))
|
||||||
|
|
||||||
|
// Progress delays are tracked in memory for smoothing calculation
|
||||||
|
// Final delays are logged when race finishes
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogRaceProgressDelay logs final race delays and cleans up timing data
|
||||||
|
func LogRaceProgressDelay(pid uint32, clientFinishTime int64, serverFinishTime int64) {
|
||||||
|
if timing, exists := RaceProgressTimings[pid]; exists {
|
||||||
|
// Calculate final unsmoothed delay using start and finish timestamps
|
||||||
|
finalUnsmoothedDelay := float64((clientFinishTime - timing.ClientStartTime) - (serverFinishTime - timing.ServerStartTime))
|
||||||
|
|
||||||
|
// Get final smoothed delay from the rolling window
|
||||||
|
var finalSmoothedDelay float64
|
||||||
|
if len(timing.RecentDelays) > 0 {
|
||||||
|
finalSmoothedDelay = timing.RecentDelays[len(timing.RecentDelays)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log both delays
|
||||||
|
logFinalRaceDelay(pid, finalSmoothedDelay, finalUnsmoothedDelay)
|
||||||
|
|
||||||
|
// Write all delay measurements to CSV file in batch
|
||||||
|
writeRaceProgressDelaysToFile(pid, timing)
|
||||||
|
|
||||||
|
// Clean up timing data for this player
|
||||||
|
delete(RaceProgressTimings, pid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeRaceProgressDelaysToFile writes all stored delay measurements to a CSV file
|
||||||
|
func writeRaceProgressDelaysToFile(pid uint32, timing *RaceProgressTiming) {
|
||||||
|
// Create filename with PID and race start time for uniqueness (one file per race per player)
|
||||||
|
filename := fmt.Sprintf("delay_logs/race_delays_%d_%d.csv", pid, timing.ClientStartTime)
|
||||||
|
|
||||||
|
file, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("race", "Failed to open race delay CSV file:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Write header if file is empty
|
||||||
|
fileInfo, _ := file.Stat()
|
||||||
|
if fileInfo.Size() == 0 {
|
||||||
|
header := "timestamp,raw_delay,smoothed_delay\n"
|
||||||
|
_, err = file.WriteString(header)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("race", "Failed to write CSV header:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write all delay measurements in batch
|
||||||
|
for _, measurement := range timing.DelayData {
|
||||||
|
logEntry := fmt.Sprintf("%d,%.2f,%.2f\n", measurement.Timestamp, measurement.RawDelay, measurement.SmoothedDelay)
|
||||||
|
_, err = file.WriteString(logEntry)
|
||||||
|
if err != nil {
|
||||||
|
logging.Error("race", "Failed to write to race delay CSV file:", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CleanupRaceProgressTiming cleans up race progress timing data for a player
|
||||||
|
func CleanupRaceProgressTiming(pid uint32) {
|
||||||
|
delete(RaceProgressTimings, pid)
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user