Skip to content
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

Cache the relationship between Lane ID (NetLane) and Lane Index (NetInfo.Lane) #1526

Merged
merged 13 commits into from
May 25, 2022

Conversation

Elesbaan70
Copy link
Contributor

Core Functionality

This adds the following method to IExtSegmentManager:

    uint GetLaneId(ushort segmentId, int laneIndex);

and adds a new IExtLaneManager with the following methods:

    bool GetSegmentAndIndex(uint laneId, out ushort segmentId, out int laneIndex);

    int GetLaneIndex(uint laneId);

    NetInfo.Lane GetLaneInfo(uint laneId);

Any existing code I could identify that did this work has been replaced with calls to the new methods. I imagine there are probably other places I didn't catch.

Added Bonus

Also, this adds a class under the Patch namespace, NetManagerEvents, which patches NetManager to provide simple high-level event notifications that can be consumed by managers ad hoc:

  • ExtLaneManager uses it exclusively for monitoring network changes.
  • The new functionality in ExtSegmentManager uses it for monitoring network changes.
  • LaneConnectionManager has been updated to monitor the ReleasingSegment event instead of overriding HandleInvalidSegment.

The ReleasingSegment event will be better in many cases than using HandleInvalidSegment because:

  1. It always occurs immediately as segments are becoming invalid, because it's patched into the right NetManager operation. It's just more reliable.
  2. It occurs while the segment is still valid. Some HandleInvalidSegment overrides are acting on data that might not be in a valid state, which may lead to problems. NetManagerEvents.ReleasingSegment addresses that issue.

@kianzarrin kianzarrin self-requested a review April 9, 2022 05:03
@kianzarrin kianzarrin added enhancement Improve existing feature feature A new distinct feature technical Tasks that need to be performed in order to improve quality and maintainability API API for external mods and removed enhancement Improve existing feature labels Apr 9, 2022
@originalfoo
Copy link
Member

