commit
e484ab331b
7 changed files with 130 additions and 0 deletions
@ -0,0 +1,16 @@ |
|||||||
|
# copySocket |
||||||
|
|
||||||
|
.. is a simple [SnekStudio] module that hosts a WebSocket server to receive hardcoded commands to affect certain things within SnekStudio from the outside. When added, it hosts a server at `ws://127.0.0.1:13981`. |
||||||
|
|
||||||
|
The included `send.gd` script may be used to send commands if another utility is not available, however it takes a small amount of time to start up. The following example makes the `CameraPositions` mod switch to "Camera 1". |
||||||
|
|
||||||
|
```sh |
||||||
|
# --headless - Don't create a window. |
||||||
|
# -q/--quiet - Only print errors. |
||||||
|
# -s <script> - Run a specific script. |
||||||
|
godot --headless -q -s path/to/send.gd -- camera 1 |
||||||
|
``` |
||||||
|
|
||||||
|
If the Godot Editor is not available, the SnekStudio binary can be used in its place. |
||||||
|
|
||||||
|
[SnekStudio]: https://github.com/ExpiredPopsicle/SnekStudio |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
extends Mod_Base |
||||||
|
|
||||||
|
static var NUMBER_REGEX := RegEx.create_from_string("^\\d+$") |
||||||
|
|
||||||
|
var server: TCPServer |
||||||
|
var peers: Dictionary[WebSocketPeer, int] |
||||||
|
|
||||||
|
func _ready() -> void: |
||||||
|
server = TCPServer.new() |
||||||
|
server.listen(13981, "127.0.0.1") |
||||||
|
|
||||||
|
func _exit_tree() -> void: |
||||||
|
server.stop() |
||||||
|
for peer in peers: |
||||||
|
peer.close() |
||||||
|
|
||||||
|
server = null |
||||||
|
peers.clear() |
||||||
|
|
||||||
|
func _process(_delta: float) -> void: |
||||||
|
# Accept new connections. |
||||||
|
while server.is_connection_available(): |
||||||
|
var conn := server.take_connection() |
||||||
|
if not conn: break |
||||||
|
|
||||||
|
var socket := WebSocketPeer.new() |
||||||
|
# socket.supported_protocols = [] |
||||||
|
socket.accept_stream(conn) |
||||||
|
socket.set_no_delay(true) |
||||||
|
|
||||||
|
peers.set(socket, Time.get_ticks_msec()) |
||||||
|
|
||||||
|
var to_remove: Array[WebSocketPeer] |
||||||
|
for peer in peers: |
||||||
|
peer.poll() |
||||||
|
|
||||||
|
# Receive messages. |
||||||
|
while peer.get_available_packet_count(): |
||||||
|
var packet := peer.get_packet() |
||||||
|
if not peer.was_string_packet(): continue # ignore binary packets |
||||||
|
var message := packet.get_string_from_utf8() |
||||||
|
_on_websocket_message(peer, message) |
||||||
|
|
||||||
|
# Remove the peer if it's disconnected. |
||||||
|
if peer.get_ready_state() != WebSocketPeer.STATE_OPEN: |
||||||
|
to_remove.append(peer) |
||||||
|
|
||||||
|
# Remove the peer if it's been around for over 0.5 seconds. |
||||||
|
if Time.get_ticks_msec() - peers[peer] > 500: |
||||||
|
to_remove.append(peer) |
||||||
|
|
||||||
|
# Clear out disconnected peers. |
||||||
|
for peer in to_remove: |
||||||
|
peers.erase(peer) |
||||||
|
|
||||||
|
func _on_websocket_message(peer: WebSocketPeer, message: String) -> void: |
||||||
|
if message.begins_with("camera "): |
||||||
|
var index := message.substr("camera ".length()) |
||||||
|
if NUMBER_REGEX.search(index) and int(index): |
||||||
|
var camera_mod := get_node("../CameraPositions") |
||||||
|
if camera_mod: |
||||||
|
camera_mod._load_camera_position("Camera " + index); |
||||||
|
else: |
||||||
|
print_log("mod with index 'CameraPositions' not found") |
||||||
|
else: |
||||||
|
print_log("invalid camera index: " + index) |
||||||
|
else: |
||||||
|
print_log("unknown command: " + message) |
||||||
|
|
||||||
|
# Process only one message, then disconnect. |
||||||
|
peer.close() |
||||||
@ -0,0 +1 @@ |
|||||||
|
uid://dme7c7r7w85pq |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
[gd_scene load_steps=2 format=3 uid="uid://0lluarde7l2e"] |
||||||
|
|
||||||
|
[ext_resource type="Script" uid="uid://dme7c7r7w85pq" path="res://Mods/copySocket/copySocket.gd" id="1_yclwn"] |
||||||
|
|
||||||
|
[node name="copySocket" type="Node3D"] |
||||||
|
script = ExtResource("1_yclwn") |
||||||
@ -0,0 +1 @@ |
|||||||
|
Hosts a WebSocket server to receive commands. |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
extends SceneTree |
||||||
|
|
||||||
|
func _init() -> void: |
||||||
|
var socket = WebSocketPeer.new() |
||||||
|
# socket.supported_protocols = [] |
||||||
|
socket.connect_to_url("ws://127.0.0.1:13981") |
||||||
|
socket.set_no_delay(true) |
||||||
|
|
||||||
|
# Wait up to 1 second for the socket to connect. |
||||||
|
_wait_until_not(socket, WebSocketPeer.STATE_CONNECTING) |
||||||
|
if socket.get_ready_state() != WebSocketPeer.STATE_OPEN: |
||||||
|
push_error("could not connect"); quit(1); return |
||||||
|
|
||||||
|
var message := "" |
||||||
|
for arg in OS.get_cmdline_user_args(): |
||||||
|
if not message.is_empty(): message += " " |
||||||
|
# TODO: Escape (and handle) quote characters? |
||||||
|
# Surround empty string and those that contain spaces with quotes. |
||||||
|
if arg.is_empty() or arg.contains(" "): arg = "\"" + arg + "\"" |
||||||
|
message += arg |
||||||
|
|
||||||
|
socket.send_text(message) |
||||||
|
|
||||||
|
# Wait up to 1 second for the socket to close. |
||||||
|
# This ensures the message makes it through. |
||||||
|
_wait_until_not(socket, WebSocketPeer.STATE_OPEN) |
||||||
|
quit() |
||||||
|
|
||||||
|
## Waits up to the specified amount for the socket to NOT be in the given state any longer. |
||||||
|
func _wait_until_not(socket: WebSocketPeer, state: int, msec := 1000) -> void: |
||||||
|
var start := Time.get_ticks_msec() |
||||||
|
while socket.get_ready_state() == state: |
||||||
|
if Time.get_ticks_msec() - start > msec: return |
||||||
|
socket.poll() # Update websocket state. |
||||||
@ -0,0 +1 @@ |
|||||||
|
uid://bdcatr7rg0gld |
||||||
Loading…
Reference in new issue