Skip to content

Commit

Permalink
Implemented heartbeat functionality. Requests are now sent to the ser…
Browse files Browse the repository at this point in the history
…ver and the response is held in a queue on the server. /heartbeat will pull the current log output
  • Loading branch information
derekShaheen committed Apr 7, 2024
1 parent 1a51f8c commit b7c0294
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 49 deletions.
2 changes: 1 addition & 1 deletion Commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void DiscoverHandlersAndVariables()
{
string path = Utilities.EnsureUniqueKey(CommandHandlers.Keys, commandAttribute.Path);
var parameters = method.GetParameters()
.Where(param => param.ParameterType != typeof(HttpListenerResponse))
//.Where(param => param.ParameterType != typeof(HttpListenerResponse))
.Select(param => param.Name)
.ToArray();
// Initialize AutoCompleteOptions as an empty dictionary
Expand Down
2 changes: 1 addition & 1 deletion Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using MelonLoader;

[assembly: MelonInfo(typeof(Wicker.WickerServer), "WickerREST", "0.94.0", "Skrip")]
[assembly: MelonInfo(typeof(Wicker.WickerServer), "WickerREST", "0.95.0", "Skrip")]
[assembly: MelonGame("Crate Entertainment", "Farthest Frontier")]
56 changes: 36 additions & 20 deletions WickerNetwork.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class WickerNetwork
private const string FAVICON_URL = @"https://raw.githubusercontent.com/derekShaheen/WickerREST/main/web/resources/favicon.ico";
private const string DWYL_URL = @"https://hits.dwyl.com/derekShaheen/WickerREST.svg";

private List<string> _logEntries = new List<string>();
private object _logLock = new object(); // For thread-safety

// Instance of this class
private static WickerNetwork? instance;

Expand Down Expand Up @@ -90,10 +93,10 @@ private async void HandleRequests(CancellationToken cancellationToken)

// Parse query parameters
var query = HttpUtility.ParseQueryString(request.Url.Query);
parameters[0] = response; // Pass the response object
if (parameterInfos.Length > 1)
//parameters[0] = response; // Pass the response object
if (parameterInfos.Length > 0)
{
for (int i = 1; i < parameterInfos.Length; i++)
for (int i = 0; i < parameterInfos.Length; i++)
{
var paramInfo = parameterInfos[i];
var paramValue = query[paramInfo.Name];
Expand Down Expand Up @@ -147,26 +150,30 @@ private async void HandleRequests(CancellationToken cancellationToken)
}
}


private void ServeHeartbeat(HttpListenerResponse response)
{
if (Commands.Instance.GameVariableMethods != null && Commands.Instance.GameVariableMethods.Count > 0)
{
var logResults = _logEntries;

var variableValues = Commands.Instance.GameVariableMethods.Select(kvp => new
{
VariableName = kvp.Key,
Value = kvp.Value?.Invoke()?.ToString()
}).ToDictionary(kvp => kvp.VariableName, kvp => kvp.Value);
var gameVariables = Commands.Instance.GameVariableMethods?.Select(kvp => new
{
VariableName = kvp.Key,
Value = kvp.Value?.Invoke()?.ToString()
}).ToDictionary(kvp => kvp.VariableName, kvp => kvp.Value) ?? new Dictionary<string, string>();

var json = JsonConvert.SerializeObject(variableValues);
SendResponse(response, json, statusCode: 200, contentType: "application/json");
}
else
// Combine game variables and log results in one object
var heartbeatResponse = new
{
SendResponse(response, "No game variables found.", statusCode: 200);
}
GameVariables = gameVariables,
LogResults = logResults
};

var json = JsonConvert.SerializeObject(heartbeatResponse);
SendResponse(response, json, statusCode: 200, contentType: "application/json");
logResults.Clear();
}


private void ServeCommandHandlers(HttpListenerResponse response)
{
if (Commands.Instance.CommandHandlers != null && Commands.Instance.CommandHandlers.Count > 0)
Expand Down Expand Up @@ -339,12 +346,21 @@ public void SendResponse(HttpListenerResponse response, string message, bool clo
}
}

public void LogResponse(HttpListenerResponse response, string message)
public void LogResponse(string message)
{
// Replace newline characters with HTML line breaks to preserve formatting in the web page
string formattedMessage = message.Replace(Environment.NewLine, "<br>");
SendResponse(response, formattedMessage, statusCode: 200, contentType: "text/html");
lock (_logLock)
{
string formattedMessage = message.Replace(Environment.NewLine, "<br>");
_logEntries.Add(formattedMessage);
}
}

