77 "time"
88
99 "github.com/rs/zerolog/log"
10- "google.golang.org/protobuf/proto "
10+ "google.golang.org/protobuf/encoding/protojson "
1111
1212 "github.com/domino14/macondo/config"
1313 "github.com/domino14/macondo/gameanalysis"
@@ -17,7 +17,6 @@ import (
1717const (
1818 VolunteerPollInterval = 30 * time .Second
1919 VolunteerHeartbeatInterval = 30 * time .Second
20- WooglesBaseURL = "https://woogles.io"
2120)
2221
2322// volunteer handles the volunteer command
@@ -62,81 +61,98 @@ func (sc *ShellController) startVolunteer() (*Response, error) {
6261 return msg ("Volunteer mode started. Polling for jobs every 30 seconds...\n Use 'volunteer stop' to exit gracefully." ), nil
6362}
6463
65- // stopVolunteer signals volunteer mode to stop after current job
64+ // stopVolunteer stops volunteer mode, immediately if idle or after current job if busy
6665func (sc * ShellController ) stopVolunteer () (* Response , error ) {
6766 if ! sc .volunteerMode {
6867 return msg ("Not in volunteer mode." ), nil
6968 }
7069
71- sc .volunteerStop = true
72- return msg ("Volunteer mode will stop after current job completes." ), nil
70+ if sc .volunteerBusy {
71+ sc .volunteerStop = true
72+ return msg ("Volunteer mode will stop after current job completes." ), nil
73+ }
74+
75+ sc .volunteerCancel ()
76+ return msg ("Volunteer mode stopped." ), nil
7377}
7478
7579// volunteerLoop polls for jobs and processes them
7680func (sc * ShellController ) volunteerLoop () {
7781 // Get API key and create client
7882 apiKey := sc .config .GetString (config .ConfigWooglesApiKey )
79- client := worker .NewWooglesClient (WooglesBaseURL , apiKey )
83+ wooglesURL := sc .config .GetString (config .ConfigWooglesURL )
84+ client := worker .NewWooglesClient (wooglesURL , apiKey , sc .config )
8085
8186 ticker := time .NewTicker (VolunteerPollInterval )
8287 defer ticker .Stop ()
8388
8489 log .Info ().Msg ("volunteer loop started" )
8590
86- for {
87- select {
88- case <- sc .volunteerCtx . Done ():
89- log .Info ().Msg ("volunteer loop cancelled " )
91+ poll := func () bool {
92+ // Check if we should stop
93+ if sc .volunteerStop {
94+ log .Info ().Msg ("volunteer stop requested " )
9095 sc .cleanupVolunteerMode ()
91- return
96+ return false
97+ }
9298
93- case <- ticker . C :
94- // Check if we should stop
95- if sc . volunteerStop {
96- log .Info ().Msg ("volunteer stop requested " )
97- sc .cleanupVolunteerMode ( )
98- return
99- }
99+ // Try to claim a job
100+ job , err := client . ClaimJob ( sc . volunteerCtx )
101+ if err != nil {
102+ log .Warn ().Err ( err ). Msg ("failed to claim job " )
103+ writeln ( fmt . Sprintf ( "Warning: Failed to claim job: %v" , err ), sc .l . Stdout () )
104+ return true
105+ }
100106
101- // Try to claim a job
102- job , err := client .ClaimJob (sc .volunteerCtx )
103- if err != nil {
104- log .Warn ().Err (err ).Msg ("failed to claim job" )
105- writeln (fmt .Sprintf ("Warning: Failed to claim job: %v" , err ), sc .l .Stdout ())
106- continue
107- }
107+ if job == nil {
108+ log .Info ().Msg ("no jobs available, polling again in 30s" )
109+ writeln ("No jobs available, polling again in 30s..." , sc .l .Stdout ())
110+ return true
111+ }
108112
109- if job == nil {
110- // No jobs available
111- log . Debug (). Msg ( "no jobs available" )
112- continue
113- }
113+ log . Info ().
114+ Str ( "job-id" , job . JobID ).
115+ Str ( "game-id" , job . GameID ).
116+ Msg ( "claimed job" )
117+ writeln ( fmt . Sprintf ( "Claimed job %s for game %s" , job . JobID , job . GameID ), sc . l . Stdout ())
114118
115- // Got a job! Process it
119+ sc .volunteerBusy = true
120+ if err := sc .processVolunteerJob (client , job ); err != nil {
121+ log .Error ().
122+ Err (err ).
123+ Str ("job-id" , job .JobID ).
124+ Msg ("failed to process job" )
125+ writeln (fmt .Sprintf ("Error processing job: %v" , err ), sc .l .Stdout ())
126+ } else {
116127 log .Info ().
117128 Str ("job-id" , job .JobID ).
118- Str ( "game-id" , job . GameID ).
119- Msg ( "claimed job" )
120- writeln ( fmt . Sprintf ( "Claimed job %s for game %s" , job . JobID , job . GameID ), sc . l . Stdout ())
121-
122- // Process the job
123- if err := sc .processVolunteerJob ( client , job ); err != nil {
124- log .Error ().
125- Err ( err ).
126- Str ( "job-id" , job . JobID ).
127- Msg ( "failed to process job" )
128- writeln ( fmt . Sprintf ( "Error processing job: %v" , err ), sc . l . Stdout ())
129- } else {
130- log . Info ().
131- Str ( "job-id" , job . JobID ).
132- Msg ( "job completed successfully" )
133- writeln ( fmt . Sprintf ( "Job %s completed successfully" , job . JobID ), sc . l . Stdout ())
134- }
129+ Msg ( " job completed successfully" )
130+ writeln ( fmt . Sprintf ( "Job %s completed successfully" , job . JobID ), sc . l . Stdout () )
131+ }
132+ sc . volunteerBusy = false
133+
134+ if sc .volunteerStop {
135+ log .Info ().Msg ( "volunteer stop requested after job completion" )
136+ sc . cleanupVolunteerMode ()
137+ return false
138+ }
139+ return true
140+ }
141+
142+ // Poll immediately on start
143+ if ! poll () {
144+ return
145+ }
135146
136- // After processing, check if we should stop
137- if sc .volunteerStop {
138- log .Info ().Msg ("volunteer stop requested after job completion" )
139- sc .cleanupVolunteerMode ()
147+ for {
148+ select {
149+ case <- sc .volunteerCtx .Done ():
150+ log .Info ().Msg ("volunteer loop cancelled" )
151+ sc .cleanupVolunteerMode ()
152+ return
153+
154+ case <- ticker .C :
155+ if ! poll () {
140156 return
141157 }
142158 }
@@ -256,11 +272,10 @@ func (sc *ShellController) processVolunteerJob(client *worker.WooglesClient, job
256272 Int ("turns-analyzed" , len (result .Turns )).
257273 Msg ("analysis complete" )
258274
259- // Convert result to protobuf
275+ // Convert result to protobuf and serialize as proto-JSON
260276 resultProto := result .ToProto ()
261277
262- // Serialize to bytes
263- resultBytes , err := proto .Marshal (resultProto )
278+ resultBytes , err := protojson .Marshal (resultProto )
264279 if err != nil {
265280 return fmt .Errorf ("failed to marshal result: %w" , err )
266281 }
0 commit comments