blob: 077e87e6c95a8dd1ec07991ac151d38e6a07ee35 [file] [log] [blame]
Stephan Massaltd021f972020-01-05 20:41:23 -08001#include "y2020/actors/autonomous_actor.h"
2
Stephan Massaltd021f972020-01-05 20:41:23 -08003#include <chrono>
Tyler Chatowbf0609c2021-07-31 16:13:27 -07004#include <cinttypes>
Stephan Massaltd021f972020-01-05 20:41:23 -08005#include <cmath>
6
7#include "aos/logging/logging.h"
James Kuszmaulddd2ba62020-03-08 22:17:13 -07008#include "aos/util/math.h"
Stephan Massaltd021f972020-01-05 20:41:23 -08009#include "frc971/control_loops/drivetrain/localizer_generated.h"
James Kuszmaul5f6d1d42020-03-01 18:10:07 -080010#include "y2020/actors/auto_splines.h"
Ravago Jonesc2a08022021-02-06 17:40:54 -080011#include "y2020/control_loops/drivetrain/drivetrain_base.h"
Stephan Massaltd021f972020-01-05 20:41:23 -080012
Austin Schuh3fb9e422021-03-31 20:11:32 -070013DEFINE_bool(spline_auto, false, "If true, define a spline autonomous mode");
Austin Schuhd0e9e062021-10-24 17:40:58 -070014DEFINE_bool(target_aligned, true,
Ravago Jonesa7b3c822021-08-26 12:36:03 -070015 "If true, run the Infinite Recharge autonomous that starts aligned "
16 "with the target");
Ravago Jones8c5737e2021-10-16 15:36:12 -070017DEFINE_bool(target_offset, false,
Ravago Jonesa7b3c822021-08-26 12:36:03 -070018 "If true, run the Infinite Recharge autonomous that starts offset "
19 "from the target");
20DEFINE_bool(just_shoot, false,
21 "If true, run the autonomous that just shoots balls.");
milind upadhyay47a0ab32020-11-25 19:34:41 -080022
Stephan Massaltd021f972020-01-05 20:41:23 -080023namespace y2020 {
24namespace actors {
25
26using ::aos::monotonic_clock;
27using ::frc971::ProfileParametersT;
28using frc971::control_loops::drivetrain::LocalizerControl;
29namespace chrono = ::std::chrono;
30
31AutonomousActor::AutonomousActor(::aos::EventLoop *event_loop)
32 : frc971::autonomous::BaseAutonomousActor(
James Kuszmaul5f6d1d42020-03-01 18:10:07 -080033 event_loop, control_loops::drivetrain::GetDrivetrainConfig()),
Ravago Jonesc2a08022021-02-06 17:40:54 -080034 localizer_control_sender_(
35 event_loop->MakeSender<
36 ::frc971::control_loops::drivetrain::LocalizerControl>(
37 "/drivetrain")),
Austin Schuh67e127e2021-03-27 13:25:23 -070038 superstructure_goal_sender_(
39 event_loop->MakeSender<control_loops::superstructure::Goal>(
40 "/superstructure")),
Ravago Jones1f32d622021-08-26 12:20:36 -070041 superstructure_status_fetcher_(
42 event_loop->MakeFetcher<y2020::control_loops::superstructure::Status>(
43 "/superstructure")),
Austin Schuhba77b7e2021-10-25 21:58:54 -070044 joystick_state_fetcher_(
45 event_loop->MakeFetcher<aos::JoystickState>("/aos")),
46 robot_state_fetcher_(event_loop->MakeFetcher<aos::RobotState>("/aos")),
Ravago Jonesc2a08022021-02-06 17:40:54 -080047 auto_splines_() {
Austin Schuhd0e9e062021-10-24 17:40:58 -070048 set_max_drivetrain_voltage(12.0);
James Kuszmaul99af8b52021-03-28 10:50:15 -070049 replan_timer_ = event_loop->AddTimer([this]() { Replan(); });
50 event_loop->OnRun([this, event_loop]() {
51 replan_timer_->Setup(event_loop->monotonic_now());
Austin Schuhba77b7e2021-10-25 21:58:54 -070052 button_poll_->Setup(event_loop->monotonic_now(), chrono::milliseconds(50));
James Kuszmaul99af8b52021-03-28 10:50:15 -070053 });
Austin Schuhba77b7e2021-10-25 21:58:54 -070054
55 button_poll_ = event_loop->AddTimer([this]() {
James Kuszmaulead4da62021-10-24 17:36:54 -070056 const aos::monotonic_clock::time_point now =
57 this->event_loop()->context().monotonic_event_time;
Austin Schuhba77b7e2021-10-25 21:58:54 -070058 if (robot_state_fetcher_.Fetch()) {
59 if (robot_state_fetcher_->user_button()) {
60 user_indicated_safe_to_reset_ = true;
61 MaybeSendStartingPosition();
62 }
James Kuszmaul4303a5d2021-10-23 19:58:48 -070063 }
Austin Schuhba77b7e2021-10-25 21:58:54 -070064 if (joystick_state_fetcher_.Fetch()) {
65 if (joystick_state_fetcher_->has_alliance() &&
66 (joystick_state_fetcher_->alliance() != alliance_)) {
67 alliance_ = joystick_state_fetcher_->alliance();
James Kuszmaulead4da62021-10-24 17:36:54 -070068 is_planned_ = false;
69 // Only kick the planning out by 2 seconds. If we end up enabled in that
70 // second, then we will kick it out further based on the code below.
71 replan_timer_->Setup(now + std::chrono::seconds(2));
72 }
73 if (joystick_state_fetcher_->enabled()) {
74 if (!is_planned_) {
75 // Only replan once we've been disabled for 5 seconds.
76 replan_timer_->Setup(now + std::chrono::seconds(5));
77 }
Austin Schuhba77b7e2021-10-25 21:58:54 -070078 }
James Kuszmaul4303a5d2021-10-23 19:58:48 -070079 }
80 });
81}
82
83void AutonomousActor::MaybeSendStartingPosition() {
James Kuszmaulead4da62021-10-24 17:36:54 -070084 if (is_planned_ && user_indicated_safe_to_reset_ &&
85 !sent_starting_position_) {
James Kuszmaul4303a5d2021-10-23 19:58:48 -070086 CHECK(starting_position_);
87 SendStartingPosition(starting_position_.value());
88 }
James Kuszmaul99af8b52021-03-28 10:50:15 -070089}
90
91void AutonomousActor::Replan() {
James Kuszmaul4303a5d2021-10-23 19:58:48 -070092 sent_starting_position_ = false;
Austin Schuhd0e9e062021-10-24 17:40:58 -070093 if (alliance_ == aos::Alliance::kInvalid) {
94 return;
95 }
Ravago Jones8c5737e2021-10-16 15:36:12 -070096 if (FLAGS_spline_auto) {
Tyler Chatowbf0609c2021-07-31 16:13:27 -070097 test_spline_ = PlanSpline(std::bind(&AutonomousSplines::TestSpline,
98 &auto_splines_, std::placeholders::_1),
99 SplineDirection::kForward);
James Kuszmaul4303a5d2021-10-23 19:58:48 -0700100 starting_position_ = test_spline_->starting_position();
Austin Schuhd0e9e062021-10-24 17:40:58 -0700101 } else if (FLAGS_target_aligned) {
102 target_aligned_splines_ = {
103 PlanSpline(std::bind(&AutonomousSplines::TargetAligned1, &auto_splines_,
104 std::placeholders::_1, alliance_),
105 SplineDirection::kForward),
106 PlanSpline(std::bind(&AutonomousSplines::TargetAligned2, &auto_splines_,
107 std::placeholders::_1, alliance_),
108 SplineDirection::kBackward)};
109 starting_position_ = target_aligned_splines_.value()[0].starting_position();
Ravago Jones8c5737e2021-10-16 15:36:12 -0700110 } else if (FLAGS_target_offset) {
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700111 target_offset_splines_ = {
112 PlanSpline(std::bind(&AutonomousSplines::TargetOffset1, &auto_splines_,
113 std::placeholders::_1),
114 SplineDirection::kForward),
115 PlanSpline(std::bind(&AutonomousSplines::TargetOffset2, &auto_splines_,
116 std::placeholders::_1),
117 SplineDirection::kBackward)};
James Kuszmaul4303a5d2021-10-23 19:58:48 -0700118 starting_position_ = target_offset_splines_.value()[0].starting_position();
James Kuszmaul4303a5d2021-10-23 19:58:48 -0700119 } else {
120 starting_position_ = Eigen::Vector3d::Zero();
James Kuszmaul99af8b52021-03-28 10:50:15 -0700121 }
James Kuszmaulead4da62021-10-24 17:36:54 -0700122
123 is_planned_ = true;
124
James Kuszmaul4303a5d2021-10-23 19:58:48 -0700125 MaybeSendStartingPosition();
milind upadhyay47a0ab32020-11-25 19:34:41 -0800126}
Stephan Massaltd021f972020-01-05 20:41:23 -0800127
128void AutonomousActor::Reset() {
129 InitializeEncoders();
130 ResetDrivetrain();
milind upadhyayb2e840a2021-03-27 13:54:49 -0700131 RetractIntake();
James Kuszmaul5f6d1d42020-03-01 18:10:07 -0800132
James Kuszmaulddd2ba62020-03-08 22:17:13 -0700133 joystick_state_fetcher_.Fetch();
134 CHECK(joystick_state_fetcher_.get() != nullptr)
135 << "Expect at least one JoystickState message before running auto...";
136 alliance_ = joystick_state_fetcher_->alliance();
Stephan Massaltd021f972020-01-05 20:41:23 -0800137}
138
139bool AutonomousActor::RunAction(
Austin Schuh6fb0a6d2021-01-23 15:43:17 -0800140 const ::frc971::autonomous::AutonomousActionParams *params) {
Stephan Massaltd021f972020-01-05 20:41:23 -0800141 Reset();
James Kuszmaul4303a5d2021-10-23 19:58:48 -0700142 if (!user_indicated_safe_to_reset_) {
143 AOS_LOG(WARNING, "Didn't send starting position prior to starting auto.");
144 SendStartingPosition(starting_position_.value());
145 }
James Kuszmaulead4da62021-10-24 17:36:54 -0700146 // Clear this so that we don't accidentally resend things as soon as we replan
147 // later.
148 user_indicated_safe_to_reset_ = false;
149 is_planned_ = false;
150 starting_position_.reset();
James Kuszmaul99af8b52021-03-28 10:50:15 -0700151
milind upadhyay47a0ab32020-11-25 19:34:41 -0800152 AOS_LOG(INFO, "Params are %d\n", params->mode());
James Kuszmaulddd2ba62020-03-08 22:17:13 -0700153 if (alliance_ == aos::Alliance::kInvalid) {
154 AOS_LOG(INFO, "Aborting autonomous due to invalid alliance selection.");
155 return false;
156 }
Austin Schuhd0e9e062021-10-24 17:40:58 -0700157 if (FLAGS_spline_auto) {
158 SplineAuto();
159 } else if (FLAGS_target_aligned) {
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700160 TargetAligned();
Ravago Jones8c5737e2021-10-16 15:36:12 -0700161 } else if (FLAGS_target_offset) {
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700162 TargetOffset();
163 } else if (FLAGS_just_shoot) {
164 JustShoot();
milind upadhyay47a0ab32020-11-25 19:34:41 -0800165 } else {
166 return DriveFwd();
167 }
168 return true;
169}
Stephan Massaltd021f972020-01-05 20:41:23 -0800170
James Kuszmaul99af8b52021-03-28 10:50:15 -0700171void AutonomousActor::SendStartingPosition(const Eigen::Vector3d &start) {
kyle96c406e2021-02-27 14:07:22 -0800172 // Set up the starting position for the blue alliance.
kyle96c406e2021-02-27 14:07:22 -0800173
174 // TODO(james): Resetting the localizer breaks the left/right statespace
175 // controller. That is a bug, but we can fix that later by not resetting.
176 auto builder = localizer_control_sender_.MakeBuilder();
177
178 LocalizerControl::Builder localizer_control_builder =
179 builder.MakeBuilder<LocalizerControl>();
James Kuszmaul99af8b52021-03-28 10:50:15 -0700180 localizer_control_builder.add_x(start(0));
181 localizer_control_builder.add_y(start(1));
182 localizer_control_builder.add_theta(start(2));
kyle96c406e2021-02-27 14:07:22 -0800183 localizer_control_builder.add_theta_uncertainty(0.00001);
184 if (!builder.Send(localizer_control_builder.Finish())) {
185 AOS_LOG(ERROR, "Failed to reset localizer.\n");
186 }
187}
188
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700189void AutonomousActor::TargetAligned() {
Austin Schuhd0e9e062021-10-24 17:40:58 -0700190 aos::monotonic_clock::time_point start_time = aos::monotonic_clock::now();
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700191 CHECK(target_aligned_splines_);
192 auto &splines = *target_aligned_splines_;
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700193
Austin Schuhd0e9e062021-10-24 17:40:58 -0700194 // Spin up.
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700195 set_shooting(true);
196 SendSuperstructureGoal();
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700197 if (!WaitForBallsShot(3)) return;
198
199 set_shooting(false);
Austin Schuhd0e9e062021-10-24 17:40:58 -0700200 ExtendIntake();
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700201 SendSuperstructureGoal();
202
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700203 if (!splines[0].WaitForPlan()) return;
204 splines[0].Start();
205
206 if (!splines[0].WaitForSplineDistanceRemaining(0.02)) return;
Austin Schuhd0e9e062021-10-24 17:40:58 -0700207 std::this_thread::sleep_for(chrono::milliseconds(200));
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700208 RetractIntake();
209
210 if (!splines[1].WaitForPlan()) return;
211 splines[1].Start();
212
Austin Schuhd0e9e062021-10-24 17:40:58 -0700213 if (!splines[1].WaitForSplineDistanceRemaining(2.0)) return;
214 // Reverse the rollers for a moment to try to unjam any jammed balls. Since
215 // we are moving here, this is free to try.
216 set_roller_voltage(-12.0);
217 std::this_thread::sleep_for(chrono::milliseconds(300));
218 set_roller_voltage(0.0);
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700219
Austin Schuhd0e9e062021-10-24 17:40:58 -0700220 // Once we come to a stop, give the robot a moment to settle down. This makes
221 // the shot more accurate.
222 if (!splines[1].WaitForSplineDistanceRemaining(0.02)) return;
223 std::this_thread::sleep_for(chrono::milliseconds(1500));
224 set_shooting(true);
225 const int balls = Balls();
226
227 SendSuperstructureGoal();
228
229 std::this_thread::sleep_for(chrono::milliseconds(1500));
230
231 // We have been seeing balls get stuck on the intake roller. Reverse the
232 // roller again for a moment to unjam it.
233 set_shooting(false);
234 set_roller_voltage(-12.0);
235 SendSuperstructureGoal();
236 std::this_thread::sleep_for(chrono::milliseconds(250));
237
238 set_roller_voltage(0.0);
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700239 set_shooting(true);
240 SendSuperstructureGoal();
241
Austin Schuhd0e9e062021-10-24 17:40:58 -0700242 if (!WaitUntilAbsoluteBallsShot(3 + balls)) return;
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700243
Austin Schuhd0e9e062021-10-24 17:40:58 -0700244 LOG(INFO) << "Took "
245 << chrono::duration<double>(aos::monotonic_clock::now() -
246 start_time)
247 .count();
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700248}
249
250void AutonomousActor::JustShoot() {
251 // shoot pre-loaded balls
252 set_shooter_tracking(true);
253 set_shooting(true);
254 SendSuperstructureGoal();
255
256 if (!WaitForBallsShot(3)) return;
257
258 set_shooting(false);
259 set_shooter_tracking(true);
260 SendSuperstructureGoal();
261}
262
263void AutonomousActor::TargetOffset() {
264 CHECK(target_offset_splines_);
265 auto &splines = *target_offset_splines_;
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700266
267 // spin up shooter
268 set_shooter_tracking(true);
269 SendSuperstructureGoal();
270 ExtendIntake();
271
272 // pickup 2 more balls in front of the trench run
273 if (!splines[0].WaitForPlan()) return;
274 splines[0].Start();
275
276 if (!splines[0].WaitForSplineDistanceRemaining(0.02)) return;
277 RetractIntake();
278
279 if (!splines[1].WaitForPlan()) return;
280 splines[1].Start();
281
282 if (!splines[1].WaitForSplineDistanceRemaining(0.02)) return;
283
284 // shoot the balls from in front of the goal.
285 set_shooting(true);
286 SendSuperstructureGoal();
287
288 if (!WaitForBallsShot(5)) return;
289
290 set_shooting(false);
291 set_shooter_tracking(false);
292 SendSuperstructureGoal();
293}
294
milind upadhyay47a0ab32020-11-25 19:34:41 -0800295void AutonomousActor::SplineAuto() {
James Kuszmaul99af8b52021-03-28 10:50:15 -0700296 CHECK(test_spline_);
James Kuszmaul5f6d1d42020-03-01 18:10:07 -0800297
James Kuszmaul99af8b52021-03-28 10:50:15 -0700298 if (!test_spline_->WaitForPlan()) return;
299 test_spline_->Start();
Stephan Massaltd021f972020-01-05 20:41:23 -0800300
James Kuszmaul99af8b52021-03-28 10:50:15 -0700301 if (!test_spline_->WaitForSplineDistanceRemaining(0.02)) return;
kyle96c406e2021-02-27 14:07:22 -0800302}
303
milind upadhyay47a0ab32020-11-25 19:34:41 -0800304ProfileParametersT MakeProfileParametersT(const float max_velocity,
305 const float max_acceleration) {
306 ProfileParametersT params;
307 params.max_velocity = max_velocity;
308 params.max_acceleration = max_acceleration;
309 return params;
310}
311
312bool AutonomousActor::DriveFwd() {
Austin Schuhfd1715f2021-01-30 16:58:24 -0800313 const ProfileParametersT kDrive = MakeProfileParametersT(0.3f, 1.0f);
milind upadhyay47a0ab32020-11-25 19:34:41 -0800314 const ProfileParametersT kTurn = MakeProfileParametersT(5.0f, 15.0f);
Austin Schuhfd1715f2021-01-30 16:58:24 -0800315 StartDrive(1.0, 0.0, kDrive, kTurn);
milind upadhyay47a0ab32020-11-25 19:34:41 -0800316 return WaitForDriveDone();
317}
Sabina Leavera0b43b42021-03-03 20:30:04 -0800318
319void AutonomousActor::SendSuperstructureGoal() {
Sabina Leavera0b43b42021-03-03 20:30:04 -0800320 auto builder = superstructure_goal_sender_.MakeBuilder();
321
322 flatbuffers::Offset<StaticZeroingSingleDOFProfiledSubsystemGoal>
323 intake_offset;
milind upadhyayb9dec712021-03-20 15:47:51 -0700324
Sabina Leavera0b43b42021-03-03 20:30:04 -0800325 {
326 StaticZeroingSingleDOFProfiledSubsystemGoal::Builder intake_builder =
327 builder.MakeBuilder<StaticZeroingSingleDOFProfiledSubsystemGoal>();
328
milind upadhyayb9dec712021-03-20 15:47:51 -0700329 frc971::ProfileParameters::Builder profile_params_builder =
Sabina Leavera0b43b42021-03-03 20:30:04 -0800330 builder.MakeBuilder<frc971::ProfileParameters>();
Austin Schuhb39a21c2021-03-31 20:12:18 -0700331 profile_params_builder.add_max_velocity(20.0);
332 profile_params_builder.add_max_acceleration(60.0);
milind upadhyayb9dec712021-03-20 15:47:51 -0700333 flatbuffers::Offset<frc971::ProfileParameters> profile_params_offset =
Sabina Leavera0b43b42021-03-03 20:30:04 -0800334 profile_params_builder.Finish();
335 intake_builder.add_unsafe_goal(intake_goal_);
336 intake_builder.add_profile_params(profile_params_offset);
337 intake_offset = intake_builder.Finish();
338 }
339
340 superstructure::Goal::Builder superstructure_builder =
Tyler Chatowbf0609c2021-07-31 16:13:27 -0700341 builder.MakeBuilder<superstructure::Goal>();
milind upadhyayb9dec712021-03-20 15:47:51 -0700342
Sabina Leavera0b43b42021-03-03 20:30:04 -0800343 superstructure_builder.add_intake(intake_offset);
Austin Schuhd0e9e062021-10-24 17:40:58 -0700344 superstructure_builder.add_intake_preloading(true);
Sabina Leavera0b43b42021-03-03 20:30:04 -0800345 superstructure_builder.add_roller_voltage(roller_voltage_);
Tyler Chatowbf0609c2021-07-31 16:13:27 -0700346 superstructure_builder.add_roller_speed_compensation(
347 kRollerSpeedCompensation);
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700348 superstructure_builder.add_hood_tracking(shooter_tracking_);
349 superstructure_builder.add_turret_tracking(shooter_tracking_);
350 superstructure_builder.add_shooter_tracking(shooter_tracking_);
351 superstructure_builder.add_shooting(shooting_);
Sabina Leavera0b43b42021-03-03 20:30:04 -0800352
353 if (!builder.Send(superstructure_builder.Finish())) {
354 AOS_LOG(ERROR, "Sending superstructure goal failed.\n");
355 }
Sabina Leavera0b43b42021-03-03 20:30:04 -0800356}
milind upadhyay5e589d72021-03-27 13:47:18 -0700357
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700358void AutonomousActor::ExtendIntake() {
Austin Schuhd0e9e062021-10-24 17:40:58 -0700359 set_intake_goal(1.30);
360 set_roller_voltage(6.0);
Ravago Jonesa7b3c822021-08-26 12:36:03 -0700361 SendSuperstructureGoal();
362}
363
milind upadhyay5e589d72021-03-27 13:47:18 -0700364void AutonomousActor::RetractIntake() {
365 set_intake_goal(-0.89);
Austin Schuhd0e9e062021-10-24 17:40:58 -0700366 set_roller_voltage(6.0);
milind upadhyay5e589d72021-03-27 13:47:18 -0700367 SendSuperstructureGoal();
368}
369
Austin Schuhd0e9e062021-10-24 17:40:58 -0700370int AutonomousActor::Balls() {
Ravago Jones1f32d622021-08-26 12:20:36 -0700371 superstructure_status_fetcher_.Fetch();
372 CHECK(superstructure_status_fetcher_.get() != nullptr);
Austin Schuhd0e9e062021-10-24 17:40:58 -0700373 return superstructure_status_fetcher_->shooter()->balls_shot();
374}
Ravago Jones1f32d622021-08-26 12:20:36 -0700375
Austin Schuhd0e9e062021-10-24 17:40:58 -0700376bool AutonomousActor::WaitUntilAbsoluteBallsShot(int absolute_balls) {
Ravago Jones1f32d622021-08-26 12:20:36 -0700377 ::aos::time::PhasedLoop phased_loop(frc971::controls::kLoopFrequency,
378 event_loop()->monotonic_now(),
379 frc971::controls::kLoopFrequency / 2);
380 while (true) {
381 if (ShouldCancel()) {
382 return false;
383 }
384 phased_loop.SleepUntilNext();
385 superstructure_status_fetcher_.Fetch();
Austin Schuhd0e9e062021-10-24 17:40:58 -0700386 CHECK(superstructure_status_fetcher_.get() != nullptr);
387 if (superstructure_status_fetcher_->shooter()->balls_shot() >=
388 absolute_balls) {
Ravago Jones1f32d622021-08-26 12:20:36 -0700389 return true;
390 }
391 }
392}
393
Austin Schuhd0e9e062021-10-24 17:40:58 -0700394bool AutonomousActor::WaitForBallsShot(int num_wanted) {
395 return WaitUntilAbsoluteBallsShot(Balls() + num_wanted);
396}
397
Stephan Massaltd021f972020-01-05 20:41:23 -0800398} // namespace actors
399} // namespace y2020