public static async Task <string> ActiveRideStarted( RideInfo rideInfo, UserRideOffer driver, IEnumerable <UserRideRequest> riders) { ActiveRideStatus status = new ActiveRideStatus { Id = IdGenerator.GenerateNewId(rideInfo), Version = 0, RideInfo = rideInfo, RideState = ActiveRideStatus.State.InProgress, RidersState = new ConcurrentDictionary <string, ActiveRideStatus.RiderState>( riders.Select( (r) => new KeyValuePair <string, ActiveRideStatus.RiderState>( r.User.UserInfo.UserId, ActiveRideStatus.RiderState.Waiting))), RidersGameElements = new ConcurrentDictionary <string, RequestGameElements>( riders .Where((r) => r.RideRequest.GameElements != null) .Select((r) => new KeyValuePair <string, RequestGameElements>( r.User.UserInfo.UserId, r.RideRequest.GameElements))), DriverGameElements = driver.RideOffer.GameElements }; Task postTask = Program.DataStore.PostActiveRideStatus(status); activeRides.TryAdd(status.Id, status); await postTask; RideStarted?.Invoke(status); return(status.Id); }
static (IList <MatchableRideRequest>, RideInfo) RideWithPassenger(UserRideOffer offer, MatchableRideRequest request) { GeoCoordinates offerOrig = offer.RideOffer.Trip.Source; GeoCoordinates offerDest = offer.RideOffer.Trip.Destination; GeoCoordinates requestOrig = request.Request.RideRequest.Trip.Source; GeoCoordinates requestDest = request.Request.RideRequest.Trip.Destination; Task <RouteInfo> leg1Task = Task.Run(() => GetRouteBetween(offerOrig, requestOrig)); Task <RouteInfo> leg2Task = Task.Run(() => GetRouteBetween(requestOrig, requestDest)); Task <RouteInfo> leg3Task = Task.Run(() => GetRouteBetween(requestDest, offerDest)); IReadOnlyList <GeoCoordinates> leg1Points = leg1Task.Result.overviewPolyline.Points; IReadOnlyList <GeoCoordinates> leg2Points = leg2Task.Result.overviewPolyline.Points; IReadOnlyList <GeoCoordinates> leg3Points = leg3Task.Result.overviewPolyline.Points; IEnumerable <Route.Stop> stops = leg1Points.SkipLast(1).Select(pt => new Route.Stop(pt)).Concat( leg2Points.Take(1).Select(pt => new Route.Stop(pt, request.Request.User.UserInfo, true))).Concat( leg2Points.Skip(1).SkipLast(1).Select(pt => new Route.Stop(pt))).Concat( leg3Points.Take(1).Select(pt => new Route.Stop(pt, request.Request.User.UserInfo, false))).Concat( leg3Points.Skip(1).Select(pt => new Route.Stop(pt))); return( new List <MatchableRideRequest> { request }, new RideInfo(offer.User.UserInfo.UserId, offer.RideOffer.Car, new Route(stops))); }
public static bool AddRideOffer(UserRideOffer offer) { // Time-out matching task after 30 seconds. CancellationTokenSource tokenSource = new CancellationTokenSource(30000); CancellationToken token = tokenSource.Token; void OnChanged(UserRequest request) { //TODO v //The only case that needs to be addressed is when a match was made for the old ride offer //Otherwise, the matching loop has the new offer } void OnCanceled(UserRequest request) { tokenSource.Cancel(); } offer.Changed += OnChanged; offer.Canceled.RunWhenFired(OnCanceled); Func <Task> computeMatch = ComputeMatch; Task.Run(computeMatch, token).OnError(Program.ErrorHandler); return(true); async Task ComputeMatch() { try { try { await MakeRide(offer, token); } finally { // Whatever happens, unsubscribe from the events. offer.Canceled.Remove(OnCanceled); offer.Changed -= OnChanged; } } catch (TaskCanceledException) { // Just in case the operation was canceled for a different // reason (e.g. timeout), cancel the offer. No need to // rethrow exception--nothing is wrong. offer.Cancel(); } catch (Exception) { // If anything goes wrong, cancel the ride offer. offer.Cancel(); throw; } } }
static (IList <MatchableRideRequest>, RideInfo) EmptyRide( UserRideOffer offer, RouteInfo originalDriverRoute) { IEnumerable <Route.Stop> stops = originalDriverRoute.overviewPolyline.Points .Select(pt => new Route.Stop(pt)); return( new List <MatchableRideRequest>(), new RideInfo(offer.User.UserInfo.UserId, offer.RideOffer.Car, new Route(stops))); }
bool DriverConfirm(UserRideOffer offer) { //TODO there is the case where there are no riders, and we should go ahead and cancel, but if there were never //any riders, the pending ride maybe shouldn't have been made in the first place? if (offer != this.offer) { return(false); } driverTimer.Stop(); //No longer can the user cancel through the offer offer.Canceled.Remove(OnRideOfferCanceled); offer.Changed -= OnRideOfferCanceled; offer.UserConfirmed(); bool hasPassengers; lock (requests) { if (requests.Any()) { hasPassengers = true; foreach (var rr in requests) { rr.DriverConfirmed(); } } else { hasPassengers = false; } } PostStatus(riderTimer.Interval).FireAndForgetAsync(Program.ErrorHandler); StateUpdated?.Invoke(this); if (hasPassengers) { state = PendingRideState.WaitingOnRiders; riderTimer.Start(); } else { DoneGettingConfirmations().FireAndForgetAsync(Program.ErrorHandler); } return(true); }
async static Task MakeRide(UserRideOffer offer, CancellationToken cancellationToken) { bool matched = false; do { // This is the preferred way of cancelling a task. Note that // a cancellation token can be made with a timeout. cancellationToken.ThrowIfCancellationRequested(); var potentialRide = await rideMatcher.GetRide(offer, rideRequestOrigins, rideRequestDestination); cancellationToken.ThrowIfCancellationRequested(); // NOTE: C# disallows using await inside a lock. This means that // the thread that is running MakeRide() will not enter OnRideRequestCanceled() // while it holds this lock (because it won't yield execution). // As long as no method call here invokes the Canceled event, // this guarantees that the locking will work as expected. lock (rideBuildLock) { IList <MatchableRideRequest> requests = potentialRide.Item1; // Some requests could have gotten canceled during the matching process. matched = requests.All(req => pendingRequests.ContainsKey(req.Request.Id)); if (matched) { // This must happen while we still hold the ride build lock // because because requests could become canceled. foreach (var request in requests) { RemoveRideRequest(request.Request); } var pendingRide = new PendingRide(potentialRide.Item2, offer, requests.Select((Mrr) => Mrr.Request).ToList(), rideMatcher); pendingRide.InitializeAndPost().FireAndForgetAsync(Program.ErrorHandler); } } } while (!matched); }
public Task <RideInfo> MakeBestRide(UserRideOffer offer, IEnumerable <UserRideRequest> requestsToMatch) { Route.Stop[] stops = new Route.Stop[requestsToMatch.Count() * 2 + 2]; stops[0] = new Route.Stop(offer.RideOffer.Trip.Source); int i = 1; foreach (UserRideRequest r in requestsToMatch) { stops[i++] = new Route.Stop(r.RideRequest.Trip.Source, r.User.UserInfo, true); stops[i++] = new Route.Stop(r.RideRequest.Trip.Destination, r.User.UserInfo, false); } stops[i] = new Route.Stop(offer.RideOffer.Trip.Destination); return(Task.FromResult(new RideInfo(offer.User.UserInfo.UserId, offer.RideOffer.Car, new Route(stops)))); }
/// <summary> /// Create a new <see cref="PendingRide"/> /// </summary> /// <param name="rideInfo">The original ride info of this ride</param> /// <param name="offer">The ride offer of this pending ride</param> /// <param name="requests">All the ride requests apart of this pending ride</param> /// <param name="rideMatcher">Used to create a new route if anything changes durring the confirmation phase</param> /// <param name="driverExpireTime">Time in milliseconds the driver has to confirm</param> /// <param name="riderExpireTime">Time in milliseconds the riders have to confirm, after the driver confirms</param> public PendingRide(RideInfo rideInfo, UserRideOffer offer, ICollection <UserRideRequest> requests, IRideMatcher rideMatcher, double driverExpireTime = 120000, double riderExpireTime = 120000) { RideInfo = rideInfo; Id = IdGenerator.GenerateNewId(this); this.offer = offer; this.requests = requests; this.rideMatcher = rideMatcher; userToUserRequest.TryAdd(offer.User, offer); foreach (UserRideRequest rr in requests) { userToUserRequest.TryAdd(rr.User, rr); } driverTimer = new Timer { AutoReset = false, Interval = driverExpireTime, Enabled = false }; riderTimer = new Timer { AutoReset = false, Interval = riderExpireTime, Enabled = false }; driverTimer.Elapsed += (a, b) => { offer.SetExpired(); Cancel(); UserTimedOut?.Invoke(offer.User.UserInfo.UserId, true, this); }; riderTimer.Elapsed += async(a, b) => await DoneGettingConfirmations(); }
public Task <RideInfo> MakeBestRide(UserRideOffer offer, IEnumerable <UserRideRequest> requestsToMatch) { // TODO: Other methods return a detailed route polyline, this one does not. var passengers = new List <UserRideRequest>(requestsToMatch); if (passengers.Count > 1) { throw new ArgumentException(); } Route.Stop[] stops; if (passengers.Count == 0) { stops = new[] { new Route.Stop(offer.RideOffer.Trip.Source), new Route.Stop(offer.RideOffer.Trip.Destination) }; } else { var passengerInfo = passengers[0].User.UserInfo; var passengerOrig = passengers[0].RideRequest.Trip.Source; var passengerDest = passengers[0].RideRequest.Trip.Destination; stops = new[] { new Route.Stop(offer.RideOffer.Trip.Source), new Route.Stop(passengerOrig, passengerInfo, true), new Route.Stop(passengerDest, passengerInfo), new Route.Stop(offer.RideOffer.Trip.Destination) }; } var route = new Route(stops); return(Task.FromResult( new RideInfo(offer.User.UserInfo.UserId, offer.RideOffer.Car, route))); }
static Task <RouteInfo> GetRouteWithPassenger(UserRideOffer offer, MatchableRideRequest passenger) { try { return(DirectionsService.ComputeApproximateDrivingInfo( Program.GoogleApiKey, offer.RideOffer.Trip.Source, offer.RideOffer.Trip.Destination, passenger.Request.RideRequest.Trip.Source, passenger.Request.RideRequest.Trip.Destination)); } catch (ApplicationException ex) { Program.LogError($"Failure in {nameof(SinglePassengerRideMatcher)}: {ex.Message}"); return(Task.FromResult(new RouteInfo(new GeoPolyline(new[] { offer.RideOffer.Trip.Source, passenger.Request.RideRequest.Trip.Source, passenger.Request.RideRequest.Trip.Destination, offer.RideOffer.Trip.Destination })))); } }
public async Task <(IList <PendingRideRequestCenter.MatchableRideRequest>, RideInfo)> GetRide( UserRideOffer offer, ConcurrentGeoQuadtree <PendingRideRequestCenter.MatchableRideRequest> origins, ConcurrentGeoQuadtree <PendingRideRequestCenter.MatchableRideRequest> destinations) { if (origins.Count == 0) { return( new List <PendingRideRequestCenter.MatchableRideRequest>(), new RideInfo(offer.User.UserInfo.UserId, offer.RideOffer.Car, new Route(new Route.Stop[] { new Route.Stop(offer.RideOffer.Trip.Source), new Route.Stop(offer.RideOffer.Trip.Destination) })) ); } RequestElement element = origins.GetElementsInside((r) => true).First(); UserInfo passenger = element.Data.Request.User.UserInfo; GeoCoordinates passengerPickUp = element.Coordinates; GeoCoordinates passengerDropOff = element.Data.DestinationElement.Coordinates; return( new List <PendingRideRequestCenter.MatchableRideRequest> { element.Data }, new RideInfo(offer.User.UserInfo.UserId, offer.RideOffer.Car, new Route(new Route.Stop[] { new Route.Stop(offer.RideOffer.Trip.Source), new Route.Stop(passengerPickUp, passenger, true), new Route.Stop(passengerDropOff, passenger), new Route.Stop(offer.RideOffer.Trip.Destination) })) ); }
GetRide(UserRideOffer offer, ConcurrentGeoQuadtree <MatchableRideRequest> origins, ConcurrentGeoQuadtree <MatchableRideRequest> destinations) { RouteInfo driverRoute = await GetRoute(offer); var originsTask = GetElementsInsideAsync(origins, NearRoute); var destinationsTask = GetElementsInsideAsync(destinations, NearRoute); // Only consider passengers whose origins and destinations are near // the driver's route. var potentialPassengers = new HashSet <MatchableRideRequest>( from element in await originsTask select element.Data); potentialPassengers.IntersectWith( from element in await destinationsTask select element.Data); // Find a passenger going in the same direction as the driver such that // picking up the passenger does not put the driver too far out of their way. foreach (var passenger in potentialPassengers.Where(GoingInDriversDirection)) { RouteInfo routeWithPassenger = await GetRouteWithPassenger(offer, passenger); // Reject route if it's too far out of the way according to // the driver's settings. if (driverRoute.drivingTime.HasValue && routeWithPassenger.drivingTime.HasValue) { TimeSpan originalTime = driverRoute.drivingTime.Value; TimeSpan newTime = routeWithPassenger.drivingTime.Value; TimeSpan maxTime = originalTime + TimeSpan.FromMinutes(offer.RideOffer.MaxTimeOutOfWay); if (newTime > maxTime) { // Output debug info for demos. Program.LogError($"Matched {offer.User.UserInfo.UserId} with {passenger.Request.User.UserInfo.UserId}" + " but resulting route was too long." + $" Original trip duration: {originalTime.Minutes} mins." + $" Matched trip duration: {newTime.Minutes} mins." + $" Driver's max time out of way: {offer.RideOffer.MaxTimeOutOfWay} mins."); continue; } } return(RideWithPassenger(offer, passenger)); } return(EmptyRide(offer, driverRoute)); /// <summary> /// Tests whether any point in the rect is close enough to <see cref="route"/>. /// </summary> bool NearRoute(Rect rect) { // Ignore passengers more than approximately 1km of the route. // TODO Take large max-time-out-of-way values into account when choosing max-dist-out-of-way. double maxDistMeters = 1000; double maxDistDegrees = offer.RideOffer.Trip.Source.DegreesUpperBound(maxDistMeters); GeoPolyline route = driverRoute.overviewPolyline; return(route.RectWithinDistance(rect, maxDistDegrees)); } /// <summary> /// Tests whether this passenger is going in the same direction as the driver. /// </summary> bool GoingInDriversDirection(MatchableRideRequest request) { var driverDest = offer.RideOffer.Trip.Destination; var driverOrig = offer.RideOffer.Trip.Source; var driverSeg = new GeoSegment(driverOrig, driverDest); var passDest = request.Request.RideRequest.Trip.Destination; var passOrig = request.Request.RideRequest.Trip.Source; var passSeg = new GeoSegment(passOrig, passDest); // Use GeoSegments so that this works near prime meridian. var passDiff = passSeg.Point2Representative - passSeg.Point1Representative; var driverDiff = driverSeg.Point2Representative - driverSeg.Point1Representative; // Compute the dot product of the vectors. This is a pretty rough // estimate and doesn't take into account the Earth's curvature. return(passDiff.Dot(driverDiff) > 0); } }
static Task <RouteInfo> GetRoute(UserRideOffer offer) { return(GetRouteBetween(offer.RideOffer.Trip.Source, offer.RideOffer.Trip.Destination)); }