//public void LogResponse(HttpListenerResponse response, string message)
//{
// // Replace newline characters with HTML line breaks to preserve formatting in the web page
// string formattedMessage = message.Replace(Environment.NewLine, "<br>");
// SendResponse(response, formattedMessage, statusCode: 200, contentType: "text/html");
//}

}
}
4 changes: 2 additions & 2 deletions WickerREST.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyVersion>0.94.0.0</AssemblyVersion>
<FileVersion>0.94.0.0</FileVersion>
<AssemblyVersion>0.95.0.0</AssemblyVersion>
<FileVersion>0.95.0.0</FileVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
2 changes: 1 addition & 1 deletion WickerServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static class BuildInfo
public const string Name = "WickerREST";
public const string Description = "WickerREST";
public const string Author = "Skrip";
public const string Version = "0.94.0";
public const string Version = "0.95.0";
public const string DownloadLink = "";
}

Expand Down
40 changes: 16 additions & 24 deletions web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>
function executeCommand(command) {
fetch(command)
.then(response => response.text())
.then(data => displayResponse(command, command, data));
.then(data => displayResponse(command.substring(1), command, data));
}

function showModalForCommand(command, parameters) {
Expand Down Expand Up @@ -275,7 +275,7 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>

fetch(`${commandStr}`)
.then(response => response.text())
.then(data => displayResponse(command, commandStr, data));
.then(data => displayResponse(command.substring(1), commandStr, data));
}

function submitModal(command) {
Expand Down Expand Up @@ -306,7 +306,7 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>
// Create a clickable link element
const commandLink = document.createElement('a');
commandLink.href = commandStr; // Using '#' for now; this will be handled by JavaScript
commandLink.innerHTML = `[${timestamp}] ${command.substring(1)}${data || 'Command executed.'}`;
commandLink.innerHTML = `[${timestamp}] ${command}${data || 'Command sent!'}`;
commandLink.classList.add('command-link');
commandLink.style.color = 'inherit'; // Use the same color as the parent element
commandLink.style.textDecoration = 'none'; // Remove underline
Expand Down Expand Up @@ -335,7 +335,7 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>
// Re-fetch the command with its parameters
fetch(commandWithParameters)
.then(response => response.text())
.then(data => displayResponse(command, commandWithParameters, data));
.then(data => displayResponse(command.substring(1), commandWithParameters, data));
}

let errorLogged = false;
Expand All @@ -359,6 +359,12 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>
return response.json();
})
.then(data => {
// Handle log results
const logResults = data.LogResults;
logResults.forEach(log => displayResponse('[Server]', '/heartbeat', log));

// Handle game variables
const gameVariables = data.GameVariables;
const container = document.getElementById('gameVariables');
const flashEnabled = document.getElementById('flashCheckbox').checked;
const numberOfColumns = parseInt(document.getElementById('columnControlInput').value) || 1; // Default to 1 column
Expand All @@ -369,7 +375,7 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>
row.className = 'row';

// Create columns
Object.entries(data).sort((a, b) => a[0].localeCompare(b[0]))
Object.entries(gameVariables).sort((a, b) => a[0].localeCompare(b[0]))
.forEach(([variableName, value], index) => {
const col = document.createElement('div');
col.className = `col-${12 / numberOfColumns}`; // Bootstrap class for column sizing
Expand Down Expand Up @@ -401,24 +407,7 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>
}
});

//Object.entries(data).forEach(([variableName, value]) => {
// // Check if the variable's value has changed
// if (prevGameVariables[variableName] !== value) {
// const variableElement = document.querySelector(`[data-variable="${variableName}"]`);
// console.log("2" + variableName);
// console.log("3" + variableElement);
// if (variableElement && flashEnabled) {
// variableElement.classList.add('flash-update');

// // Remove the class after animation ends
// setTimeout(() => {
// variableElement.classList.remove('flash-update');
// }, 1000);
// }
// }
//});

prevGameVariables = { ...data };
prevGameVariables = { ...gameVariables };

// Add any remaining columns not appended in the forEach loop
if (row.hasChildNodes()) {
Expand Down Expand Up @@ -448,12 +437,15 @@ <h5 class="modal-title" id="modalLabel">Command Request</h5>
}
// Implement a retry logic after a delay
setTimeout(fetchHeartbeat, 5000); // Retry after 5 seconds
})
.finally(() => {
setTimeout(fetchHeartbeat, 1000);
});
}

// Poll game variables
fetchHeartbeat();
setInterval(fetchHeartbeat, 2000);
//setInterval(fetchHeartbeat, 2000);

function updateConnectionStatus(isConnected) {
const statusElement = document.getElementById('connectionStatus');
Expand Down

0 comments on commit b7c0294

Please sign in to comment.