Skip to content

Commit

Permalink
Server connect/reconnect fixes
Browse files Browse the repository at this point in the history
- Auto reconnect on disconnect
- Show status message while connecting
- Try to connect for 30 seconds
  • Loading branch information
dsrw committed Nov 21, 2023
1 parent 66b0fb0 commit 60bd519
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 11 deletions.
2 changes: 1 addition & 1 deletion enu.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ requires "nim >= 1.6.10",
"https://github.com/dsrw/Nim#3b33173",
"https://github.com/arnetheduck/nim-results#f3c666a",
"https://github.com/dsrw/godot-nim#892c482",
"https://github.com/dsrw/model_citizen 0.18.15",
"https://github.com/dsrw/model_citizen 0.18.16",
"https://github.com/dsrw/nanoid.nim 0.2.1",
"cligen 1.6.0",
"https://github.com/treeform/pretty",
Expand Down
33 changes: 29 additions & 4 deletions src/controllers/script_controllers/worker.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import std / [locks, os, random]
import std / [locks, os, random, net]
import std / times except seconds
from pkg / netty import Reactor
import core, models, models / [serializers], libs / [interpreters, eval]
import ./ [vars, host_bridge, scripting]

Expand Down Expand Up @@ -78,7 +79,6 @@ proc change_code(self: Worker, unit: Unit, code: Code) =
self.interpreter.reset_module(unit.script_ctx.module_name)
debug "reset module", module = unit.script_ctx.module_name
unit.script_ctx.running = false
remove_file unit.script_ctx.script
self.module_names.excl unit.script_ctx.module_name
elif code.nim.strip != "":
debug "loading unit", unit_id = unit.id
Expand Down Expand Up @@ -189,7 +189,9 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
if ?unit.script_ctx:
unit.script_ctx.running = false
unit.script_ctx.callback = nil
if LoadingScript notin state.local_flags and not ?unit.clone_of:
if not (unit of Player) and LoadingScript notin state.local_flags and
not ?unit.clone_of:

remove_file unit.script_ctx.script
remove_dir unit.data_dir

Expand All @@ -205,6 +207,7 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
if Server in state.local_flags:
state.units.add player
else:
state.push_flag(Connecting)
let tmp_path = join_path(state.config.work_dir, "tmp")
create_dir tmp_path
state.config_value.value:
Expand Down Expand Up @@ -235,8 +238,19 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
level_dir = change.item.level_dir
if level_dir != "":
worker.load_level(level_dir)

else:
Zen.thread_ctx.subscribe(connect_address)
var timeout_at = get_mono_time() + 30.seconds
var connected = false
while not connected and get_mono_time() < timeout_at:
try:
Zen.thread_ctx.subscribe(connect_address)
connected = true
except ConnectionError:
discard
if not connected:
fail \"Unable to connect to server at {connect_address}"
state.pop_flag(Connecting)
state.units.add player
player.script_ctx.interpreter = worker.interpreter
worker.load_script_and_dependents(player)
Expand All @@ -256,6 +270,8 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
save_level(state.config.level_dir)
state.pop_flag Quitting
running = false
elif NeedsRestart.added:
running = false

const max_time = (1.0 / 30.0).seconds
const min_time = (1.0 / 120.0).seconds
Expand All @@ -279,6 +295,10 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
else:
i += 1

if Server notin state.local_flags:
state.push_flag(NeedsRestart)
break

var to_process: seq[Unit]
state.units.value.walk_tree proc(unit: Unit) = to_process.add unit
to_process.shuffle
Expand Down Expand Up @@ -308,6 +328,11 @@ proc worker_thread(params: (ZenContext, GameState)) {.gcsafe.} =
if frame_end < wait_until:
sleep int((wait_until - frame_end).in_milliseconds)

if NeedsRestart in state.local_flags:
if ?listen_address:
private_access Reactor
Zen.thread_ctx.reactor.socket.close
state.pop_flag NeedsRestart
Zen.thread_ctx.boop

