示例#1
0
        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)));
        }
示例#5
0
        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);
        }
示例#7
0
        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))));
        }
示例#8
0
        /// <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
                }))));
            }
        }
示例#11
0
        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));
 }