mirror of
https://github.com/Team254/cheesy-arena-lite.git
synced 2026-03-09 13:46:44 -04:00
Calculate event lateness periodically to improve accuracy.
This commit is contained in:
@@ -13,15 +13,19 @@ import (
|
||||
"github.com/Team254/cheesy-arena/partner"
|
||||
"github.com/Team254/cheesy-arena/plc"
|
||||
"log"
|
||||
"math"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
arenaLoopPeriodMs = 10
|
||||
dsPacketPeriodMs = 250
|
||||
periodicTaskPeriodSec = 30
|
||||
matchEndScoreDwellSec = 3
|
||||
postTimeoutSec = 4
|
||||
preLoadNextMatchDelaySec = 5
|
||||
earlyLateThresholdMin = 2.5
|
||||
MaxMatchGapMin = 20
|
||||
)
|
||||
|
||||
// Progression of match states.
|
||||
@@ -59,6 +63,8 @@ type Arena struct {
|
||||
RedRealtimeScore *RealtimeScore
|
||||
BlueRealtimeScore *RealtimeScore
|
||||
lastDsPacketTime time.Time
|
||||
lastPeriodicTaskTime time.Time
|
||||
EventStatusMessage string
|
||||
FieldVolunteers bool
|
||||
FieldReset bool
|
||||
AudienceDisplayMode string
|
||||
@@ -512,6 +518,10 @@ func (arena *Arena) Run() {
|
||||
|
||||
for {
|
||||
arena.Update()
|
||||
if time.Since(arena.lastPeriodicTaskTime).Seconds() >= periodicTaskPeriodSec {
|
||||
arena.lastPeriodicTaskTime = time.Now()
|
||||
go arena.runPeriodicTasks()
|
||||
}
|
||||
time.Sleep(time.Millisecond * arenaLoopPeriodMs)
|
||||
}
|
||||
}
|
||||
@@ -854,3 +864,73 @@ func (arena *Arena) alliancePostMatchScoreReady(alliance string) bool {
|
||||
numPanels := arena.ScoringPanelRegistry.GetNumPanels(alliance)
|
||||
return numPanels > 0 && arena.ScoringPanelRegistry.GetNumScoreCommitted(alliance) >= numPanels
|
||||
}
|
||||
|
||||
// Performs any actions that need to run at the interval specified by periodicTaskPeriodSec.
|
||||
func (arena *Arena) runPeriodicTasks() {
|
||||
// Check how early or late the event is running and publish an update to the displays that show it.
|
||||
newEventStatusMessage := arena.getEventStatusMessage()
|
||||
if newEventStatusMessage != arena.EventStatusMessage {
|
||||
arena.EventStatusMessage = newEventStatusMessage
|
||||
arena.EventStatusNotifier.Notify()
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the string that indicates how early or late the event is running.
|
||||
func (arena *Arena) getEventStatusMessage() string {
|
||||
currentMatch := arena.CurrentMatch
|
||||
if currentMatch.Type != "practice" && currentMatch.Type != "qualification" {
|
||||
// Only practice and qualification matches have a strict schedule.
|
||||
return ""
|
||||
}
|
||||
if currentMatch.Status == "complete" {
|
||||
// This is a replay or otherwise unpredictable situation.
|
||||
return ""
|
||||
}
|
||||
|
||||
var minutesLate float64
|
||||
if arena.MatchState > PreMatch && arena.MatchState < PostMatch {
|
||||
// The match is in progress; simply calculate lateness from its start time.
|
||||
minutesLate = currentMatch.StartedAt.Sub(currentMatch.Time).Minutes()
|
||||
} else {
|
||||
// We need to check the adjacent matches to accurately determine lateness.
|
||||
matches, _ := arena.Database.GetMatchesByType(currentMatch.Type)
|
||||
|
||||
previousMatchIndex := -1
|
||||
nextMatchIndex := len(matches)
|
||||
for i, match := range matches {
|
||||
if match.Id == currentMatch.Id {
|
||||
previousMatchIndex = i - 1
|
||||
nextMatchIndex = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if arena.MatchState == PreMatch {
|
||||
currentMinutesLate := time.Now().Sub(currentMatch.Time).Minutes()
|
||||
if previousMatchIndex >= 0 &&
|
||||
currentMatch.Time.Sub(matches[previousMatchIndex].Time).Minutes() <= MaxMatchGapMin {
|
||||
previousMatch := matches[previousMatchIndex]
|
||||
previousMinutesLate := previousMatch.StartedAt.Sub(previousMatch.Time).Minutes()
|
||||
minutesLate = math.Max(previousMinutesLate, currentMinutesLate)
|
||||
} else {
|
||||
minutesLate = math.Max(currentMinutesLate, 0)
|
||||
}
|
||||
} else if arena.MatchState == PostMatch {
|
||||
currentMinutesLate := currentMatch.StartedAt.Sub(currentMatch.Time).Minutes()
|
||||
if nextMatchIndex < len(matches) {
|
||||
nextMatch := matches[nextMatchIndex]
|
||||
nextMinutesLate := time.Now().Sub(nextMatch.Time).Minutes()
|
||||
minutesLate = math.Max(currentMinutesLate, nextMinutesLate)
|
||||
} else {
|
||||
minutesLate = currentMinutesLate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if minutesLate > earlyLateThresholdMin {
|
||||
return fmt.Sprintf("Event is running %d minutes late", int(minutesLate))
|
||||
} else if minutesLate < -earlyLateThresholdMin {
|
||||
return fmt.Sprintf("Event is running %d minutes early", int(-minutesLate))
|
||||
}
|
||||
return "Event is running on schedule"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type ArenaNotifiers struct {
|
||||
ArenaStatusNotifier *websocket.Notifier
|
||||
AudienceDisplayModeNotifier *websocket.Notifier
|
||||
DisplayConfigurationNotifier *websocket.Notifier
|
||||
EventStatusNotifier *websocket.Notifier
|
||||
LowerThirdNotifier *websocket.Notifier
|
||||
MatchLoadNotifier *websocket.Notifier
|
||||
MatchTimeNotifier *websocket.Notifier
|
||||
@@ -57,6 +58,7 @@ func (arena *Arena) configureNotifiers() {
|
||||
arena.generateAudienceDisplayModeMessage)
|
||||
arena.DisplayConfigurationNotifier = websocket.NewNotifier("displayConfiguration",
|
||||
arena.generateDisplayConfigurationMessage)
|
||||
arena.EventStatusNotifier = websocket.NewNotifier("eventStatus", arena.generateEventStatusMessage)
|
||||
arena.LowerThirdNotifier = websocket.NewNotifier("lowerThird", arena.generateLowerThirdMessage)
|
||||
arena.MatchLoadNotifier = websocket.NewNotifier("matchLoad", arena.generateMatchLoadMessage)
|
||||
arena.MatchTimeNotifier = websocket.NewNotifier("matchTime", arena.generateMatchTimeMessage)
|
||||
@@ -117,6 +119,10 @@ func (arena *Arena) generateDisplayConfigurationMessage() interface{} {
|
||||
return &DisplayConfigurationMessage{displaysCopy, displayUrls}
|
||||
}
|
||||
|
||||
func (arena *Arena) generateEventStatusMessage() interface{} {
|
||||
return arena.EventStatusMessage
|
||||
}
|
||||
|
||||
func (arena *Arena) generateLowerThirdMessage() interface{} {
|
||||
return &struct {
|
||||
LowerThird *model.LowerThird
|
||||
|
||||
@@ -644,3 +644,100 @@ func TestSaveTeamHasConnected(t *testing.T) {
|
||||
assert.Equal(t, "San Jose", teams[5].City)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEventStatusMessage(t *testing.T) {
|
||||
arena := setupTestArena(t)
|
||||
|
||||
arena.LoadTestMatch()
|
||||
assert.Equal(t, "", arena.getEventStatusMessage())
|
||||
|
||||
arena.Database.CreateMatch(&model.Match{Type: "qualification", DisplayName: "1"})
|
||||
arena.Database.CreateMatch(&model.Match{Type: "qualification", DisplayName: "2"})
|
||||
matches, _ := arena.Database.GetMatchesByType("qualification")
|
||||
assert.Equal(t, 2, len(matches))
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(300*time.Second), time.Time{}, false)
|
||||
arena.CurrentMatch = &matches[0]
|
||||
arena.MatchState = PreMatch
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(60*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(-60*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(-120*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(-180*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running 3 minutes late", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(181*time.Second), time.Now(), false)
|
||||
arena.MatchState = AutoPeriod
|
||||
assert.Equal(t, "Event is running 3 minutes early", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(-300*time.Second), time.Now().Add(-601*time.Second), false)
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(481*time.Second), time.Time{}, false)
|
||||
arena.MatchState = PostMatch
|
||||
assert.Equal(t, "Event is running 5 minutes early", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(181*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running 3 minutes early", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(-60*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(-180*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running 3 minutes late", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[0], time.Now().Add(-300*time.Second), time.Now().Add(-601*time.Second), true)
|
||||
assert.Equal(t, "", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(900*time.Second), time.Time{}, false)
|
||||
arena.CurrentMatch = &matches[1]
|
||||
arena.MatchState = PreMatch
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(899*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running 5 minutes early", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(60*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(-120*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running on schedule", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(-180*time.Second), time.Time{}, false)
|
||||
assert.Equal(t, "Event is running 3 minutes late", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now().Add(-180*time.Second), time.Now().Add(-541*time.Second), false)
|
||||
arena.MatchState = TeleopPeriod
|
||||
assert.Equal(t, "Event is running 6 minutes early", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now(), time.Now().Add(481*time.Second), false)
|
||||
arena.MatchState = PostMatch
|
||||
assert.Equal(t, "Event is running 8 minutes late", arena.getEventStatusMessage())
|
||||
|
||||
setMatch(arena.Database, &matches[1], time.Now(), time.Now().Add(481*time.Second), true)
|
||||
assert.Equal(t, "", arena.getEventStatusMessage())
|
||||
|
||||
// Check other match types.
|
||||
arena.MatchState = PreMatch
|
||||
arena.CurrentMatch = &model.Match{Type: "practice", Time: time.Now().Add(-181 * time.Second)}
|
||||
assert.Equal(t, "Event is running 3 minutes late", arena.getEventStatusMessage())
|
||||
|
||||
arena.CurrentMatch = &model.Match{Type: "elimination", Time: time.Now().Add(-181 * time.Second)}
|
||||
assert.Equal(t, "", arena.getEventStatusMessage())
|
||||
}
|
||||
|
||||
func setMatch(database *model.Database, match *model.Match, matchTime time.Time, startedAt time.Time, isComplete bool) {
|
||||
match.Time = matchTime
|
||||
match.StartedAt = startedAt
|
||||
if isComplete {
|
||||
match.Status = "complete"
|
||||
} else {
|
||||
match.Status = ""
|
||||
}
|
||||
_ = database.SaveMatch(match)
|
||||
}
|
||||
|
||||
@@ -75,3 +75,11 @@ body {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
#eventStatusMessage {
|
||||
margin-top: 10px;
|
||||
font-size: 25px;
|
||||
font-family: "FuturaLTBold";
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ h1 {
|
||||
.avatars {
|
||||
line-height: 51px;
|
||||
}
|
||||
#footer {
|
||||
#eventStatusMessage {
|
||||
font-size: 25px;
|
||||
font-family: "FuturaLTBold";
|
||||
color: #fff;
|
||||
|
||||
@@ -225,6 +225,11 @@ var handleAllianceStationDisplayMode = function(data) {
|
||||
$("input[name=allianceStationDisplay][value=" + data + "]").prop("checked", true);
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the event status message.
|
||||
var handleEventStatus = function(data) {
|
||||
$("#eventStatusMessage").text(data);
|
||||
};
|
||||
|
||||
$(function() {
|
||||
// Activate tooltips above the status headers.
|
||||
$("[data-toggle=tooltip]").tooltip({"placement": "top"});
|
||||
@@ -234,9 +239,10 @@ $(function() {
|
||||
allianceStationDisplayMode: function(event) { handleAllianceStationDisplayMode(event.data); },
|
||||
arenaStatus: function(event) { handleArenaStatus(event.data); },
|
||||
audienceDisplayMode: function(event) { handleAudienceDisplayMode(event.data); },
|
||||
eventStatus: function(event) { handleEventStatus(event.data); },
|
||||
matchTime: function(event) { handleMatchTime(event.data); },
|
||||
matchTiming: function(event) { handleMatchTiming(event.data); },
|
||||
realtimeScore: function(event) { handleRealtimeScore(event.data); },
|
||||
scoringStatus: function(event) { handleScoringStatus(event.data); }
|
||||
scoringStatus: function(event) { handleScoringStatus(event.data); },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,13 +79,20 @@ var setHighestPlayedMatch = function(highestPlayedMatch) {
|
||||
}
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the event status message.
|
||||
var handleEventStatus = function(data) {
|
||||
$("#eventStatusMessage").text(data);
|
||||
};
|
||||
|
||||
$(function() {
|
||||
// Read the configuration for this display from the URL query string.
|
||||
var urlParams = new URLSearchParams(window.location.search);
|
||||
scrollMsPerRow = urlParams.get("scrollMsPerRow");
|
||||
|
||||
// Set up the websocket back to the server. Used only for remote forcing of reloads.
|
||||
websocket = new CheesyWebsocket("/displays/pit/websocket", {});
|
||||
websocket = new CheesyWebsocket("/displays/pit/websocket", {
|
||||
eventStatus: function(event) { handleEventStatus(event.data); },
|
||||
});
|
||||
|
||||
updateStaticRankings();
|
||||
});
|
||||
|
||||
@@ -28,11 +28,17 @@ var handleMatchTime = function(data) {
|
||||
});
|
||||
};
|
||||
|
||||
// Handles a websocket message to update the event status message.
|
||||
var handleEventStatus = function(data) {
|
||||
$("#eventStatusMessage").text(data);
|
||||
};
|
||||
|
||||
$(function() {
|
||||
// Set up the websocket back to the server.
|
||||
websocket = new CheesyWebsocket("/displays/queueing/websocket", {
|
||||
eventStatus: function(event) { handleEventStatus(event.data); },
|
||||
matchLoad: function(event) { handleMatchLoad(event.data); },
|
||||
matchTime: function(event) { handleMatchTime(event.data); },
|
||||
matchTiming: function(event) { handleMatchTiming(event.data); }
|
||||
matchTiming: function(event) { handleMatchTiming(event.data); },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -216,6 +216,9 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div id="eventStatusMessage" class="col-lg-12"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
<span id="highestPlayedMatch"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div id="eventStatusMessage"></div>
|
||||
</div>
|
||||
<script id="standingsTemplate" type="text/x-handlebars-template">
|
||||
<tbody>
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<div id="footer" class="col-lg-10 col-lg-offset-1">{{.StatusMessage}}</div>
|
||||
<div id="eventStatusMessage" class="col-lg-10 col-lg-offset-1"></div>
|
||||
</body>
|
||||
<script src="/static/js/lib/jquery.min.js"></script>
|
||||
<script src="/static/js/lib/jquery.json-2.4.min.js"></script>
|
||||
|
||||
@@ -177,7 +177,7 @@ func (web *Web) matchPlayWebsocketHandler(w http.ResponseWriter, r *http.Request
|
||||
// Subscribe the websocket to the notifiers whose messages will be passed on to the client, in a separate goroutine.
|
||||
go ws.HandleNotifiers(web.arena.MatchTimingNotifier, web.arena.ArenaStatusNotifier, web.arena.MatchTimeNotifier,
|
||||
web.arena.RealtimeScoreNotifier, web.arena.ScoringStatusNotifier, web.arena.AudienceDisplayModeNotifier,
|
||||
web.arena.AllianceStationDisplayModeNotifier)
|
||||
web.arena.AllianceStationDisplayModeNotifier, web.arena.EventStatusNotifier)
|
||||
|
||||
// Loop, waiting for commands and responding to them, until the client closes the connection.
|
||||
for {
|
||||
|
||||
@@ -258,6 +258,7 @@ func TestMatchPlayWebsocketCommands(t *testing.T) {
|
||||
readWebsocketType(t, ws, "scoringStatus")
|
||||
readWebsocketType(t, ws, "audienceDisplayMode")
|
||||
readWebsocketType(t, ws, "allianceStationDisplayMode")
|
||||
readWebsocketType(t, ws, "eventStatus")
|
||||
|
||||
// Test that a server-side error is communicated to the client.
|
||||
ws.Write("nonexistenttype", nil)
|
||||
@@ -348,6 +349,7 @@ func TestMatchPlayWebsocketNotifications(t *testing.T) {
|
||||
readWebsocketType(t, ws, "scoringStatus")
|
||||
readWebsocketType(t, ws, "audienceDisplayMode")
|
||||
readWebsocketType(t, ws, "allianceStationDisplayMode")
|
||||
readWebsocketType(t, ws, "eventStatus")
|
||||
|
||||
web.arena.AllianceStations["R1"].Bypass = true
|
||||
web.arena.AllianceStations["R2"].Bypass = true
|
||||
|
||||
@@ -49,5 +49,6 @@ func (web *Web) pitDisplayWebsocketHandler(w http.ResponseWriter, r *http.Reques
|
||||
defer ws.Close()
|
||||
|
||||
// Subscribe the websocket to the notifiers whose messages will be passed on to the client.
|
||||
ws.HandleNotifiers(web.arena.DisplayConfigurationNotifier, web.arena.ReloadDisplaysNotifier)
|
||||
ws.HandleNotifiers(web.arena.EventStatusNotifier, web.arena.DisplayConfigurationNotifier,
|
||||
web.arena.ReloadDisplaysNotifier)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ func TestPitDisplayWebsocket(t *testing.T) {
|
||||
ws := websocket.NewTestWebsocket(conn)
|
||||
|
||||
// Should get a few status updates right after connection.
|
||||
readWebsocketType(t, ws, "eventStatus")
|
||||
readWebsocketType(t, ws, "displayConfiguration")
|
||||
|
||||
// Check forced reloading as that is the only purpose the pit websocket serves.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/Team254/cheesy-arena/field"
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"github.com/Team254/cheesy-arena/websocket"
|
||||
"net/http"
|
||||
@@ -14,9 +14,7 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
earlyLateThresholdMin = 2
|
||||
maxGapMin = 20
|
||||
numMatchesToShow = 5
|
||||
numMatchesToShow = 5
|
||||
)
|
||||
|
||||
// Renders the queueing display that shows upcoming matches and timing information.
|
||||
@@ -41,7 +39,7 @@ func (web *Web) queueingDisplayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Don't include any more matches if there is a significant gap before the next one.
|
||||
if i+1 < len(matches) && matches[i+1].Time.Sub(match.Time) > maxGapMin*time.Minute {
|
||||
if i+1 < len(matches) && matches[i+1].Time.Sub(match.Time) > field.MaxMatchGapMin*time.Minute {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -56,9 +54,7 @@ func (web *Web) queueingDisplayHandler(w http.ResponseWriter, r *http.Request) {
|
||||
*model.EventSettings
|
||||
MatchTypePrefix string
|
||||
Matches []model.Match
|
||||
StatusMessage string
|
||||
}{web.arena.EventSettings, web.arena.CurrentMatch.TypePrefix(), upcomingMatches,
|
||||
generateEventStatusMessage(web.arena.CurrentMatch.Type, matches)}
|
||||
}{web.arena.EventSettings, web.arena.CurrentMatch.TypePrefix(), upcomingMatches}
|
||||
err = template.ExecuteTemplate(w, "queueing_display.html", data)
|
||||
if err != nil {
|
||||
handleWebErr(w, err)
|
||||
@@ -84,35 +80,5 @@ func (web *Web) queueingDisplayWebsocketHandler(w http.ResponseWriter, r *http.R
|
||||
|
||||
// Subscribe the websocket to the notifiers whose messages will be passed on to the client.
|
||||
ws.HandleNotifiers(web.arena.MatchTimingNotifier, web.arena.MatchLoadNotifier, web.arena.MatchTimeNotifier,
|
||||
web.arena.DisplayConfigurationNotifier, web.arena.ReloadDisplaysNotifier)
|
||||
}
|
||||
|
||||
// Returns a message indicating how early or late the event is running.
|
||||
func generateEventStatusMessage(matchType string, matches []model.Match) string {
|
||||
if matchType != "practice" && matchType != "qualification" {
|
||||
// Only practice and qualification matches have a strict schedule.
|
||||
return ""
|
||||
}
|
||||
if len(matches) == 0 || matches[len(matches)-1].Status == "complete" {
|
||||
// All matches of the current type are complete.
|
||||
return ""
|
||||
}
|
||||
|
||||
for i := len(matches) - 1; i >= 0; i-- {
|
||||
match := matches[i]
|
||||
if match.Status == "complete" {
|
||||
if i+1 < len(matches) && matches[i+1].Time.Sub(match.Time) > maxGapMin*time.Minute {
|
||||
break
|
||||
} else {
|
||||
minutesLate := match.StartedAt.Sub(match.Time).Minutes()
|
||||
if minutesLate > earlyLateThresholdMin {
|
||||
return fmt.Sprintf("Event is running %d minutes late", int(minutesLate))
|
||||
} else if minutesLate < -earlyLateThresholdMin {
|
||||
return fmt.Sprintf("Event is running %d minutes early", int(-minutesLate))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "Event is running on schedule"
|
||||
web.arena.EventStatusNotifier, web.arena.DisplayConfigurationNotifier, web.arena.ReloadDisplaysNotifier)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,10 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"github.com/Team254/cheesy-arena/model"
|
||||
"github.com/Team254/cheesy-arena/websocket"
|
||||
gorillawebsocket "github.com/gorilla/websocket"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestQueueingDisplay(t *testing.T) {
|
||||
@@ -34,59 +32,6 @@ func TestQueueingDisplayWebsocket(t *testing.T) {
|
||||
readWebsocketType(t, ws, "matchTiming")
|
||||
readWebsocketType(t, ws, "matchLoad")
|
||||
readWebsocketType(t, ws, "matchTime")
|
||||
readWebsocketType(t, ws, "eventStatus")
|
||||
readWebsocketType(t, ws, "displayConfiguration")
|
||||
}
|
||||
|
||||
func TestQueueingStatusMessage(t *testing.T) {
|
||||
assert.Equal(t, "", generateEventStatusMessage("practice", []model.Match{}))
|
||||
|
||||
matches := make([]model.Match, 3)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("practice", matches))
|
||||
|
||||
// Check within threshold considered to be on time.
|
||||
setMatchLateness(&matches[1], 0)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("qualification", matches))
|
||||
setMatchLateness(&matches[1], 60)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("practice", matches))
|
||||
setMatchLateness(&matches[1], -60)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("qualification", matches))
|
||||
setMatchLateness(&matches[1], 90)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("qualification", matches))
|
||||
setMatchLateness(&matches[1], -90)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("qualification", matches))
|
||||
setMatchLateness(&matches[1], 110)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("practice", matches))
|
||||
setMatchLateness(&matches[1], -110)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("qualification", matches))
|
||||
|
||||
// Check lateness.
|
||||
setMatchLateness(&matches[1], 130)
|
||||
assert.Equal(t, "Event is running 2 minutes late", generateEventStatusMessage("practice", matches))
|
||||
setMatchLateness(&matches[1], 3601)
|
||||
assert.Equal(t, "Event is running 60 minutes late", generateEventStatusMessage("qualification", matches))
|
||||
|
||||
// Check earliness.
|
||||
setMatchLateness(&matches[1], -130)
|
||||
assert.Equal(t, "Event is running 2 minutes early", generateEventStatusMessage("qualification", matches))
|
||||
setMatchLateness(&matches[1], -3601)
|
||||
assert.Equal(t, "Event is running 60 minutes early", generateEventStatusMessage("practice", matches))
|
||||
|
||||
// Check other match types.
|
||||
assert.Equal(t, "", generateEventStatusMessage("test", matches))
|
||||
assert.Equal(t, "", generateEventStatusMessage("elimination", matches))
|
||||
|
||||
// Check that later matches supersede earlier ones.
|
||||
matches = append(matches, model.Match{})
|
||||
setMatchLateness(&matches[2], 180)
|
||||
assert.Equal(t, "Event is running 3 minutes late", generateEventStatusMessage("qualification", matches))
|
||||
|
||||
// Check that a lateness before a large gap is ignored.
|
||||
matches[3].Time = time.Now().Add(time.Minute * 25)
|
||||
assert.Equal(t, "Event is running on schedule", generateEventStatusMessage("qualification", matches))
|
||||
}
|
||||
|
||||
func setMatchLateness(match *model.Match, secondsLate int) {
|
||||
match.Time = time.Now()
|
||||
match.StartedAt = time.Now().Add(time.Second * time.Duration(secondsLate))
|
||||
match.Status = "complete"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user