proc launch_worker*(ctx: ZenContext, state: GameState): Thread[tuple[
Expand Down
4 changes: 2 additions & 2 deletions src/core.nim
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ proc update_action_index*(state: GameState, change: int) =

state.tool = Tools(index)

template watch*(zen: Zen, unit: untyped, body: untyped) =
template watch*[T, O](zen: Zen[T, O], unit: untyped, body: untyped) =
when unit is Unit:
mixin thread_ctx
let zid = zen.changes:
Expand All @@ -228,7 +228,7 @@ template watch*(zen: Zen, unit: untyped, body: untyped) =
"can be passed explicitly, or found implicitly by evaluating " &
"`self.model`, then `self`." .}

template watch*(zen: Zen, body: untyped) =
template watch*[T, O](zen: Zen[T, O], body: untyped) =
when compiles(self.model):
watch(zen, self.model, body)
else:
Expand Down
48 changes: 46 additions & 2 deletions src/game.nim
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ when defined(metrics):

ZenContext.init_metrics "main", "worker"

var saved_transform {.threadvar.}: Transform
var saved_rotation {.threadvar.}: float

gdobj Game of Node:
var
reticle: Control
Expand All @@ -27,6 +30,7 @@ gdobj Game of Node:
saved_mouse_position: Vector2
rescale_at = get_mono_time()
update_metrics_at = get_mono_time()
force_quit_at = MonoTime.high
node_controller: NodeController
script_controller: ScriptController

Expand Down Expand Up @@ -61,6 +65,9 @@ level: {state.level_name}
self.rescale_at = MonoTime.high
self.rescale()

if time > self.force_quit_at:
state.pop_flag Quitting

proc rescale*() =
let vp = self.get_viewport().size
state.scale_factor = sqrt(state.config.mega_pixels *
Expand All @@ -71,6 +78,7 @@ level: {state.level_name}
method notification*(what: int) =
if what == main_loop.NOTIFICATION_WM_QUIT_REQUEST:
state.push_flag Quitting

if what == main_loop.NOTIFICATION_WM_ABOUT:
alert \"Enu {enu_version}\n\n© 2023 Scott Wadden", "Enu"

Expand Down Expand Up @@ -219,9 +227,39 @@ level: {state.level_name}
self.stats = self.find_node("stats").as(Label)
self.stats.visible = state.config.show_stats

state.local_flags.changes:
if Quitting.removed:
state.player_value.changes:
if added and ?change.item and ?saved_transform:
change.item.transform = saved_transform
change.item.rotation = saved_rotation
saved_transform = Transform.init
saved_rotation = 0.0

state.local_flags.changes(false):
if Quitting.added:
# We don't quit until the worker thread acks by popping the `Quitting`
# flag, giving it a chance to save and cleanup. If the worker thread is
# stuck, killed, or hasn't fully started because it's trying to connect
# to a server, it won't pop the flag, so we force it after a timeout.
self.force_quit_at = get_mono_time() + 2.seconds
elif Quitting.removed:
self.get_tree().quit()

if NeedsRestart.removed:
saved_transform = state.player.transform
saved_rotation = state.player.rotation
discard self.get_tree.reload_current_scene()

if Connecting.added:
state.status_message = \"""
# Connecting...
Trying to connect to {state.config.connect_address}.
"""
elif Connecting.removed:
state.status_message = ""

if MouseCaptured.added:
let center = self.get_viewport().get_visible_rect().size * 0.5
self.saved_mouse_position = self.get_viewport().get_mouse_position()
Expand Down Expand Up @@ -287,6 +325,12 @@ level: {state.level_name}
event.as(InputEventKey).scancode == KEY_ENTER):

set_window_fullscreen not is_window_fullscreen()
var user_config = load_user_config()
state.config_value.value:
start_full_screen = is_window_fullscreen()

user_config.start_full_screen = some(is_window_fullscreen())
save_user_config(user_config)
elif event.is_action_pressed("next_level"):
self.switch_world(+1)
elif event.is_action_pressed("prev_level"):
Expand Down
3 changes: 2 additions & 1 deletion src/models/states.nim
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ proc init*(_: type GameState): GameState =
open_sign_value: ~(Sign, flags),
wants: ~(seq[LocalStateFlags], flags),
level_name_value: ~("", id = "level_name"),
queued_action_value: ~("", flags)
queued_action_value: ~("", flags),
status_message_value: ~("", flags),
)
result = self
self.open_unit_value.changes:
Expand Down
1 change: 1 addition & 0 deletions src/nodes/player_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ gdobj PlayerNode of KinematicBody:
if added:
self.transform = change.item

self.camera_rig.rotation = vec3(0, deg_to_rad self.model.rotation, 0)
self.rotation_zid = self.model.rotation_value.watch:
if added or touched:
self.camera_rig.rotation = vec3(0, deg_to_rad change.item, 0)
Expand Down
3 changes: 2 additions & 1 deletion src/types.nim
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type
BlockTargetVisible, ReticleVisible, DocsVisible, MouseCaptured,
PrimaryDown, SecondaryDown, EditorFocused, ConsoleFocused, DocsFocused,
Playing, Flying, God, AltWalkSpeed, AltFlySpeed,
LoadingScript, Server, Quitting, ResettingVM
LoadingScript, Server, Quitting, ResettingVM, NeedsRestart, Connecting

GlobalStateFlags* = enum
LoadingLevel
Expand Down Expand Up @@ -67,6 +67,7 @@ type
scale_factor*: float
worker_ctx_name*: string
level_name_value*: ZenValue[string]
status_message_value*: ZenValue[string]

Model* = ref object of RootObj
id*: string
Expand Down
11 changes: 11 additions & 0 deletions src/ui/right_panel.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ gdobj RightPanel of MarginContainer:
method ready* =
self.label = self.find_node("MarkdownLabel") as MarkdownLabel

state.status_message_value.changes:
if added:
if ?change.item:
state.push_flags DocsVisible, DocsFocused
self.label.markdown = change.item
self.label.update
else:
state.pop_flags DocsFocused, DocsVisible
self.label.markdown = ""
self.label.update

state.open_sign_value.changes:
if added and change.item != nil:
state.push_flags DocsVisible, DocsFocused
Expand Down

0 comments on commit 60bd519

Please sign in to comment.