From 8edf6f7a77d6a0f8902fd346ff189cb53e106e41 Mon Sep 17 00:00:00 2001 From: Dustin Pianalto Date: Thu, 6 Mar 2025 13:43:36 -0900 Subject: [PATCH] return race results immediately instead of waiting for race to finish --- Dockerfile | 51 ++++++++++++- derby/derby.go | 192 +++++++++++++++++++++++++++---------------------- go.mod | 2 +- 3 files changed, 157 insertions(+), 88 deletions(-) diff --git a/Dockerfile b/Dockerfile index 0519ecb..626deb2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1 +1,50 @@ - \ No newline at end of file +# Start with a Go base image +FROM golang:1.21-bullseye as builder + +# Install dependencies for cross-compilation +RUN apt-get update && apt-get install -y \ + gcc-aarch64-linux-gnu \ + libc6-dev-arm64-cross \ + && rm -rf /var/lib/apt/lists/* + +# Set up working directory +WORKDIR /app + +# Copy go.mod and go.sum files first for better caching +COPY go.mod ./ +# COPY go.sum ./ # Uncomment if you have a go.sum file + +# Download dependencies +RUN go mod download + +# Copy the source code +COPY . . + +# Set environment variables for cross-compilation to ARM64 (Raspberry Pi 4) +ENV GOOS=linux +ENV GOARCH=arm64 +ENV CC=aarch64-linux-gnu-gcc +ENV CGO_ENABLED=1 + +# Build the application +RUN go build -o track-gopher-arm64 ./examples/main.go + +# Create a minimal runtime image +FROM arm64v8/debian:bullseye-slim + +# Install required runtime dependencies +RUN apt-get update && apt-get install -y \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +# Set up working directory +WORKDIR /app + +# Copy the binary from the builder stage +COPY --from=builder /app/track-gopher-arm64 . + +# Make the binary executable +RUN chmod +x ./track-gopher-arm64 + +# Command to run when the container starts +ENTRYPOINT ["./track-gopher-arm64"] \ No newline at end of file diff --git a/derby/derby.go b/derby/derby.go index b93f062..d37fd77 100644 --- a/derby/derby.go +++ b/derby/derby.go @@ -6,7 +6,6 @@ import ( "fmt" "io" "strconv" - "strings" "sync" "time" @@ -161,12 +160,16 @@ func (dc *DerbyClock) Results() []*Result { // readLoop continuously reads from the serial port func (dc *DerbyClock) readLoop() { + buffer := make([]byte, 0, 256) + for { select { case <-dc.stopReading: return default: - line, err := dc.reader.ReadString('\n') + // Read a single byte + b := make([]byte, 1) + _, err := dc.port.Read(b) if err != nil { if err != io.EOF { // Only log if it's not EOF @@ -176,103 +179,120 @@ func (dc *DerbyClock) readLoop() { continue } - // Process the line - dc.processLine(line) - } - } -} + // Add the byte to our buffer + buffer = append(buffer, b[0]) -// processLine handles a line of text from the derby clock -func (dc *DerbyClock) processLine(line string) { - // Trim any whitespace and carriage returns - line = strings.TrimSpace(line) - - if line == "C" { - // Race has started - dc.mu.Lock() - dc.status = StatusRunning - dc.results = nil - dc.mu.Unlock() - - // Send race start event - dc.eventChan <- Event{Type: EventRaceStart} - return - } - - // Check if this is a results line - if strings.Contains(line, "=") { - results := parseResults(line) + // Check for race start signal "C\r\n" + if len(buffer) >= 3 && string(buffer[len(buffer)-3:]) == "C\r\n" { + dc.mu.Lock() + dc.status = StatusRunning + dc.results = nil + dc.mu.Unlock() - dc.mu.Lock() - // Add results to our stored results - for _, result := range results { - dc.results = append(dc.results, result) + // Send race start event + dc.eventChan <- Event{Type: EventRaceStart} - // Send lane finish event - dc.eventChan <- Event{ - Type: EventLaneFinish, - Result: result, + // Clear the buffer + buffer = buffer[:0] + continue } - } - - // If we have results, the race is now finished - if len(results) > 0 { - dc.status = StatusFinished - // Send race complete event - dc.eventChan <- Event{Type: EventRaceComplete} + // Check for lane result pattern (n=t.ttttc) + // We need to look for an equals sign followed by digits, a period, more digits, and a finish character + if b[0] == '=' || b[0] == ' ' || b[0] == '\r' || b[0] == '\n' { + // These characters could indicate a complete result or a separator + // Try to extract a result from the buffer + result := dc.tryExtractResult(buffer) + if result != nil { + dc.mu.Lock() + // Add result to our stored results + dc.results = append(dc.results, result) + + // Determine finish place based on how many results we have + result.FinishPlace = len(dc.results) + dc.mu.Unlock() + + // Send lane finish event + dc.eventChan <- Event{ + Type: EventLaneFinish, + Result: result, + } + + // If we hit a newline, this might be the end of all results + if b[0] == '\n' { + // Check if we should consider the race complete + dc.mu.Lock() + if dc.status == StatusRunning { + dc.status = StatusFinished + dc.mu.Unlock() + + // Send race complete event + dc.eventChan <- Event{Type: EventRaceComplete} + } else { + dc.mu.Unlock() + } + + // Clear the buffer after a newline + buffer = buffer[:0] + } + } + } } - dc.mu.Unlock() } } -// parseResults parses a line of results from the derby clock -func parseResults(line string) []*Result { - parts := strings.Split(line, " ") - results := make([]*Result, 0, len(parts)) - - for i, part := range parts { - if !strings.Contains(part, "=") { - continue - } - - // Format is "n=0.0000c" - laneStr := strings.Split(part, "=")[0] - timeAndPlace := strings.Split(part, "=")[1] - - if len(timeAndPlace) < 2 { - continue - } - - // Extract time and place character - timeStr := timeAndPlace[:len(timeAndPlace)-1] - placeChar := timeAndPlace[len(timeAndPlace)-1:] +// tryExtractResult attempts to extract a lane result from the buffer +func (dc *DerbyClock) tryExtractResult(buffer []byte) *Result { + // Convert buffer to string for easier processing + bufStr := string(buffer) + + // Look for the pattern: n=t.ttttc + // where n is the lane number, t.tttt is the time, and c is the finish character + for i := 0; i < len(bufStr); i++ { + if bufStr[i] == '=' { + // Found an equals sign, try to extract a lane number before it + j := i - 1 + for j >= 0 && bufStr[j] >= '0' && bufStr[j] <= '9' { + j-- + } + j++ // Move back to the first digit - // Parse lane number - lane, err := strconv.Atoi(laneStr) - if err != nil { - continue - } + if j < i { // We found at least one digit + laneStr := bufStr[j:i] - // Parse time - timeVal, err := strconv.ParseFloat(timeStr, 64) - if err != nil { - continue - } - - // Determine place - place := i + 1 + // Now look for the time and finish character after the equals sign + k := i + 1 + for k < len(bufStr) && (bufStr[k] == '.' || (bufStr[k] >= '0' && bufStr[k] <= '9')) { + k++ + } - // Create result - result := &Result{ - Lane: lane, - Time: timeVal, - FinishPlace: place, - FinishSymbol: placeChar, + if k < len(bufStr) && k > i+1 { // We found a time and a finish character + timeStr := bufStr[i+1 : k] + finishChar := bufStr[k : k+1] + + // Parse the lane number + lane, err := strconv.Atoi(laneStr) + if err != nil { + continue + } + + // Parse the time + timeVal, err := strconv.ParseFloat(timeStr, 64) + if err != nil { + continue + } + + // Create and return the result + return &Result{ + Lane: lane, + Time: timeVal, + FinishPlace: 0, // Will be set by the caller + FinishSymbol: finishChar, + } + } + } } - - results = append(results, result) } - return results + return nil } diff --git a/go.mod b/go.mod index e6169c3..88ac78a 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module track-gopher -go 1.23.5 +go 1.24.1 require go.bug.st/serial v1.6.2