1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use amethyst::ecs::{Entities, Join, System};
use crossbeam_channel::Receiver;
use crate::serializable_entity::SerializableEntity;
use std::cmp::min;
use std::fmt::Write;
use std::net::UdpSocket;
use std::time::{Duration, Instant};
use crate::types::SerializedData;
const MAX_PACKET_SIZE: usize = 32 * 1024;
/// The system in charge of sending updated state data to the editor process.
pub struct EditorSenderSystem {
receiver: Receiver<SerializedData>,
socket: UdpSocket,
send_interval: Duration,
next_send: Instant,
scratch_string: String,
}
impl EditorSenderSystem {
pub fn from_channel(
receiver: Receiver<SerializedData>,
send_interval: Duration,
socket: UdpSocket,
) -> Self {
// Create the socket used for communicating with the editor.
//
// NOTE: We set the socket to nonblocking so that we don't block if there are no incoming
// messages to read. We `expect` on the call to `set_nonblocking` because the game will
// hang if the socket is still set to block when the game runs.
let scratch_string = String::with_capacity(MAX_PACKET_SIZE);
EditorSenderSystem {
receiver,
socket,
send_interval,
next_send: Instant::now() + send_interval,
scratch_string,
}
}
}
impl<'a> System<'a> for EditorSenderSystem {
type SystemData = Entities<'a>;
fn run(&mut self, entities: Self::SystemData) {
// Determine if we should send full state data this frame.
let now = Instant::now();
let send_this_frame = now >= self.next_send;
// Calculate when we should next send full state data.
//
// NOTE: We do `next_send += send_interval` instead of `next_send = now + send_interval`
// to ensure that state updates happen at a consistent cadence even if there are slight
// timing variations in when individual frames are sent.
//
// NOTE: We repeatedly add `send_interval` to `next_send` to ensure that the next send
// time is after `now`. This is to avoid running into a death spiral if a frame spike
// causes frame time to be so long that the next send time would still be in the past.
while self.next_send < now {
self.next_send += self.send_interval;
}
let mut components = Vec::new();
let mut resources = Vec::new();
let mut messages = Vec::new();
while let Ok(serialized) = self.receiver.try_recv() {
match serialized {
SerializedData::Component(c) => components.push(c),
SerializedData::Resource(r) => resources.push(r),
SerializedData::Message(m) => messages.push(m),
}
}
let mut entity_data = Vec::<SerializableEntity>::new();
for (entity,) in (&*entities,).join() {
entity_data.push(entity.into());
}
let entity_string =
serde_json::to_string(&entity_data).expect("Failed to serialize entities");
// Create the message and serialize it to JSON. If we don't need to send the full state
// data this frame, we discard entities, components, and resources, and only send the
// messages (e.g. log output) from the current frame.
if send_this_frame {
write!(
self.scratch_string,
r#"{{
"type": "message",
"data": {{
"entities": {},
"components": [{}],
"resources": [{}],
"messages": [{}]
}}
}}"#,
entity_string,
// Insert a comma between components so that it's valid JSON.
components.join(","),
resources.join(","),
messages.join(","),
)
.expect("Failed to write JSON string");
} else {
write!(
self.scratch_string,
r#"{{
"type": "message",
"data": {{
"messages": [{}]
}}
}}"#,
// Insert a comma between components so that it's valid JSON.
messages.join(","),
)
.expect("Failed to write JSON string");
}
// NOTE: We need to append a page feed character after each message since that's
// what node-ipc expects to delimit messages.
self.scratch_string.push_str("\u{C}");
// Send the message, breaking it up into multiple packets if the message is too large.
let editor_address: std::net::SocketAddr = ([127, 0, 0, 1], 8000).into();
let mut bytes_sent = 0;
while bytes_sent < self.scratch_string.len() {
let bytes_to_send = min(self.scratch_string.len() - bytes_sent, MAX_PACKET_SIZE);
let end_offset = bytes_sent + bytes_to_send;
// Send the JSON message.
let bytes = self.scratch_string[bytes_sent..end_offset].as_bytes();
self.socket
.send_to(bytes, editor_address)
.expect("Failed to send message");
bytes_sent += bytes_to_send;
}
self.scratch_string.clear();
}
}