Would the Managers/AbstractGeometryObservingManager benefit from any of the NetManager event patches you added - or even be made obsolete? (I've not checked yet, just random thought on quick glance)

TLM/TLM/Manager/Impl/ExtLaneManager.cs Show resolved Hide resolved
TLM/TLM/Manager/Impl/ExtLaneManager.cs Show resolved Hide resolved
TLM/TLM/Manager/Impl/ExtSegmentManager.cs Outdated Show resolved Hide resolved
TLM/TLM/Manager/Impl/ManagerFactory.cs Outdated Show resolved Hide resolved
TLM/TLM/Manager/Impl/SpeedLimitManager.cs Show resolved Hide resolved
@originalfoo
Copy link
Member

originalfoo commented Apr 9, 2022

Probably outside scope of this PR, but...

When a segment change is detected, could we iterate the lanes and compile:

  • laneInfo.m_laneType (| the flags)
  • laneInfo.m_vehicleType (| the flags)
  • laneInfo.m_finalDirection - (count the directions - ignoring None; and | the flags)

With that lane summary stored on an ExtSegment we could very quickly check if a segment has lanes suitable for specific TMPE tool, especially in overlays code, etc. Things like GetSortedLanes() could first check if the segment has required lane/vehicle types, direction, etc. before having to do any iteration (which itself will be much easier now thanks to existing stuff in this PR).

Maybe have extensions like netSegment.Matches(laneType?, vehicleType?, direction?), etc? As an example of where it would be useful see MayHaveCustomSpeedLimits() extension which could be condensed to:

public static bool MayHaveCustomSpeedLimits(this ref NetSegment segment) =>
    segment.IsValid() &&
    segment.Directions != 0 && // extension that gets the compiled direction flags
    segment.Matches(
        laneTypes: SpeedLimitManager.LANE_TYPES,
        vehicleTypes: SpeedLimitManager.VEHICLE_TYPES);

The original is checking service/subservice however that can be problematic as there's often decorative networks in workshop that are using the service/subservice that TMPE is interested in so we get false positives when checking at the segment level.

We could probably merge that stuff in to a dedicate 'quick check' extension something like:

public static bool IsApplicable(
    this ref NetSegment segment,
    NetInfo.LaneType? laneTypes = null,
    VehicleInfo.VehicleType? vehicleTypes = null) {

    if (!segment.m_flags.CheckFlags(
        required: NetSegment.Flags.Created,
        forbidden: NetSegment.Flags.Collapsed | NetSegment.Flags.Deleted))
            return false;

    var extSeg = ...

    return
        extSeg.Directions != 0 &&
        (laneTypes == null || (extSeg.laneTypes & laneTypes) != 0) &&
        (vehicleTypes == null || (extSeg.vehicleTypes & vehicleTypes) != 0);
}

Then we can quick check if a segment is relevant to a TMPE tool:

if (someSegment.IsApplicable(someTool.LANE_TYPES, someTool.VEHICLE_TYPES) {
   ...
}

If there's an interface that ensures LANE_TYPES and VEHICLE_TYPES are defined on a class, we could also do something joyful along the lines of...

// elswhere
if (someSegment.IsApplicableTo(SpeedLimitManager) ...

@Elesbaan70
Copy link
Contributor Author

Elesbaan70 commented Apr 9, 2022

Would the Managers/AbstractGeometryObservingManager benefit from any of the NetManager event patches you added - or even be made obsolete?

It is possible long-term, and in my opinion that wouldn't be a bad thing. I dislike the whole paradigm of infrastructure code knowing about all of its consumers.

But AbstractGeometryObservingManager does a lot more than NetManagerEvents, and even where their functionality seems to overlap, there might be use cases that the existing events don't cover. So this would need to be treated as a separate project where these questions are examined carefully.

I created NetManagerEvents because I needed lane events and because I was concerned about what I saw in LaneConnectionManager. (I don't remember the specifics, but I believe it makes assumptions that require the segment to still be valid.) I think it's best to take a minimalist approach, shifting over to this paradigm only as the need arises.

@kianzarrin
Copy link
Collaborator

Can we have a single threaded benchmark? there is a benchmark project.

// based on an ID-derived index.
private static object GetLockObject(ushort segmentId) => lockObject;

private bool CheckLanes(ref ExtSegment segment) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be better to call this extSegment. but this is OK too.

}

private void HandleInvalidSegmentImpl(ushort segmentId) {
private void ReleasingSegment(ushort segmentId, ref NetSegment segment) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename to OnReleasingSegment()?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on the second thought this is fine too.
https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/names-of-type-members#names-of-events

CS uses the On... convention.
Notifier uses events that begin with On. also we have OnSegmentChange/Modified/Replacement and several other events like this.
In other places we don't use the On... naming convention. So its a mixed bag so far.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the naming convention on the page you linked to, Microsoft's convention, at least as implemented in places like WinForms, is that "On..." is used on virtual methods that are called by event handlers. So basically, it's a second layer of event handling within the context of a specific class that monitors an event.

"PreReleaseSegmentImplementation",
new[] { typeof(ushort), typeof(NetSegment), typeof(bool) },
new[] { ArgumentType.Normal, ArgumentType.Ref, ArgumentType.Normal }
)]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Note]
maybe you can create this attribute:

    public class HarmonyPatch2 : HarmonyLib.HarmonyPatch {
        public HarmonyPatch2(Type delcaringType, Type delegateType, bool instance = false){
            info.declaringType = delcaringType;
            info.methodName = delegateType.Name;
            var args = delegateType.GetMethod("Invoke").GetParameters().Select(p => p.ParameterType); // or use what we have in transpiler utils.
            if (instance) args = args.Skip(1); // skip arg0 because its instance method.
            info.argumentTypes = args.ToArray();
        }
    }

And then use it like this:

private delegate void PreReleaseSegmentImplementation(ushort segment, ref NetSegment data, bool keepNodes);

[HarmonyPatch2(typeof(NetManager), typeof(PreReleaseSegmentImplementation))]

The advantage of doing it this way is that you are less likely to make mistake copy pasting the function from dnSpy.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't even using dnSpy. I did this all from within Visual Studio. But I'm still enough of a novice with Harmony that I'd rather wait to do things like this until I have a better handle on the big picture of how it works.

Copy link
Collaborator

@kianzarrin kianzarrin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[lower priority] simple benchmark.

Copy link
Collaborator

@kianzarrin kianzarrin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if benchmark is possible with Unity objects like NetInfo. So I am approving this as it is. if you managed to do any speed comparison between the current code and the previous lane util code that would be nice.

@Elesbaan70
Copy link
Contributor Author

Where are we at on this? It would probably be good to get it merged soonish if it's going to be accepted, since the conflicts with #354 are starting to get messy.

/// <summary>
/// The Lane IDs as an array for fast lookup by index.
/// </summary>
public uint[] lanes;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be in the API?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want it to be that way, but that's how ExtSegmentManager is written. As much as I'd like to secure my reputation as the API killer, I'd rather not change that unless forced to as I basically was in the traffic light stuff.

Copy link
Contributor Author

@Elesbaan70 Elesbaan70 May 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not really an API. It's exactly the same mess as the traffic light stuff.

@krzychu124 do you have an opinion on this? The nice thing in this case is, it's a much smaller problem, and easier to define, than the traffic light stuff. If we did want to clean it up, we could make sure there's new API in place to do whatever people might have needed with it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would leave it like it is. For now nothing knows about its existence and before it will go to Stable we might have few chances to throw it away from there 😄

Copy link
Member

@krzychu124 krzychu124 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good, I'm not sure yet if should go before or after #1479
Both affect lane connector so could be released to Test at the same time.
I think it might be easier to merge it after since this one does not change logic, just replaces calls.
@kianzarrin any thoughts in which order it could be easier to proceed if we want to move forward?

@kianzarrin
Copy link
Collaborator

I think #1479 is more complicated and harder to fix conflicts. So after #1479 is good idea.

@originalfoo originalfoo added this to the 11.6.6.0 milestone May 22, 2022
@originalfoo originalfoo self-requested a review May 22, 2022 03:07
@Elesbaan70 Elesbaan70 mentioned this pull request May 25, 2022
@Elesbaan70 Elesbaan70 merged commit d0f98b9 into CitiesSkylinesMods:master May 25, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
API API for external mods feature A new distinct feature technical Tasks that need to be performed in order to improve quality and maintainability
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants