Changes
diff --git a/include/game_session.hpp b/include/game_session.hpp
index 3903aa6..062b163 100644
--- a/include/game_session.hpp
+++ b/include/game_session.hpp
@@ -1,6 +1,7 @@
#pragma once
#include <atomic>
+#include <chrono>
#include <cstdint>
#include <filesystem>
#include <mutex>
@@ -64,6 +65,10 @@ public:
/// Return the latest state for the spectator server.
Json snapshot() const;
+ /// Wait for a viewer snapshot newer than the given revision.
+ Json waitForSnapshot(std::uint64_t revision,
+ std::chrono::milliseconds timeout) const;
+
private:
ToolResult sendInput(const Json& arguments, Json response,
const std::string& expected_kind);
diff --git a/src/game_http_server.cpp b/src/game_http_server.cpp
index c28550d..c542296 100644
--- a/src/game_http_server.cpp
+++ b/src/game_http_server.cpp
@@ -4,6 +4,7 @@
#include "game_session.hpp"
#include "mcp_server.hpp"
+#include <chrono>
#include <string>
#include <string_view>
@@ -256,11 +257,20 @@ void GameHttpServer::serveState(const Request& request, Response& response)
response.status = 403;
return;
}
- const Json state = session_.snapshot();
- const std::string etag = "\"" +
+ Json state = session_.snapshot();
+ const std::string requested_etag = request.get_header_value(
+ "If-None-Match");
+ std::string etag = "\"" +
std::to_string(state.value("revision", 0ULL)) + "\"";
+ if(requested_etag == etag)
+ {
+ state = session_.waitForSnapshot(
+ state.value("revision", 0ULL), std::chrono::seconds(15));
+ etag = "\"" +
+ std::to_string(state.value("revision", 0ULL)) + "\"";
+ }
response.set_header("ETag", etag);
- if(request.get_header_value("If-None-Match") == etag)
+ if(requested_etag == etag)
{
response.status = 304;
return;
diff --git a/src/game_session.cpp b/src/game_session.cpp
index 9d5301a..0e95375 100644
--- a/src/game_session.cpp
+++ b/src/game_session.cpp
@@ -536,6 +536,12 @@ Json GameSession::snapshot() const
return observations_.snapshot();
}
+Json GameSession::waitForSnapshot(
+ std::uint64_t revision, std::chrono::milliseconds timeout) const
+{
+ return observations_.waitForRevision(revision, timeout);
+}
+
ToolResult GameSession::sendInput(const Json& arguments, Json response,
const std::string& expected_kind)
{
diff --git a/web/viewer.js b/web/viewer.js
index 7aed8c1..190ac0d 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -114,23 +114,28 @@
}
async function poll() {
+ let retry_delay = 0;
try
{
const headers = etag ? {"If-None-Match": etag} : {};
const response = await fetch("/api/state", {
headers, cache: "no-store",
});
- if(response.status === 304) return;
- if(!response.ok) throw new Error(`HTTP ${response.status}`);
- etag = response.headers.get("ETag") || "";
- show(await response.json());
+ if(response.status !== 304)
+ {
+ if(!response.ok) throw new Error(`HTTP ${response.status}`);
+ etag = response.headers.get("ETag") || "";
+ show(await response.json());
+ }
}
catch(error)
{
lifecycle.textContent = `Disconnected: ${error.message}`;
+ etag = "";
+ retry_delay = 1000;
}
+ setTimeout(poll, retry_delay);
}
poll();
- setInterval(poll, 250);
})();