-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
STARS v2: Part 1 #30
base: master
Are you sure you want to change the base?
STARS v2: Part 1 #30
Conversation
sim.go
Outdated
@@ -2456,7 +2456,9 @@ type HeadingArgs struct { | |||
func (s *Sim) AssignHeading(hdg *HeadingArgs) error { | |||
s.mu.Lock(s.lg) | |||
defer s.mu.Unlock(s.lg) | |||
|
|||
if hdg.Heading%5 != 0 { // Just so that scratchpads like R11 can go in | |||
return errors.New("Heading too specific") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not meant to block this PR but I think tweaks like this are telltale that we need to make simulator commands explicitly distinct from STARS commands.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be a good idea. Two questions I have:
- What would be a good toggle/ prefix to do this with?
- What if that aircraft command doesn't exist? What would be returned?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This was done in 22225c2, so the check here can go.)
stars.go
Outdated
} | ||
if lc > 4 || (lc == 4 && !ctx.world.STARSFacilityAdaptation.ScratchpadRules[0] && !isSecondary) || | ||
(lc == 4 && !ctx.world.STARSFacilityAdaptation.ScratchpadRules[1] && isSecondary) { | ||
sp.previewAreaOutput = GetSTARSError(ErrSTARSIllegalScratchpad).Error() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two general comment about error flows (applies here and elsewhere):
- Whenever possible, prefer returning an error code than doing straight to setting
sp.previewAreaOutput
. In that way, the caller of the function can see that an error occurred and decide what to do about it, possibly handling the error or possibly showing it to the user. The main exception to this is the error handling callbacks for RPCs, which pretty much have no other alternative than to show it to the user. - Related,
GetSTARSError
is only needed to remap non-STARS error types (e.g.strconv.Atoi
returning an error) to a STARS error code for display in the preview area. So there's no need to call that when you know for sure you have a STARS error code.
stars.go
Outdated
} | ||
} | ||
if callsign == "" { | ||
return "", errors.New("Cannot calculate airspace") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's probably better to return ErrSTARSIllegalPosition
here (and similarly elsewhere in the function). It looks like the calling code is either checking if the error is nil
or not, or is returning ErrSTARSIllegalPosition
itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, I think it would be better to return just a *Controller
from this function, with nil
for none found. Some of the calling code turns around and calls world.GetController()
with the returned callsign, which is an unnecessary round-trip, and otherwise just shows an illegal position error if none is found. So a nil
pointer is sufficient to convey the only failure case here.
stars.go
Outdated
controller := singleScope(ctx, string(controller[2])) | ||
if controller != nil { | ||
return true, controller.Callsign | ||
if controller[0] == 'C' || (haveTrianglePrefix && lc == 3) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the lc==3
test: is this implicitly baking in the knowledge that the len
of STARSTriangleCharacter
is 2? (Similar question about the lc == 5 && haveTrianglePrefix
test below.)
I think it would be better to always call strings.TrimPrefix(controller, STARSTriangleCharacter)
right after HasPrefix(controller, STARSTriangleCharacter)
at the start of the function (and then move lc := len(controller)
after that . Then following code can purely use haveTrianglePrefix
to distinguish that case and can then operate on string lengths that are based on the remaining non-triangle text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The 3 and 5 did have the length of the triangle character in mind. What I did before was if haveTrianglePrefix
was true, lc -= 1
. I could do that here, or just trim the prefix and get the length of that. One issue I see with trimming the prefix is that haveTrianglePrefix
is used multiple times during calculateController
, so trimming it at the beginning may lead to some problems with checking the other handoffs that could have been done.
stars.go
Outdated
@@ -3133,6 +3162,9 @@ func (sp *STARSPane) handoffTrack(ctx *PaneContext, callsign string, controller | |||
// and route. | |||
func calculateAirspace(ctx *PaneContext, callsign string) (string, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would work better to return *AirspaceAwareness
here (with nil
indicating "couldn't find anything"). Returning a magic string "no target" is a little error prone / obscure (e.g. it could be misspelled here or there without that being realized), and looking at the calling code, it ends up returning an error in any case when AirspaceAwareness
isn't found. (More comments below where it's used.)
If details of why the function failed were needed, it would be better to use an error
and to declare a global variable for the error code or codes so that calling code could use ==
tests to see what the actual error was.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is one thing about this. All errors are the same because we're returning nil
. For example, if the ACID/BCN was entered incorrectly, the error code should be NO FLIGHT
; but, if the receiving position does not exist, the error should be ILL POS
. I think changing it to a string and an error would be the way to go.
stars.go
Outdated
return "", errors.New("Cannot calculate airspace") | ||
} | ||
if controller == "C" { | ||
control, toCenter := calculateAirspace(ctx, callsign) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the flow of how these return values are used currently, "no target" returns an error. For the other failure case in calculateAirspace
(not finding a match), "", false
is returned; in turn, world.GetController()
will fail and an error is returned as well.
It should never happen that a match is found in calculateAirspace
but passing rules.ReceivingController
to world.GetController
will fail; we validate that in scenario.go when the scenario is loaded. We have commented-out code in scenario.g (line 696) that validates this; it currently fails since most scenarios are still using sector ids and not callsigns there.
Anyway, once the scenarios are updates and we validate all of the ReceivingController
callsigns at scenario load time, then it's reasonable to assume that world.GetController()
will always succeed for a valid AirspaceAwareness
.
stars.go
Outdated
@@ -3175,76 +3207,86 @@ func singleScope(ctx *PaneContext, facilityIdentifier string) *Controller { | |||
|
|||
// Give a bool if the handoff is good and the correct syntax. | |||
// Also decode the controller into its regular sector (N4P -> 4P) | |||
func (sp *STARSPane) calculateController(ctx *PaneContext, controller, callsign string) (bool, string) { | |||
func (sp *STARSPane) calculateController(ctx *PaneContext, controller, callsign string) (string, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is a little tricky to understand (in part because it's inherently complex how this is resolved), but I think renaming the controller
parameter would help: it is actually.. a TCP ID? Or is there a better word to describe what the user's input corresponds to? controller
suggests to me at least that the variable is either a controller callsign or a Controller
object. Anyway, I think a more distinct name that makes it more clear that this is the thing the user entered that is being mapped to a control position would make the function easier to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall looks good; a few small requests..
sim.go
Outdated
s.lg.Infof("%s/%s/%s: launch departure", airport, runway, category) | ||
s.launchAircraftNoLock(*ac) | ||
s.NextDepartureSpawn[airport] = now.Add(randomWait(rateSum, false)) | ||
for { // There will always be a gate, because is all gates are checked this func wont get called |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From before I thought that this wasn't true? Wasn't there a case something like if the scenario has 0 departures to some gate then if every other gate but that one is stopped but that one isn't, then it still won't be able to launch any aircraft?
If so, would it work to just continue to call spawnAircraft()
, let it fail if the gate it chose is stopped, and then assume it will eventually find a gate that's open (if there is one) in the next few times...?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean if there are no departures made/ coded in for that one gate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I mean for example, say I launch the "KEWR 4L Departure" scenario and set an ADR of 0 for KMMU and KTEB when I create the sim. Then if I halt departures from KEWR 4L I think this will go into an infinite loop because yes, there are some gates that aren't stopped, but on the other hand a departure from them will never be launched.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to reproduce this and looks like it was fixed in 0eb5aec.
sim.go
Outdated
@@ -2456,7 +2456,9 @@ type HeadingArgs struct { | |||
func (s *Sim) AssignHeading(hdg *HeadingArgs) error { | |||
s.mu.Lock(s.lg) | |||
defer s.mu.Unlock(s.lg) | |||
|
|||
if hdg.Heading%5 != 0 { // Just so that scratchpads like R11 can go in | |||
return errors.New("Heading too specific") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(This was done in 22225c2, so the check here can go.)
Lots of new features. Probably should be a beta release because there may be a few bugs that when running a local server and two instances of vice at the same time on a MacBook were not found:
Specifying positions
A
to handoff to4A
working4P
C
can be used as a handoff to the appropriate center controllerHandoffs
Misc. STARS Commands
**
will Force QL the target to the user, if they have control of the track and it is allowed by the STARS facility (specified in the facility configuration file)** + SECTORID
will force QL a track to that intrafacility positionMULTI FUNC, O
will display the last 20 TCPs that have accepted a point outMULI FUNC, Q
(yay!)SLEW
instead ofMULTI FUNC, Y
The Length of scratchpads defaults at 3, but can be extended to four if allowed by the STARS facility. (Specified in the facility configuration file)Facility configuration file changes
Controller
struct now needs afacility_identifier
JSON fieldNewSimConfigration
now needs thestars_adaptation
JSON field containing: a bool:force_ql_self
, a [2]bool:scratchpad_rules
bool[0] is for the primary scratchpad, bool[1] is for the secondary scratchpad, and theairspace_awareness
struct. Inside of this struct there is a []string:fix
which is a slice of fixes that an aircraft could be filed over. A stringaltitude_range
which is specified as100450
for 10,000 to 45,000. A stringreceiving_controller
which is the controller that will receive this. Finally the book:to_center
which is pretty self-explanatoryTODO List
Redirecting HandoffsMULTI FUNC ZZ
LDB vs PDBGlobal Leader LinesFixes
Let me know if anything should be changed (probably a lot that is going to be needed too)!