Godot Version: 4.3 stable
Problem
I am having problems with HttpRequest signals in C#. I have been debugging my code but I started to think it is an engine bug. The code I am going to share is a little bit difficult to follow so I am going to try explaining it. I am not sure it is a good idea to simplify the code as I think the problem is related with object lifecycles.
I know I can easily rewrite my code, I am not looking for a workaround, I just want to find where the problem is, if it is something related with my code I apologize in advance. A C# debugger is recommended.
Well, I wanted to create an InternetChecker
class, it is a standard class (not a Node
) that I can call from a node. The idea is checking if the user has internet by making HTTP requests to some urls. The class has a Check
method that receives a list of urls and a couple of callbacks, success and failure.
The Cehck
method creates N
HttpRequest
nodes being N
the number of urls we passed. Each HttpRequest
is suposed to run in parallel so I subscribe to RequestCompleted
on each one. The thing is: if any of the request succeeds I know that the user has internet so I can unsubscribe from the other HttpRequest
s and invoke the success listener.
This last part is the one failing, godot says that the signal I am trying to unsubsribe does not exist in one context but after that, in other context it works. (You need to read and test the code to see what I am trying to explain)
Actual class:
public class InternetChecker
{
const int DEFAULT_REQUEST_TIMEOUT = 4;
Node m_Parent;
int m_RequestTimeout;
List<HttpRequest> m_HTTPRequests;
public InternetChecker(Node parent, int requestTimeout = DEFAULT_REQUEST_TIMEOUT)
{
m_Parent = parent;
m_RequestTimeout = requestTimeout;
}
public void Check(string[] urls, Action onSuccessListener, Action onFailureListener)
{
m_HTTPRequests = new List<HttpRequest>(urls.Length); // Here I store the references to HttpRequest nodes
int checks = 0;
foreach (string url in urls)
{
// Create HttpRequest nodes
HttpRequest httpRequest = new HttpRequest()
{
Timeout = m_RequestTimeout
};
m_Parent.CallDeferred("add_child", httpRequest); // This is done like that so I can call this from a node _Ready()
m_HTTPRequests.Add(httpRequest);
// Request Completed Listener
void _OnRequestCompleted(long result, long responseCode, string[] headers, byte[] body)
{
// Here we unsuscribe from the signal and remove the http request node from the register
GD.Print($"We have response from: {httpRequest.Name}, Unregistering: {httpRequest.Name}");
httpRequest.RequestCompleted -= _OnRequestCompleted;
m_HTTPRequests.Remove(httpRequest);
// FreeHTTPRequest(httpRequest);
// 0 -> SUCCESS
// https://docs.godotengine.org/en/stable/classes/class_httprequest.html#enumerations
if ( result == (int) HttpRequest.Result.Success)
{
// If one http request succeeds we have internet so we no longer care about other http respones so we want to unsubscribe from them
for (int i = m_HTTPRequests.Count - 1; i >= 0; i--)
{
GD.Print($"Unregistering: {m_HTTPRequests[i].Name}");
m_HTTPRequests[i].RequestCompleted -= _OnRequestCompleted; // THIS FAILS
m_HTTPRequests.RemoveAt(i);
// FreeHTTPRequest(m_HTTPRequests[i]);
}
onSuccessListener.Invoke();
}
else
{
// If the http request fails we just keep waiting until all have finished so if none succeded we don't have internet
checks += 1;
if (checks == urls.Length)
{
onFailureListener.Invoke();
}
}
}
void _OnHTTPRequestReady()
{
httpRequest.Ready -= _OnHTTPRequestReady;
GD.Print($"Connecting {httpRequest.Name}");
httpRequest.RequestCompleted += _OnRequestCompleted;
httpRequest.Request(url);
}
// I need to waint until node is ready as I am creating the node with CallDeferred()
httpRequest.Ready += _OnHTTPRequestReady;
}
}
// References to this are commented so this never gets called to simplify a little bit
void FreeHTTPRequest(HttpRequest httpRequest)
{
m_Parent.RemoveChild(httpRequest);
httpRequest.QueueFree();
}
}
How to use
Create a node and attach this script, make sure you have the InternetChecker
class also
using Godot;
public partial class MyNode : Node
{
public override void _Ready()
{
InternetChecker internetChecker = new InternetChecker( GetTree().Root );
string[] urls = new string[] {"https://www.google.com/", "https://ismyinternetworking.com/"};
internetChecker.Check(urls, OnInternetSuccess, OnInternetFailure);
}
void OnInternetSuccess()
{
GD.Print("Internet good");
}
void OnInternetFailure()
{
GD.Print("Internet bad");
}
}