Files
jak-project/common/repl/nrepl/ReplServer.cpp
T
Tyler Wilding d1ece445d4 Dependency graph work - Part 1 - Preliminary work (#3505)
Relates to #1353 

This adds no new functionality or overhead to the compiler, yet. This is
the preliminary work that has:
- added code to the compiler in several spots to flag when something is
used without being properly required/imported/whatever (disabled by
default)
- that was used to generate project wide file dependencies (some
circulars were manually fixed)
- then that graph underwent a transitive reduction and the result was
written to all `jak1` source files.

The next step will be making this actually produce and use a dependency
graph. Some of the reasons why I'm working on this:
- eliminates more `game.gp` boilerplate. This includes the `.gd` files
to some extent (`*-ag` files and `tpage` files will still need to be
handled) this is the point of the new `bundles` form. This should make
it even easier to add a new file into the source tree.
- a build order that is actually informed from something real and
compiler warnings that tell you when you are using something that won't
be available at build time.
- narrows the search space for doing LSP actions -- like searching for
references. Since it would be way too much work to store in the compiler
every location where every symbol/function/etc is used, I have to do
ad-hoc searches. By having a dependency graph i can significantly reduce
that search space.
- opens the doors for common shared code with a legitimate pattern.
Right now jak 2 shares code from the jak 1 folder. This is basically a
hack -- but by having an explicit require syntax, it would be possible
to reference arbitrary file paths, such as a `common` folder.

Some stats:
- Jak 1 has about 2500 edges between files, including transitives
- With transitives reduced at the source code level, each file seems to
have a modest amount of explicit requirements.

Known issues:
- Tracking the location for where `defmacro`s and virtual state
definitions were defined (and therefore the file) is still problematic.
Because those forms are in a macro environment, the reader does not
track them. I'm wondering if a workaround could be to search the
reader's text_db by not just the `goos::Object` but by the text
position. But for the purposes of finishing this work, I just statically
analyzed and searched the code with throwaway python code.
2024-05-12 12:37:59 -04:00

155 lines
4.9 KiB
C++

// clang-format off
#include "ReplServer.h"
#include "common/cross_sockets/XSocket.h"
#include "common/versions/versions.h"
#include "fmt/core.h"
#ifdef _WIN32
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#endif
#include "common/log/log.h"
// clang-format on
// TODO - The server also needs to eventually return the result of the evaluation
ReplServer::~ReplServer() {
// Close all our client sockets!
for (const int& sock : client_sockets) {
close_socket(sock);
}
}
void ReplServer::post_init() {
// Add the listening socket to our set of sockets
lg::info("[nREPL:{}:{}] awaiting connections", tcp_port, listening_socket);
}
void ReplServer::ping_response(int socket) {
std::string ping = fmt::format("Connected to OpenGOAL v{}.{} nREPL!",
versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR);
auto resp = write_to_socket(socket, ping.c_str(), ping.size());
if (resp == -1) {
lg::warn("[nREPL:{}] Client Disconnected: {}", tcp_port, address_to_string(addr),
ntohs(addr.sin_port), socket);
close_socket(socket);
client_sockets.erase(socket);
}
}
std::optional<std::string> ReplServer::get_msg() {
// Clear the sockets we are listening on
FD_ZERO(&read_sockets);
// Add the server's main listening socket (where we accept clients from)
FD_SET(listening_socket, &read_sockets);
int max_sd = listening_socket;
for (const int& sock : client_sockets) {
if (sock > max_sd) {
max_sd = sock;
}
if (sock > 0) {
FD_SET(sock, &read_sockets);
}
}
// Wait for activity on _something_, with a timeout so we don't get stuck here on exit.
struct timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 100000;
auto activity = select(max_sd + 1, &read_sockets, NULL, NULL, &timeout);
if (activity < 0) { // TODO - || error!
return std::nullopt;
}
// If something happened on the master socket - it's a new connection
if (FD_ISSET(listening_socket, &read_sockets)) {
socklen_t addr_len = sizeof(addr);
auto new_socket = accept_socket(listening_socket, (sockaddr*)&addr, &addr_len);
if (new_socket < 0) {
// TODO - handle error
} else {
lg::info("[nREPL:{}]: New socket connection: {}:{}:{}", tcp_port, address_to_string(addr),
ntohs(addr.sin_port), new_socket);
// Say hello
ping_response(new_socket);
// Track the new socket
if ((int)client_sockets.size() < max_clients) {
client_sockets.insert(new_socket);
} else {
// TODO - Respond with NO
}
}
}
// otherwise (and no matter what) check all the clients to see if they have sent us anything
// else its some IO operation on some other socket
//
// RACE - the first client wins
// TODO - there are ways to do this with iterators but, couldn't figure it out!
std::vector<int> sockets_to_scan(client_sockets.begin(), client_sockets.end());
for (const int& sock : sockets_to_scan) {
if (FD_ISSET(sock, &read_sockets)) {
// Attempt to read a header
// TODO - should this be in a loop?
auto req_bytes = read_from_socket(sock, header_buffer.data(), header_buffer.size());
if (req_bytes == 0) {
// Socket disconnected
// TODO - add a queue of messages in the REPL::Wrapper so we can print _BEFORE_ the prompt
// is output
lg::warn("[nREPL:{}] Client Disconnected: {}", tcp_port, address_to_string(addr),
ntohs(addr.sin_port), sock);
// Cleanup the socket and remove it from our set
close_socket(sock);
client_sockets.erase(sock);
} else {
// Otherwise, process the message
auto* header = (ReplServerHeader*)(header_buffer.data());
// get the body of the message
int expected_size = header->length;
int got = 0;
int tries = 0;
while (got < expected_size) {
tries++;
if (tries > 100) {
break;
}
if (got + expected_size > (int)buffer.size()) {
lg::error(
"[nREPL:{}]: Bad message, aborting the read. Got :{}, Expected: {}, Buffer "
"Size: {}",
tcp_port, got, expected_size, buffer.size());
return std::nullopt;
}
auto x = read_from_socket(sock, buffer.data() + got, expected_size - got);
if (want_exit_callback()) {
return std::nullopt;
}
got += x > 0 ? x : 0;
}
switch (header->type) {
case ReplServerMessageType::PING:
ping_response(sock);
return std::nullopt;
case ReplServerMessageType::EVAL:
std::string msg(buffer.data(), header->length);
lg::debug("[nREPL:{}] Received Message: {}", tcp_port, msg);
return std::make_optional(msg);
}
}
}
}
return std::nullopt;
}