mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-12-01 02:33:54 +03:00
clean up dupe logic in popdat for figuring out path from a trip. instead just repeat the main path used in simulation layer
This commit is contained in:
parent
8ed42e9fb6
commit
f73e0c15af
@ -29,9 +29,14 @@ impl TripsVisualizer {
|
|||||||
let trips = ctx.loading_screen("load trip data", |_, mut timer| {
|
let trips = ctx.loading_screen("load trip data", |_, mut timer| {
|
||||||
let (all_trips, _) = clip_trips(&ui.primary.map, &mut timer);
|
let (all_trips, _) = clip_trips(&ui.primary.map, &mut timer);
|
||||||
let map = &ui.primary.map;
|
let map = &ui.primary.map;
|
||||||
|
let sim = &ui.primary.sim;
|
||||||
|
let flags = &ui.primary.current_flags.sim_flags;
|
||||||
let maybe_trips =
|
let maybe_trips =
|
||||||
timer.parallelize("calculate paths with geometry", all_trips, |trip| {
|
timer.parallelize("calculate paths with geometry", all_trips, |trip| {
|
||||||
if let Some(req) = trip.path_req(map) {
|
if let Some(spawn_trip) = trip.to_spawn_trip(map) {
|
||||||
|
let mut rng = flags.make_rng();
|
||||||
|
let (_, spec) = spawn_trip.to_trip_spec(&mut rng);
|
||||||
|
let req = sim.trip_spec_to_path_req(&spec, map);
|
||||||
if let Some(route) = map
|
if let Some(route) = map
|
||||||
.pathfind(req.clone())
|
.pathfind(req.clone())
|
||||||
.and_then(|path| path.trace(map, req.start.dist_along(), None))
|
.and_then(|path| path.trace(map, req.start.dist_along(), None))
|
||||||
|
@ -2,9 +2,7 @@ use crate::psrc::{Endpoint, Mode, Parcel, Purpose};
|
|||||||
use crate::PopDat;
|
use crate::PopDat;
|
||||||
use abstutil::Timer;
|
use abstutil::Timer;
|
||||||
use geom::{Distance, Duration, LonLat, Polygon, Pt2D};
|
use geom::{Distance, Duration, LonLat, Polygon, Pt2D};
|
||||||
use map_model::{
|
use map_model::{BuildingID, IntersectionID, LaneType, Map, PathConstraints, Position};
|
||||||
BuildingID, IntersectionID, LaneType, Map, PathConstraints, PathRequest, Position,
|
|
||||||
};
|
|
||||||
use sim::{DrivingGoal, Scenario, SidewalkSpot, SpawnTrip, TripSpec};
|
use sim::{DrivingGoal, Scenario, SidewalkSpot, SpawnTrip, TripSpec};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
|
||||||
@ -33,48 +31,86 @@ impl Trip {
|
|||||||
self.depart_at + self.trip_time
|
self.depart_at + self.trip_time
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path_req(&self, map: &Map) -> Option<PathRequest> {
|
pub fn to_spawn_trip(&self, map: &Map) -> Option<SpawnTrip> {
|
||||||
Some(match self.mode {
|
match self.mode {
|
||||||
Mode::Walk => PathRequest {
|
Mode::Drive => match self.from {
|
||||||
start: self.from.start_sidewalk_spot(map).sidewalk_pos,
|
TripEndpt::Border(i, _) => {
|
||||||
end: self.from.end_sidewalk_spot(map).sidewalk_pos,
|
if let Some(start) = TripSpec::spawn_car_at(
|
||||||
constraints: PathConstraints::Pedestrian,
|
Position::new(
|
||||||
},
|
map.get_i(i).get_outgoing_lanes(map, LaneType::Driving)[0],
|
||||||
Mode::Bike => PathRequest {
|
Distance::ZERO,
|
||||||
start: self.from.start_pos_driving(map)?,
|
),
|
||||||
end: self
|
map,
|
||||||
.to
|
) {
|
||||||
.driving_goal(PathConstraints::Bike, map)
|
Some(SpawnTrip::CarAppearing {
|
||||||
.goal_pos(map),
|
depart: self.depart_at,
|
||||||
constraints: PathConstraints::Bike,
|
start,
|
||||||
},
|
goal: self.to.driving_goal(PathConstraints::Car, map),
|
||||||
Mode::Drive => PathRequest {
|
is_bike: false,
|
||||||
start: self.from.start_pos_driving(map)?,
|
})
|
||||||
end: self
|
} else {
|
||||||
.to
|
// TODO need to be able to emit warnings from parallelize
|
||||||
.driving_goal(PathConstraints::Car, map)
|
//timer.warn(format!("No room for car to appear at {:?}", self.from));
|
||||||
.goal_pos(map),
|
None
|
||||||
constraints: PathConstraints::Car,
|
|
||||||
},
|
|
||||||
Mode::Transit => {
|
|
||||||
let start = self.from.start_sidewalk_spot(map).sidewalk_pos;
|
|
||||||
let end = self.to.end_sidewalk_spot(map).sidewalk_pos;
|
|
||||||
if let Some((stop1, _, _)) = map.should_use_transit(start, end) {
|
|
||||||
PathRequest {
|
|
||||||
start,
|
|
||||||
end: SidewalkSpot::bus_stop(stop1, map).sidewalk_pos,
|
|
||||||
constraints: PathConstraints::Pedestrian,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Just fall back to walking. :\
|
|
||||||
PathRequest {
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
constraints: PathConstraints::Pedestrian,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
TripEndpt::Building(b) => Some(SpawnTrip::MaybeUsingParkedCar(
|
||||||
|
self.depart_at,
|
||||||
|
b,
|
||||||
|
self.to.driving_goal(PathConstraints::Car, map),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
Mode::Bike => match self.from {
|
||||||
|
TripEndpt::Building(b) => Some(SpawnTrip::UsingBike(
|
||||||
|
self.depart_at,
|
||||||
|
SidewalkSpot::building(b, map),
|
||||||
|
self.to.driving_goal(PathConstraints::Bike, map),
|
||||||
|
)),
|
||||||
|
TripEndpt::Border(i, _) => {
|
||||||
|
if let Some(start) = TripSpec::spawn_car_at(
|
||||||
|
Position::new(
|
||||||
|
map.get_i(i).get_outgoing_lanes(map, LaneType::Driving)[0],
|
||||||
|
Distance::ZERO,
|
||||||
|
),
|
||||||
|
map,
|
||||||
|
) {
|
||||||
|
Some(SpawnTrip::CarAppearing {
|
||||||
|
depart: self.depart_at,
|
||||||
|
start,
|
||||||
|
goal: self.to.driving_goal(PathConstraints::Bike, map),
|
||||||
|
is_bike: true,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
//timer.warn(format!("No room for bike to appear at {:?}", self.from));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Mode::Walk => Some(SpawnTrip::JustWalking(
|
||||||
|
self.depart_at,
|
||||||
|
self.from.start_sidewalk_spot(map),
|
||||||
|
self.to.end_sidewalk_spot(map),
|
||||||
|
)),
|
||||||
|
Mode::Transit => {
|
||||||
|
let start = self.from.start_sidewalk_spot(map);
|
||||||
|
let goal = self.to.end_sidewalk_spot(map);
|
||||||
|
if let Some((stop1, stop2, route)) =
|
||||||
|
map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
|
||||||
|
{
|
||||||
|
Some(SpawnTrip::UsingTransit(
|
||||||
|
self.depart_at,
|
||||||
|
start,
|
||||||
|
goal,
|
||||||
|
route,
|
||||||
|
stop1,
|
||||||
|
stop2,
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
//timer.warn(format!("{:?} not actually using transit, because pathfinding didn't find any useful route", trip));
|
||||||
|
Some(SpawnTrip::JustWalking(self.depart_at, start, goal))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,19 +149,6 @@ impl TripEndpt {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO or biking
|
|
||||||
// TODO bldg_via_driving needs to do find_driving_lane_near_building sometimes
|
|
||||||
// Doesn't adjust for starting length yet.
|
|
||||||
fn start_pos_driving(&self, map: &Map) -> Option<Position> {
|
|
||||||
match self {
|
|
||||||
TripEndpt::Building(b) => Position::bldg_via_driving(*b, map),
|
|
||||||
TripEndpt::Border(i, _) => Some(Position::new(
|
|
||||||
map.get_i(*i).get_outgoing_lanes(map, LaneType::Driving)[0],
|
|
||||||
Distance::ZERO,
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn driving_goal(&self, constraints: PathConstraints, map: &Map) -> DrivingGoal {
|
fn driving_goal(&self, constraints: PathConstraints, map: &Map) -> DrivingGoal {
|
||||||
match self {
|
match self {
|
||||||
TripEndpt::Building(b) => DrivingGoal::ParkNear(*b),
|
TripEndpt::Building(b) => DrivingGoal::ParkNear(*b),
|
||||||
@ -199,7 +222,7 @@ pub fn clip_trips(map: &Map, timer: &mut Timer) -> (Vec<Trip>, HashMap<BuildingI
|
|||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut trip = Trip {
|
let trip = Trip {
|
||||||
from,
|
from,
|
||||||
to,
|
to,
|
||||||
depart_at: trip.depart_at,
|
depart_at: trip.depart_at,
|
||||||
@ -217,27 +240,8 @@ pub fn clip_trips(map: &Map, timer: &mut Timer) -> (Vec<Trip>, HashMap<BuildingI
|
|||||||
// Fix depart_at, trip_time, and trip_dist for border cases. Assume constant speed
|
// Fix depart_at, trip_time, and trip_dist for border cases. Assume constant speed
|
||||||
// through the trip.
|
// through the trip.
|
||||||
// TODO Disabled because slow and nonsensical distance ratios. :(
|
// TODO Disabled because slow and nonsensical distance ratios. :(
|
||||||
(TripEndpt::Border(_, _), TripEndpt::Building(_)) => {
|
(TripEndpt::Border(_, _), TripEndpt::Building(_)) => {}
|
||||||
if false {
|
(TripEndpt::Building(_), TripEndpt::Border(_, _)) => {}
|
||||||
// TODO Figure out why some paths fail.
|
|
||||||
// TODO Since we're doing the work anyway, store the result?
|
|
||||||
let dist = map.pathfind(trip.path_req(map)?)?.total_length();
|
|
||||||
// TODO This is failing all over the place, why?
|
|
||||||
assert!(dist <= trip.trip_dist);
|
|
||||||
let trip_time = (dist / trip.trip_dist) * trip.trip_time;
|
|
||||||
trip.depart_at += trip.trip_time - trip_time;
|
|
||||||
trip.trip_time = trip_time;
|
|
||||||
trip.trip_dist = dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(TripEndpt::Building(_), TripEndpt::Border(_, _)) => {
|
|
||||||
if false {
|
|
||||||
let dist = map.pathfind(trip.path_req(map)?)?.total_length();
|
|
||||||
assert!(dist <= trip.trip_dist);
|
|
||||||
trip.trip_time = (dist / trip.trip_dist) * trip.trip_time;
|
|
||||||
trip.trip_dist = dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(TripEndpt::Building(_), TripEndpt::Building(_)) => {}
|
(TripEndpt::Building(_), TripEndpt::Building(_)) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,85 +263,7 @@ pub fn trips_to_scenario(map: &Map, timer: &mut Timer) -> Scenario {
|
|||||||
// TODO Don't clone trips for parallelize
|
// TODO Don't clone trips for parallelize
|
||||||
let individ_trips = timer
|
let individ_trips = timer
|
||||||
.parallelize("turn PSRC trips into SpawnTrips", trips.clone(), |trip| {
|
.parallelize("turn PSRC trips into SpawnTrips", trips.clone(), |trip| {
|
||||||
match trip.mode {
|
trip.to_spawn_trip(map)
|
||||||
Mode::Drive => match trip.from {
|
|
||||||
TripEndpt::Border(i, _) => {
|
|
||||||
if let Some(start) = TripSpec::spawn_car_at(
|
|
||||||
Position::new(
|
|
||||||
map.get_i(i).get_outgoing_lanes(map, LaneType::Driving)[0],
|
|
||||||
Distance::ZERO,
|
|
||||||
),
|
|
||||||
map,
|
|
||||||
) {
|
|
||||||
Some(SpawnTrip::CarAppearing {
|
|
||||||
depart: trip.depart_at,
|
|
||||||
start,
|
|
||||||
goal: trip.to.driving_goal(PathConstraints::Car, map),
|
|
||||||
is_bike: false,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// TODO need to be able to emit warnings from parallelize
|
|
||||||
//timer.warn(format!("No room for car to appear at {:?}", trip.from));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TripEndpt::Building(b) => Some(SpawnTrip::MaybeUsingParkedCar(
|
|
||||||
trip.depart_at,
|
|
||||||
b,
|
|
||||||
trip.to.driving_goal(PathConstraints::Car, map),
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
Mode::Bike => match trip.from {
|
|
||||||
TripEndpt::Building(b) => Some(SpawnTrip::UsingBike(
|
|
||||||
trip.depart_at,
|
|
||||||
SidewalkSpot::building(b, map),
|
|
||||||
trip.to.driving_goal(PathConstraints::Bike, map),
|
|
||||||
)),
|
|
||||||
TripEndpt::Border(i, _) => {
|
|
||||||
if let Some(start) = TripSpec::spawn_car_at(
|
|
||||||
Position::new(
|
|
||||||
map.get_i(i).get_outgoing_lanes(map, LaneType::Driving)[0],
|
|
||||||
Distance::ZERO,
|
|
||||||
),
|
|
||||||
map,
|
|
||||||
) {
|
|
||||||
Some(SpawnTrip::CarAppearing {
|
|
||||||
depart: trip.depart_at,
|
|
||||||
start,
|
|
||||||
goal: trip.to.driving_goal(PathConstraints::Bike, map),
|
|
||||||
is_bike: true,
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
//timer.warn(format!("No room for bike to appear at {:?}", trip.from));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Mode::Walk => Some(SpawnTrip::JustWalking(
|
|
||||||
trip.depart_at,
|
|
||||||
trip.from.start_sidewalk_spot(map),
|
|
||||||
trip.to.end_sidewalk_spot(map),
|
|
||||||
)),
|
|
||||||
Mode::Transit => {
|
|
||||||
let start = trip.from.start_sidewalk_spot(map);
|
|
||||||
let goal = trip.to.end_sidewalk_spot(map);
|
|
||||||
if let Some((stop1, stop2, route)) =
|
|
||||||
map.should_use_transit(start.sidewalk_pos, goal.sidewalk_pos)
|
|
||||||
{
|
|
||||||
Some(SpawnTrip::UsingTransit(
|
|
||||||
trip.depart_at,
|
|
||||||
start,
|
|
||||||
goal,
|
|
||||||
route,
|
|
||||||
stop1,
|
|
||||||
stop2,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
//timer.warn(format!("{:?} not actually using transit, because pathfinding didn't find any useful route", trip));
|
|
||||||
Some(SpawnTrip::JustWalking(trip.depart_at, start, goal))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
|
@ -151,79 +151,9 @@ impl Scenario {
|
|||||||
|
|
||||||
timer.start_iter("SpawnTrip", self.individ_trips.len());
|
timer.start_iter("SpawnTrip", self.individ_trips.len());
|
||||||
for t in &self.individ_trips {
|
for t in &self.individ_trips {
|
||||||
match t.clone() {
|
|
||||||
SpawnTrip::CarAppearing {
|
|
||||||
depart,
|
|
||||||
start,
|
|
||||||
goal,
|
|
||||||
is_bike,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
sim.schedule_trip(
|
|
||||||
depart,
|
|
||||||
TripSpec::CarAppearing {
|
|
||||||
start_pos: start,
|
|
||||||
goal,
|
|
||||||
vehicle_spec: if is_bike {
|
|
||||||
Scenario::rand_bike(rng)
|
|
||||||
} else {
|
|
||||||
Scenario::rand_car(rng)
|
|
||||||
},
|
|
||||||
ped_speed: Scenario::rand_ped_speed(rng),
|
|
||||||
},
|
|
||||||
map,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SpawnTrip::MaybeUsingParkedCar(depart, start_bldg, goal) => {
|
|
||||||
sim.schedule_trip(
|
|
||||||
depart,
|
|
||||||
TripSpec::MaybeUsingParkedCar {
|
|
||||||
start_bldg,
|
|
||||||
goal,
|
|
||||||
ped_speed: Scenario::rand_ped_speed(rng),
|
|
||||||
},
|
|
||||||
map,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SpawnTrip::UsingBike(depart, start, goal) => {
|
|
||||||
sim.schedule_trip(
|
|
||||||
depart,
|
|
||||||
TripSpec::UsingBike {
|
|
||||||
start,
|
|
||||||
goal,
|
|
||||||
vehicle: Scenario::rand_bike(rng),
|
|
||||||
ped_speed: Scenario::rand_ped_speed(rng),
|
|
||||||
},
|
|
||||||
map,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SpawnTrip::JustWalking(depart, start, goal) => {
|
|
||||||
sim.schedule_trip(
|
|
||||||
depart,
|
|
||||||
TripSpec::JustWalking {
|
|
||||||
start,
|
|
||||||
goal,
|
|
||||||
ped_speed: Scenario::rand_ped_speed(rng),
|
|
||||||
},
|
|
||||||
map,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
SpawnTrip::UsingTransit(depart, start, goal, route, stop1, stop2) => {
|
|
||||||
sim.schedule_trip(
|
|
||||||
depart,
|
|
||||||
TripSpec::UsingTransit {
|
|
||||||
start,
|
|
||||||
goal,
|
|
||||||
route,
|
|
||||||
stop1,
|
|
||||||
stop2,
|
|
||||||
ped_speed: Scenario::rand_ped_speed(rng),
|
|
||||||
},
|
|
||||||
map,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
timer.next();
|
timer.next();
|
||||||
|
let (depart, spec) = t.clone().to_trip_spec(rng);
|
||||||
|
sim.schedule_trip(depart, spec, map);
|
||||||
}
|
}
|
||||||
|
|
||||||
sim.spawn_all_trips(map, timer, true);
|
sim.spawn_all_trips(map, timer, true);
|
||||||
@ -900,3 +830,66 @@ pub enum SpawnTrip {
|
|||||||
BusStopID,
|
BusStopID,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl SpawnTrip {
|
||||||
|
// (departure time, spec)
|
||||||
|
pub fn to_trip_spec(self, rng: &mut XorShiftRng) -> (Duration, TripSpec) {
|
||||||
|
match self {
|
||||||
|
SpawnTrip::CarAppearing {
|
||||||
|
depart,
|
||||||
|
start,
|
||||||
|
goal,
|
||||||
|
is_bike,
|
||||||
|
..
|
||||||
|
} => (
|
||||||
|
depart,
|
||||||
|
TripSpec::CarAppearing {
|
||||||
|
start_pos: start,
|
||||||
|
goal,
|
||||||
|
vehicle_spec: if is_bike {
|
||||||
|
Scenario::rand_bike(rng)
|
||||||
|
} else {
|
||||||
|
Scenario::rand_car(rng)
|
||||||
|
},
|
||||||
|
ped_speed: Scenario::rand_ped_speed(rng),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SpawnTrip::MaybeUsingParkedCar(depart, start_bldg, goal) => (
|
||||||
|
depart,
|
||||||
|
TripSpec::MaybeUsingParkedCar {
|
||||||
|
start_bldg,
|
||||||
|
goal,
|
||||||
|
ped_speed: Scenario::rand_ped_speed(rng),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SpawnTrip::UsingBike(depart, start, goal) => (
|
||||||
|
depart,
|
||||||
|
TripSpec::UsingBike {
|
||||||
|
start,
|
||||||
|
goal,
|
||||||
|
vehicle: Scenario::rand_bike(rng),
|
||||||
|
ped_speed: Scenario::rand_ped_speed(rng),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SpawnTrip::JustWalking(depart, start, goal) => (
|
||||||
|
depart,
|
||||||
|
TripSpec::JustWalking {
|
||||||
|
start,
|
||||||
|
goal,
|
||||||
|
ped_speed: Scenario::rand_ped_speed(rng),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SpawnTrip::UsingTransit(depart, start, goal, route, stop1, stop2) => (
|
||||||
|
depart,
|
||||||
|
TripSpec::UsingTransit {
|
||||||
|
start,
|
||||||
|
goal,
|
||||||
|
route,
|
||||||
|
stop1,
|
||||||
|
stop2,
|
||||||
|
ped_speed: Scenario::rand_ped_speed(rng),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -464,7 +464,11 @@ impl TripSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_pathfinding_request(&self, map: &Map, parking: &ParkingSimState) -> PathRequest {
|
pub(crate) fn get_pathfinding_request(
|
||||||
|
&self,
|
||||||
|
map: &Map,
|
||||||
|
parking: &ParkingSimState,
|
||||||
|
) -> PathRequest {
|
||||||
match self {
|
match self {
|
||||||
TripSpec::CarAppearing {
|
TripSpec::CarAppearing {
|
||||||
start_pos,
|
start_pos,
|
||||||
|
@ -952,6 +952,10 @@ impl Sim {
|
|||||||
self.driving
|
self.driving
|
||||||
.find_blockage_front(car, map, &self.intersections)
|
.find_blockage_front(car, map, &self.intersections)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn trip_spec_to_path_req(&self, spec: &TripSpec, map: &Map) -> PathRequest {
|
||||||
|
spec.get_pathfinding_request(map, &self.parking)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invasive debugging
|
// Invasive debugging
|
||||||
|
Loading…
Reference in New Issue
Block a user