Пример #1
0
    def test_hyperbolic_orbital_parameters(self):
        # Unlike the elliptical test, this tests our favourite extra-solar
        # visitor to make sure we can calculate Keplerian orbital
        # characteristics from its orbital state vectors! That's right, we're
        # talking about Sedna! The expected values are arrived at through
        # calculation, and also
        # http://orbitsimulator.com/formulas/OrbitalElements.html
        physics_state = common.load_savefile(
            common.savefile('tests/sedna.json'))
        sun = physics_state[0]
        oumuamua = physics_state[1]

        expected_semimajor_axis = -71231070.14146987
        self.assertAlmostEqual(calc.semimajor_axis(oumuamua, sun),
                               expected_semimajor_axis,
                               delta=abs(0.01 * expected_semimajor_axis))

        expected_eccentricity = 1644.477
        self.assertAlmostEqual(calc.fastnorm(calc.eccentricity(oumuamua, sun)),
                               expected_eccentricity,
                               delta=0.01 * expected_eccentricity)

        expected_periapsis = 1.1714e11  # Through calculation
        self.assertAlmostEqual(calc.periapsis(sun, oumuamua) + oumuamua.r,
                               expected_periapsis,
                               delta=0.01 * 78989185420.15271)
Пример #2
0
def main(args: argparse.Namespace):
    loadfile: Path
    if os.path.isabs(args.loadfile):
        loadfile = Path(args.loadfile)
    else:
        # Take paths relative to 'data/saves/'
        loadfile = common.savefile(args.loadfile)

    physics_engine = physics.PhysicsEngine(common.load_savefile(loadfile))
    initial_state = physics_engine.get_state()

    gui = flight_gui.FlightGui(initial_state,
                               title=name,
                               running_as_mirror=False)
    atexit.register(gui.shutdown)

    if args.flamegraph:
        common.start_flamegraphing()
    if args.profile:
        common.start_profiling()

    while True:
        state = physics_engine.get_state()

        # If we have any commands, process them so the simthread has as
        # much time as possible to restart before next update.
        physics_engine.handle_requests(gui.pop_commands())

        gui.draw(state)
        gui.rate(common.FRAMERATE)
Пример #3
0
    def test_elliptical_orbital_parameters(self):
        # Again, see
        # https://www.wolframalpha.com/input/?i=International+Space+Station
        # For these expected values
        physics_state = common.load_savefile(
            common.savefile('tests/gui-test.json'))
        iss = physics_state[0]
        earth = physics_state[1]

        # The semiaxes are relatively close to expected.
        self.assertAlmostEqual(calc.semimajor_axis(iss, earth),
                               6785e3,
                               delta=0.01 * earth.r)

        # The eccentricity is within 1e-6 of the expected.
        self.assertAlmostEqual(calc.fastnorm(calc.eccentricity(iss, earth)),
                               5.893e-4,
                               delta=1e-3)

        # The apoapsis is relatively close to expected.
        self.assertAlmostEqual(calc.apoapsis(iss, earth),
                               418.3e3,
                               delta=0.01 * earth.r)

        # The periapsis is relatively close to expected.
        self.assertAlmostEqual(calc.periapsis(iss, earth),
                               410.3e3,
                               delta=0.01 * earth.r)
Пример #4
0
    def test_speeds(self):
        physics_state = common.load_savefile(
            common.savefile('tests/gui-test.json'))
        iss = physics_state[0]
        earth = physics_state[1]

        self.assertAlmostEqual(calc.h_speed(iss, earth), 7665, delta=10)
        self.assertAlmostEqual(calc.v_speed(iss, earth), -0.1, delta=0.1)
Пример #5
0
    def test_drag(self):
        """Test that drag is small but noticeable during unpowered flight."""
        atmosphere_save = common.load_savefile(
            common.savefile('tests/atmosphere.json'))
        # The habitat starts 1 km in the air, the same speed as the Earth.

        hab = atmosphere_save.craft_entity()
        hab.vy += 10
        atmosphere_save[atmosphere_save.craft] = hab
        drag = calc.fastnorm(calc.drag(atmosphere_save))
        self.assertLess(59, drag)
        self.assertGreater(60, drag)
Пример #6
0
def lead_server_loop(args):
    """Main, 'while True'-style loop for a lead server. Blocking.
    See help text for command line arguments for what a lead server is."""

    # Before you make changes to the lead server architecture, consider that
    # the GRPC server runs in a separate thread than this thread!
    state_server = network.StateServer()

    log.info(f'Loading save at {args.data_location.path}')
    physics_engine = physics.PEngine(
        common.load_savefile(Path(args.data_location.path)))

    if not args.no_gui:
        global cleanup_function
        global ungraceful_shutdown
        gui = flight_gui.FlightGui(physics_engine.get_state(),
                                   no_intro=args.no_intro)
        cleanup_function = gui.shutdown
        ungraceful_shutdown = gui.ungraceful_shutdown

    server = grpc.server(concurrent.futures.ThreadPoolExecutor(max_workers=4))
    grpc_stubs.add_StateServerServicer_to_server(state_server, server)
    server.add_insecure_port(f'[::]:{args.serve_on_port}')
    server.start()  # This doesn't block!
    # Need a context manager from now on, to make sure the server always stops.
    with common.GrpcServerContext(server):
        log.info(f'Server running on port {args.serve_on_port}. Ctrl-C exits.')

        if args.profile:
            common.start_profiling()
        while True:
            user_commands = []
            state = physics_engine.get_state()
            state_server.notify_state_change(copy.deepcopy(state._proto_state))

            if not args.no_gui:
                user_commands += gui.pop_commands()
            user_commands += state_server.pop_commands()

            # If we have any commands, process them so the simthread has as
            # much time as possible to regenerate solutions before next update
            for command in user_commands:
                if command.ident == network.Request.NOOP:
                    continue
                log.info(f'Got command: {command}')
                physics_engine.handle_request(command)

            if not args.no_gui:
                gui.draw(state)
                gui.rate(common.FRAMERATE)
            else:
                time.sleep(1 / common.FRAMERATE)
Пример #7
0
def main(args: argparse.Namespace):
    # Before you make changes to this function, keep in mind that this function
    # starts a GRPC server that runs in a separate thread!
    state_server = network.StateServer()

    loadfile: Path
    if os.path.isabs(args.loadfile):
        loadfile = Path(args.loadfile)
    else:
        # Take paths relative to 'data/saves/'
        loadfile = common.savefile(args.loadfile)

    physics_engine = physics.PhysicsEngine(common.load_savefile(loadfile))
    initial_state = physics_engine.get_state()

    TICKS_BETWEEN_CLIENT_LIST_REFRESHES = 150
    ticks_until_next_client_list_refresh = 0

    server = grpc.server(
        concurrent.futures.ThreadPoolExecutor(max_workers=4))
    atexit.register(lambda: server.stop(grace=2))
    grpc_stubs.add_StateServerServicer_to_server(state_server, server)
    server.add_insecure_port(f'[::]:{network.DEFAULT_PORT}')
    state_server.notify_state_change(initial_state.as_proto())
    server.start()  # This doesn't block!

    gui = ServerGui()

    try:
        if args.flamegraph:
            common.start_flamegraphing()
        if args.profile:
            common.start_profiling()

        while True:
            # If we have any commands, process them immediately so input lag
            # is minimized.
            commands = state_server.pop_commands() + gui.pop_commands()
            physics_engine.handle_requests(commands)

            state = physics_engine.get_state()
            state_server.notify_state_change(state.as_proto())

            if ticks_until_next_client_list_refresh == 0:
                ticks_until_next_client_list_refresh = \
                    TICKS_BETWEEN_CLIENT_LIST_REFRESHES
                state_server.refresh_client_list()
            ticks_until_next_client_list_refresh -= 1

            gui.update(state, state_server.addr_to_connected_clients.values())
    finally:
        server.stop(grace=1)
Пример #8
0
 def test_longterm_stable_landing(self):
     """Test that landed ships have stable altitude in the long term."""
     savestate = common.load_savefile(common.savefile('OCESS.json'))
     initial_t = savestate.timestamp
     with PhysicsEngine('OCESS.json') as physics_engine:
         initial = physics_engine.get_state(initial_t + 10)
         physics_engine.handle_requests([
             network.Request(ident=network.Request.TIME_ACC_SET,
                             time_acc_set=common.TIME_ACCS[-1].value)
         ],
                                        requested_t=initial_t + 10)
         final = physics_engine.get_state(initial_t + 100_000)
         self.assertAlmostEqual(calc.fastnorm(initial['Earth'].pos -
                                              initial['Habitat'].pos),
                                initial['Earth'].r + initial['Habitat'].r,
                                delta=1)
         self.assertAlmostEqual(calc.fastnorm(final['Earth'].pos -
                                              final['Habitat'].pos),
                                final['Earth'].r + final['Habitat'].r,
                                delta=1)
Пример #9
0
def _one_request(request: Request, y0: PhysicsState) \
        -> PhysicsState:
    """Interface to set habitat controls.

    Use an argument to change habitat throttle or spinning, and simulation
    will restart with this new information."""
    log.info(f'At simtime={y0.timestamp}, '
             f'Got command {MessageToString(request, as_one_line=True)}')

    if request.ident != Request.TIME_ACC_SET:
        # Reveal the type of y0.craft as str (not None).
        assert y0.craft is not None

    if request.ident == Request.HAB_SPIN_CHANGE:
        if y0.navmode != Navmode['Manual']:
            # We're in autopilot, ignore this command
            return y0
        craft = y0.craft_entity()
        if not craft.landed():
            craft.spin += request.spin_change
    elif request.ident == Request.HAB_THROTTLE_CHANGE:
        y0.craft_entity().throttle += request.throttle_change
    elif request.ident == Request.HAB_THROTTLE_SET:
        y0.craft_entity().throttle = request.throttle_set
    elif request.ident == Request.TIME_ACC_SET:
        assert request.time_acc_set >= 0
        y0.time_acc = request.time_acc_set
    elif request.ident == Request.ENGINEERING_UPDATE:
        # Multiply this value by 100, because OrbitV considers engines at
        # 100% to be 100x the maximum thrust.
        common.craft_capabilities[HABITAT] = \
            common.craft_capabilities[HABITAT]._replace(
                thrust=100 * request.engineering_update.max_thrust)
        hab = y0[HABITAT]
        ayse = y0[AYSE]
        hab.fuel = request.engineering_update.hab_fuel
        ayse.fuel = request.engineering_update.ayse_fuel
        y0[HABITAT] = hab
        y0[AYSE] = ayse

        if request.engineering_update.module_state == \
                Request.DETACHED_MODULE and \
                MODULE not in y0._entity_names and \
                not hab.landed():
            # If the Habitat is freely floating and engineering asks us to
            # detach the Module, spawn in the Module.
            module = Entity(protos.Entity(
                name=MODULE, mass=100, r=10, artificial=True))
            module.pos = (hab.pos - (module.r + hab.r) *
                          calc.heading_vector(hab.heading))
            module.v = calc.rotational_speed(module, hab)

            y0_proto = y0.as_proto()
            y0_proto.entities.extend([module.proto])
            y0 = PhysicsState(None, y0_proto)

    elif request.ident == Request.UNDOCK:
        habitat = y0[HABITAT]

        if habitat.landed_on == AYSE:
            ayse = y0[AYSE]
            habitat.landed_on = ''

            norm = habitat.pos - ayse.pos
            unit_norm = norm / calc.fastnorm(norm)
            habitat.v += unit_norm * common.UNDOCK_PUSH
            habitat.spin = ayse.spin

            y0[HABITAT] = habitat

    elif request.ident == Request.REFERENCE_UPDATE:
        y0.reference = request.reference
    elif request.ident == Request.TARGET_UPDATE:
        y0.target = request.target
    elif request.ident == Request.LOAD_SAVEFILE:
        y0 = common.load_savefile(common.savefile(request.loadfile))
    elif request.ident == Request.NAVMODE_SET:
        y0.navmode = Navmode(request.navmode)
        if y0.navmode == Navmode['Manual']:
            y0.craft_entity().spin = 0
    elif request.ident == Request.PARACHUTE:
        y0.parachute_deployed = request.deploy_parachute
    elif request.ident == Request.IGNITE_SRBS:
        if round(y0.srb_time) == common.SRB_FULL:
            y0.srb_time = common.SRB_BURNTIME
    elif request.ident == Request.TOGGLE_COMPONENT:
        component = y0.engineering.components[request.component_to_toggle]
        component.connected = not component.connected
    elif request.ident == Request.TOGGLE_RADIATOR:
        radiator = y0.engineering.radiators[request.radiator_to_toggle]
        radiator.functioning = not radiator.functioning
    elif request.ident == Request.CONNECT_RADIATOR_TO_LOOP:
        radiator = y0.engineering.radiators[request.radiator_to_loop.rad]
        radiator.attached_to_coolant_loop = request.radiator_to_loop.loop
    elif request.ident == Request.TOGGLE_COMPONENT_COOLANT:
        component = y0.engineering.components[request.component_to_loop.component]
        # See comments on _component_coolant_cnxn_transitions for more info.
        component.coolant_connection = (
            _component_coolant_cnxn_transitions
            [request.component_to_loop.loop]
            [component.coolant_connection]
        )

    return y0
Пример #10
0
 def __init__(self, savefile):
     self.physics_engine = physics.PhysicsEngine(
         common.load_savefile(common.savefile(savefile)))