Merge pull request #46728 from Faless/js/4.x_fetch_world
[HTML5] Replace XMLHttpRequest(s) with Fetch.
This commit is contained in:
commit
a4b5edf468
13 changed files with 535 additions and 402 deletions
|
@ -17,7 +17,7 @@ sys_env.AddJSLibraries(
|
|||
[
|
||||
"js/libs/library_godot_audio.js",
|
||||
"js/libs/library_godot_display.js",
|
||||
"js/libs/library_godot_http_request.js",
|
||||
"js/libs/library_godot_fetch.js",
|
||||
"js/libs/library_godot_os.js",
|
||||
"js/libs/library_godot_runtime.js",
|
||||
]
|
||||
|
|
|
@ -242,7 +242,7 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform {
|
|||
return name;
|
||||
}
|
||||
|
||||
void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects);
|
||||
void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes);
|
||||
|
||||
static void _server_thread_poll(void *data);
|
||||
|
||||
|
@ -281,7 +281,7 @@ public:
|
|||
~EditorExportPlatformJavaScript();
|
||||
};
|
||||
|
||||
void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects) {
|
||||
void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug, int p_flags, const Vector<SharedObject> p_shared_objects, const Dictionary &p_file_sizes) {
|
||||
String str_template = String::utf8(reinterpret_cast<const char *>(p_html.ptr()), p_html.size());
|
||||
String str_export;
|
||||
Vector<String> lines = str_template.split("\n");
|
||||
|
@ -300,6 +300,7 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re
|
|||
config["gdnativeLibs"] = libs;
|
||||
config["executable"] = p_name;
|
||||
config["args"] = args;
|
||||
config["fileSizes"] = p_file_sizes;
|
||||
const String str_config = JSON::print(config);
|
||||
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
|
@ -472,6 +473,8 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
|||
return ERR_FILE_CORRUPT;
|
||||
}
|
||||
|
||||
Vector<uint8_t> html;
|
||||
Dictionary file_sizes;
|
||||
do {
|
||||
//get filename
|
||||
unz_file_info info;
|
||||
|
@ -480,6 +483,16 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
|||
|
||||
String file = fname;
|
||||
|
||||
// HTML is handled later
|
||||
if (file == "godot.html") {
|
||||
if (custom_html.is_empty()) {
|
||||
html.resize(info.uncompressed_size);
|
||||
unzOpenCurrentFile(pkg);
|
||||
unzReadCurrentFile(pkg, html.ptrw(), html.size());
|
||||
unzCloseCurrentFile(pkg);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Vector<uint8_t> data;
|
||||
data.resize(info.uncompressed_size);
|
||||
|
||||
|
@ -490,14 +503,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
|||
|
||||
//write
|
||||
|
||||
if (file == "godot.html") {
|
||||
if (!custom_html.is_empty()) {
|
||||
continue;
|
||||
}
|
||||
_fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
|
||||
file = p_path.get_file();
|
||||
|
||||
} else if (file == "godot.js") {
|
||||
if (file == "godot.js") {
|
||||
file = p_path.get_file().get_basename() + ".js";
|
||||
|
||||
} else if (file == "godot.worker.js") {
|
||||
|
@ -511,6 +517,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
|||
|
||||
} else if (file == "godot.wasm") {
|
||||
file = p_path.get_file().get_basename() + ".wasm";
|
||||
file_sizes[file.get_file()] = (uint64_t)info.uncompressed_size;
|
||||
}
|
||||
|
||||
String dst = p_path.get_base_dir().plus_file(file);
|
||||
|
@ -532,19 +539,26 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese
|
|||
EditorNode::get_singleton()->show_warning(TTR("Could not read custom HTML shell:") + "\n" + custom_html);
|
||||
return ERR_FILE_CANT_READ;
|
||||
}
|
||||
Vector<uint8_t> buf;
|
||||
buf.resize(f->get_len());
|
||||
f->get_buffer(buf.ptrw(), buf.size());
|
||||
html.resize(f->get_len());
|
||||
f->get_buffer(html.ptrw(), html.size());
|
||||
memdelete(f);
|
||||
_fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects);
|
||||
|
||||
}
|
||||
{
|
||||
FileAccess *f = FileAccess::open(pck_path, FileAccess::READ);
|
||||
if (f) {
|
||||
file_sizes[pck_path.get_file()] = (uint64_t)f->get_len();
|
||||
memdelete(f);
|
||||
f = NULL;
|
||||
}
|
||||
_fix_html(html, p_preset, p_path.get_file().get_basename(), p_debug, p_flags, shared_objects, file_sizes);
|
||||
f = FileAccess::open(p_path, FileAccess::WRITE);
|
||||
if (!f) {
|
||||
EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path);
|
||||
return ERR_FILE_CANT_WRITE;
|
||||
}
|
||||
f->store_buffer(buf.ptr(), buf.size());
|
||||
f->store_buffer(html.ptr(), html.size());
|
||||
memdelete(f);
|
||||
html.resize(0);
|
||||
}
|
||||
|
||||
Ref<Image> splash;
|
||||
|
|
|
@ -30,24 +30,21 @@
|
|||
|
||||
// HTTPClient's additional private members in the javascript platform
|
||||
|
||||
Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers);
|
||||
Error make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len);
|
||||
static void _parse_headers(int p_len, const char **p_headers, void *p_ref);
|
||||
|
||||
int xhr_id;
|
||||
int js_id = 0;
|
||||
int read_limit = 4096;
|
||||
int response_read_offset = 0;
|
||||
Status status = STATUS_DISCONNECTED;
|
||||
|
||||
String host;
|
||||
int port = -1;
|
||||
bool use_tls = false;
|
||||
String username;
|
||||
String password;
|
||||
|
||||
int polled_response_code = 0;
|
||||
String polled_response_header;
|
||||
PackedByteArray polled_response;
|
||||
Vector<String> response_headers;
|
||||
Vector<uint8_t> response_buffer;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
bool has_polled = false;
|
||||
uint64_t last_polling_frame = 0;
|
||||
#endif
|
||||
|
|
|
@ -30,7 +30,38 @@
|
|||
|
||||
#include "core/io/http_client.h"
|
||||
|
||||
#include "http_request.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "stddef.h"
|
||||
|
||||
typedef enum {
|
||||
GODOT_JS_FETCH_STATE_REQUESTING = 0,
|
||||
GODOT_JS_FETCH_STATE_BODY = 1,
|
||||
GODOT_JS_FETCH_STATE_DONE = 2,
|
||||
GODOT_JS_FETCH_STATE_ERROR = -1,
|
||||
} godot_js_fetch_state_t;
|
||||
|
||||
extern int godot_js_fetch_create(const char *p_method, const char *p_url, const char **p_headers, int p_headers_len, const uint8_t *p_body, int p_body_len);
|
||||
extern int godot_js_fetch_read_headers(int p_id, void (*parse_callback)(int p_size, const char **p_headers, void *p_ref), void *p_ref);
|
||||
extern int godot_js_fetch_read_chunk(int p_id, uint8_t *p_buf, int p_buf_size);
|
||||
extern void godot_js_fetch_free(int p_id);
|
||||
extern godot_js_fetch_state_t godot_js_fetch_state_get(int p_id);
|
||||
extern int godot_js_fetch_body_length_get(int p_id);
|
||||
extern int godot_js_fetch_http_status_get(int p_id);
|
||||
extern int godot_js_fetch_is_chunked(int p_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
void HTTPClient::_parse_headers(int p_len, const char **p_headers, void *p_ref) {
|
||||
HTTPClient *client = static_cast<HTTPClient *>(p_ref);
|
||||
for (int i = 0; i < p_len; i++) {
|
||||
client->response_headers.push_back(String::utf8(p_headers[i]));
|
||||
}
|
||||
}
|
||||
|
||||
Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) {
|
||||
close();
|
||||
|
@ -74,7 +105,7 @@ Ref<StreamPeer> HTTPClient::get_connection() const {
|
|||
ERR_FAIL_V_MSG(REF(), "Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform.");
|
||||
}
|
||||
|
||||
Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers) {
|
||||
Error HTTPClient::make_request(Method p_method, const String &p_url, const Vector<String> &p_headers, const uint8_t *p_body, int p_body_len) {
|
||||
ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER);
|
||||
ERR_FAIL_COND_V_MSG(p_method == METHOD_TRACE || p_method == METHOD_CONNECT, ERR_UNAVAILABLE, "HTTP methods TRACE and CONNECT are not supported for the HTML5 platform.");
|
||||
ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER);
|
||||
|
@ -83,46 +114,33 @@ Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Ve
|
|||
ERR_FAIL_COND_V(!p_url.begins_with("/"), ERR_INVALID_PARAMETER);
|
||||
|
||||
String url = (use_tls ? "https://" : "http://") + host + ":" + itos(port) + p_url;
|
||||
godot_xhr_reset(xhr_id);
|
||||
godot_xhr_open(xhr_id, _methods[p_method], url.utf8().get_data(),
|
||||
username.is_empty() ? nullptr : username.utf8().get_data(),
|
||||
password.is_empty() ? nullptr : password.utf8().get_data());
|
||||
|
||||
Vector<CharString> keeper;
|
||||
Vector<const char *> c_strings;
|
||||
for (int i = 0; i < p_headers.size(); i++) {
|
||||
int header_separator = p_headers[i].find(": ");
|
||||
ERR_FAIL_COND_V(header_separator < 0, ERR_INVALID_PARAMETER);
|
||||
godot_xhr_set_request_header(xhr_id,
|
||||
p_headers[i].left(header_separator).utf8().get_data(),
|
||||
p_headers[i].right(header_separator + 2).utf8().get_data());
|
||||
keeper.push_back(p_headers[i].utf8());
|
||||
c_strings.push_back(keeper[i].get_data());
|
||||
}
|
||||
response_read_offset = 0;
|
||||
if (js_id) {
|
||||
godot_js_fetch_free(js_id);
|
||||
}
|
||||
js_id = godot_js_fetch_create(_methods[p_method], url.utf8().get_data(), c_strings.ptrw(), c_strings.size(), p_body, p_body_len);
|
||||
status = STATUS_REQUESTING;
|
||||
return OK;
|
||||
}
|
||||
|
||||
Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const Vector<uint8_t> &p_body) {
|
||||
Error err = prepare_request(p_method, p_url, p_headers);
|
||||
if (err != OK)
|
||||
return err;
|
||||
if (p_body.is_empty()) {
|
||||
godot_xhr_send(xhr_id, nullptr, 0);
|
||||
} else {
|
||||
godot_xhr_send(xhr_id, p_body.ptr(), p_body.size());
|
||||
return make_request(p_method, p_url, p_headers, nullptr, 0);
|
||||
}
|
||||
return OK;
|
||||
return make_request(p_method, p_url, p_headers, p_body.ptr(), p_body.size());
|
||||
}
|
||||
|
||||
Error HTTPClient::request(Method p_method, const String &p_url, const Vector<String> &p_headers, const String &p_body) {
|
||||
Error err = prepare_request(p_method, p_url, p_headers);
|
||||
if (err != OK)
|
||||
return err;
|
||||
if (p_body.is_empty()) {
|
||||
godot_xhr_send(xhr_id, nullptr, 0);
|
||||
} else {
|
||||
const CharString cs = p_body.utf8();
|
||||
godot_xhr_send(xhr_id, cs.get_data(), cs.length());
|
||||
return make_request(p_method, p_url, p_headers, nullptr, 0);
|
||||
}
|
||||
return OK;
|
||||
const CharString cs = p_body.utf8();
|
||||
return make_request(p_method, p_url, p_headers, (const uint8_t *)cs.get_data(), cs.size() - 1);
|
||||
}
|
||||
|
||||
void HTTPClient::close() {
|
||||
|
@ -130,10 +148,13 @@ void HTTPClient::close() {
|
|||
port = -1;
|
||||
use_tls = false;
|
||||
status = STATUS_DISCONNECTED;
|
||||
polled_response.resize(0);
|
||||
polled_response_code = 0;
|
||||
polled_response_header = String();
|
||||
godot_xhr_reset(xhr_id);
|
||||
response_headers.resize(0);
|
||||
response_buffer.resize(0);
|
||||
if (js_id) {
|
||||
godot_js_fetch_free(js_id);
|
||||
js_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
HTTPClient::Status HTTPClient::get_status() const {
|
||||
|
@ -141,12 +162,11 @@ HTTPClient::Status HTTPClient::get_status() const {
|
|||
}
|
||||
|
||||
bool HTTPClient::has_response() const {
|
||||
return !polled_response_header.is_empty();
|
||||
return response_headers.size() > 0;
|
||||
}
|
||||
|
||||
bool HTTPClient::is_response_chunked() const {
|
||||
// TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream
|
||||
return false;
|
||||
return godot_js_fetch_is_chunked(js_id);
|
||||
}
|
||||
|
||||
int HTTPClient::get_response_code() const {
|
||||
|
@ -154,36 +174,42 @@ int HTTPClient::get_response_code() const {
|
|||
}
|
||||
|
||||
Error HTTPClient::get_response_headers(List<String> *r_response) {
|
||||
if (polled_response_header.is_empty())
|
||||
if (!response_headers.size()) {
|
||||
return ERR_INVALID_PARAMETER;
|
||||
|
||||
Vector<String> header_lines = polled_response_header.split("\r\n", false);
|
||||
for (int i = 0; i < header_lines.size(); ++i) {
|
||||
r_response->push_back(header_lines[i]);
|
||||
}
|
||||
polled_response_header = String();
|
||||
for (int i = 0; i < response_headers.size(); i++) {
|
||||
r_response->push_back(response_headers[i]);
|
||||
}
|
||||
response_headers.clear();
|
||||
return OK;
|
||||
}
|
||||
|
||||
int HTTPClient::get_response_body_length() const {
|
||||
return polled_response.size();
|
||||
return godot_js_fetch_body_length_get(js_id);
|
||||
}
|
||||
|
||||
PackedByteArray HTTPClient::read_response_body_chunk() {
|
||||
ERR_FAIL_COND_V(status != STATUS_BODY, PackedByteArray());
|
||||
|
||||
int to_read = MIN(read_limit, polled_response.size() - response_read_offset);
|
||||
PackedByteArray chunk;
|
||||
chunk.resize(to_read);
|
||||
memcpy(chunk.ptrw(), polled_response.ptr() + response_read_offset, to_read);
|
||||
response_read_offset += to_read;
|
||||
if (response_buffer.size() != read_limit) {
|
||||
response_buffer.resize(read_limit);
|
||||
}
|
||||
int read = godot_js_fetch_read_chunk(js_id, response_buffer.ptrw(), read_limit);
|
||||
|
||||
if (response_read_offset == polled_response.size()) {
|
||||
status = STATUS_CONNECTED;
|
||||
polled_response.resize(0);
|
||||
godot_xhr_reset(xhr_id);
|
||||
// Check if the stream is over.
|
||||
godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id);
|
||||
if (state == GODOT_JS_FETCH_STATE_DONE) {
|
||||
status = STATUS_DISCONNECTED;
|
||||
} else if (state != GODOT_JS_FETCH_STATE_BODY) {
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
PackedByteArray chunk;
|
||||
if (!read) {
|
||||
return chunk;
|
||||
}
|
||||
chunk.resize(read);
|
||||
copymem(chunk.ptrw(), response_buffer.ptr(), read);
|
||||
return chunk;
|
||||
}
|
||||
|
||||
|
@ -217,48 +243,48 @@ Error HTTPClient::poll() {
|
|||
return OK;
|
||||
|
||||
case STATUS_CONNECTED:
|
||||
case STATUS_BODY:
|
||||
return OK;
|
||||
|
||||
case STATUS_BODY: {
|
||||
godot_js_fetch_state_t state = godot_js_fetch_state_get(js_id);
|
||||
if (state == GODOT_JS_FETCH_STATE_DONE) {
|
||||
status = STATUS_DISCONNECTED;
|
||||
} else if (state != GODOT_JS_FETCH_STATE_BODY) {
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
case STATUS_CONNECTION_ERROR:
|
||||
return ERR_CONNECTION_ERROR;
|
||||
|
||||
case STATUS_REQUESTING: {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (!has_polled) {
|
||||
has_polled = true;
|
||||
} else {
|
||||
// forcing synchronous requests is not possible on the web
|
||||
if (last_polling_frame == Engine::get_singleton()->get_process_frames()) {
|
||||
WARN_PRINT("HTTPClient polled multiple times in one frame, "
|
||||
"but request cannot progress more than once per "
|
||||
"frame on the HTML5 platform.");
|
||||
}
|
||||
// forcing synchronous requests is not possible on the web
|
||||
if (last_polling_frame == Engine::get_singleton()->get_process_frames()) {
|
||||
WARN_PRINT("HTTPClient polled multiple times in one frame, "
|
||||
"but request cannot progress more than once per "
|
||||
"frame on the HTML5 platform.");
|
||||
}
|
||||
last_polling_frame = Engine::get_singleton()->get_process_frames();
|
||||
#endif
|
||||
|
||||
polled_response_code = godot_xhr_get_status(xhr_id);
|
||||
if (godot_xhr_get_ready_state(xhr_id) != XHR_READY_STATE_DONE) {
|
||||
polled_response_code = godot_js_fetch_http_status_get(js_id);
|
||||
godot_js_fetch_state_t js_state = godot_js_fetch_state_get(js_id);
|
||||
if (js_state == GODOT_JS_FETCH_STATE_REQUESTING) {
|
||||
return OK;
|
||||
} else if (!polled_response_code) {
|
||||
} else if (js_state == GODOT_JS_FETCH_STATE_ERROR) {
|
||||
// Fetch is in error state.
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
if (godot_js_fetch_read_headers(js_id, &_parse_headers, this)) {
|
||||
// Failed to parse headers.
|
||||
status = STATUS_CONNECTION_ERROR;
|
||||
return ERR_CONNECTION_ERROR;
|
||||
}
|
||||
|
||||
status = STATUS_BODY;
|
||||
|
||||
PackedByteArray bytes;
|
||||
int len = godot_xhr_get_response_headers_length(xhr_id);
|
||||
bytes.resize(len + 1);
|
||||
|
||||
godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(bytes.ptrw()), len);
|
||||
bytes.ptrw()[len] = 0;
|
||||
|
||||
polled_response_header = String::utf8(reinterpret_cast<const char *>(bytes.ptr()));
|
||||
|
||||
polled_response.resize(godot_xhr_get_response_length(xhr_id));
|
||||
godot_xhr_get_response(xhr_id, polled_response.ptrw(), polled_response.size());
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -269,9 +295,8 @@ Error HTTPClient::poll() {
|
|||
}
|
||||
|
||||
HTTPClient::HTTPClient() {
|
||||
xhr_id = godot_xhr_new();
|
||||
}
|
||||
|
||||
HTTPClient::~HTTPClient() {
|
||||
godot_xhr_free(xhr_id);
|
||||
close();
|
||||
}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
/*************************************************************************/
|
||||
/* http_request.h */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
#ifndef HTTP_REQUEST_H
|
||||
#define HTTP_REQUEST_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "stddef.h"
|
||||
|
||||
typedef enum {
|
||||
XHR_READY_STATE_UNSENT = 0,
|
||||
XHR_READY_STATE_OPENED = 1,
|
||||
XHR_READY_STATE_HEADERS_RECEIVED = 2,
|
||||
XHR_READY_STATE_LOADING = 3,
|
||||
XHR_READY_STATE_DONE = 4,
|
||||
} godot_xhr_ready_state_t;
|
||||
|
||||
extern int godot_xhr_new();
|
||||
extern void godot_xhr_reset(int p_xhr_id);
|
||||
extern void godot_xhr_free(int p_xhr_id);
|
||||
|
||||
extern int godot_xhr_open(int p_xhr_id, const char *p_method, const char *p_url, const char *p_user = nullptr, const char *p_password = nullptr);
|
||||
|
||||
extern void godot_xhr_set_request_header(int p_xhr_id, const char *p_header, const char *p_value);
|
||||
|
||||
extern void godot_xhr_send(int p_xhr_id, const void *p_data, int p_len);
|
||||
extern void godot_xhr_abort(int p_xhr_id);
|
||||
|
||||
/* this is an HTTPClient::ResponseCode, not ::Status */
|
||||
extern int godot_xhr_get_status(int p_xhr_id);
|
||||
extern godot_xhr_ready_state_t godot_xhr_get_ready_state(int p_xhr_id);
|
||||
|
||||
extern int godot_xhr_get_response_headers_length(int p_xhr_id);
|
||||
extern void godot_xhr_get_response_headers(int p_xhr_id, char *r_dst, int p_len);
|
||||
|
||||
extern int godot_xhr_get_response_length(int p_xhr_id);
|
||||
extern void godot_xhr_get_response(int p_xhr_id, void *r_dst, int p_len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* HTTP_REQUEST_H */
|
|
@ -100,6 +100,11 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
|||
* @type {Array.<string>}
|
||||
*/
|
||||
gdnativeLibs: [],
|
||||
/**
|
||||
* @ignore
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
fileSizes: [],
|
||||
/**
|
||||
* A callback function for handling Godot's ``OS.execute`` calls.
|
||||
*
|
||||
|
@ -219,6 +224,7 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
|||
this.canvasResizePolicy = parse('canvasResizePolicy', this.canvasResizePolicy);
|
||||
this.persistentPaths = parse('persistentPaths', this.persistentPaths);
|
||||
this.gdnativeLibs = parse('gdnativeLibs', this.gdnativeLibs);
|
||||
this.fileSizes = parse('fileSizes', this.fileSizes);
|
||||
this.args = parse('args', this.args);
|
||||
this.onExecute = parse('onExecute', this.onExecute);
|
||||
this.onExit = parse('onExit', this.onExit);
|
||||
|
@ -227,10 +233,10 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
|||
/**
|
||||
* @ignore
|
||||
* @param {string} loadPath
|
||||
* @param {Promise} loadPromise
|
||||
* @param {Response} response
|
||||
*/
|
||||
Config.prototype.getModuleConfig = function (loadPath, loadPromise) {
|
||||
let loader = loadPromise;
|
||||
Config.prototype.getModuleConfig = function (loadPath, response) {
|
||||
let r = response;
|
||||
return {
|
||||
'print': this.onPrint,
|
||||
'printErr': this.onPrintError,
|
||||
|
@ -238,12 +244,17 @@ const InternalConfig = function (initConfig) { // eslint-disable-line no-unused-
|
|||
'noExitRuntime': true,
|
||||
'dynamicLibraries': [`${loadPath}.side.wasm`],
|
||||
'instantiateWasm': function (imports, onSuccess) {
|
||||
loader.then(function (xhr) {
|
||||
WebAssembly.instantiate(xhr.response, imports).then(function (result) {
|
||||
onSuccess(result['instance'], result['module']);
|
||||
function done(result) {
|
||||
onSuccess(result['instance'], result['module']);
|
||||
}
|
||||
if (typeof (WebAssembly.instantiateStreaming) !== 'undefined') {
|
||||
WebAssembly.instantiateStreaming(Promise.resolve(r), imports).then(done);
|
||||
} else {
|
||||
r.arrayBuffer().then(function (buffer) {
|
||||
WebAssembly.instantiate(buffer, imports).then(done);
|
||||
});
|
||||
});
|
||||
loader = null;
|
||||
}
|
||||
r = null;
|
||||
return {};
|
||||
},
|
||||
'locateFile': function (path) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
var Godot;
|
||||
var WebAssembly = {};
|
||||
WebAssembly.instantiate = function(buffer, imports) {};
|
||||
WebAssembly.instantiateStreaming = function(response, imports) {};
|
||||
|
|
|
@ -35,14 +35,15 @@ const Engine = (function () {
|
|||
* Load the engine from the specified base path.
|
||||
*
|
||||
* @param {string} basePath Base path of the engine to load.
|
||||
* @param {number=} [size=0] The file size if known.
|
||||
* @returns {Promise} A Promise that resolves once the engine is loaded.
|
||||
*
|
||||
* @function Engine.load
|
||||
*/
|
||||
Engine.load = function (basePath) {
|
||||
Engine.load = function (basePath, size) {
|
||||
if (loadPromise == null) {
|
||||
loadPath = basePath;
|
||||
loadPromise = preloader.loadPromise(`${loadPath}.wasm`);
|
||||
loadPromise = preloader.loadPromise(`${loadPath}.wasm`, size, true);
|
||||
requestAnimationFrame(preloader.animateProgress);
|
||||
}
|
||||
return loadPromise;
|
||||
|
@ -96,23 +97,27 @@ const Engine = (function () {
|
|||
initPromise = Promise.reject(new Error('A base path must be provided when calling `init` and the engine is not loaded.'));
|
||||
return initPromise;
|
||||
}
|
||||
Engine.load(basePath);
|
||||
Engine.load(basePath, this.config.fileSizes[`${basePath}.wasm`]);
|
||||
}
|
||||
const me = this;
|
||||
function doInit(promise) {
|
||||
return promise.then(function (response) {
|
||||
return Godot(me.config.getModuleConfig(loadPath, response.clone()));
|
||||
}).then(function (module) {
|
||||
const paths = me.config.persistentPaths;
|
||||
return module['initFS'](paths).then(function (err) {
|
||||
return Promise.resolve(module);
|
||||
});
|
||||
}).then(function (module) {
|
||||
me.rtenv = module;
|
||||
if (me.config.unloadAfterInit) {
|
||||
Engine.unload();
|
||||
}
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
preloader.setProgressFunc(this.config.onProgress);
|
||||
let config = this.config.getModuleConfig(loadPath, loadPromise);
|
||||
const me = this;
|
||||
initPromise = new Promise(function (resolve, reject) {
|
||||
Godot(config).then(function (module) {
|
||||
module['initFS'](me.config.persistentPaths).then(function (fs_err) {
|
||||
me.rtenv = module;
|
||||
if (me.config.unloadAfterInit) {
|
||||
Engine.unload();
|
||||
}
|
||||
resolve();
|
||||
config = null;
|
||||
});
|
||||
});
|
||||
});
|
||||
initPromise = doInit(loadPromise);
|
||||
return initPromise;
|
||||
},
|
||||
|
||||
|
@ -133,7 +138,7 @@ const Engine = (function () {
|
|||
* @returns {Promise} A Promise that resolves once the file is loaded.
|
||||
*/
|
||||
preloadFile: function (file, path) {
|
||||
return preloader.preload(file, path);
|
||||
return preloader.preload(file, path, this.config.fileSizes[file]);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,54 +1,79 @@
|
|||
const Preloader = /** @constructor */ function () { // eslint-disable-line no-unused-vars
|
||||
const loadXHR = function (resolve, reject, file, tracker, attempts) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
tracker[file] = {
|
||||
total: 0,
|
||||
loaded: 0,
|
||||
final: false,
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
if (attempts <= 1) {
|
||||
reject(new Error(`Failed loading file '${file}'`));
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
loadXHR(resolve, reject, file, tracker, attempts - 1);
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
xhr.onabort = function () {
|
||||
tracker[file].final = true;
|
||||
reject(new Error(`Loading file '${file}' was aborted.`));
|
||||
};
|
||||
xhr.onloadstart = function (ev) {
|
||||
tracker[file].total = ev.total;
|
||||
tracker[file].loaded = ev.loaded;
|
||||
};
|
||||
xhr.onprogress = function (ev) {
|
||||
tracker[file].loaded = ev.loaded;
|
||||
tracker[file].total = ev.total;
|
||||
};
|
||||
xhr.onload = function () {
|
||||
if (xhr.status >= 400) {
|
||||
if (xhr.status < 500 || attempts <= 1) {
|
||||
reject(new Error(`Failed loading file '${file}': ${xhr.statusText}`));
|
||||
xhr.abort();
|
||||
} else {
|
||||
setTimeout(function () {
|
||||
loadXHR(resolve, reject, file, tracker, attempts - 1);
|
||||
}, 1000);
|
||||
function getTrackedResponse(response, load_status) {
|
||||
let clen = 0;
|
||||
let compressed = false;
|
||||
response.headers.forEach(function (value, header) {
|
||||
const h = header.toLowerCase().trim();
|
||||
// We can't accurately compute compressed stream length.
|
||||
if (h === 'content-encoding') {
|
||||
compressed = true;
|
||||
} else if (h === 'content-length') {
|
||||
const length = parseInt(value, 10);
|
||||
if (!Number.isNaN(length) && length > 0) {
|
||||
clen = length;
|
||||
}
|
||||
} else {
|
||||
tracker[file].final = true;
|
||||
resolve(xhr);
|
||||
}
|
||||
};
|
||||
// Make request.
|
||||
xhr.open('GET', file);
|
||||
if (!file.endsWith('.js')) {
|
||||
xhr.responseType = 'arraybuffer';
|
||||
});
|
||||
if (!compressed && clen) {
|
||||
load_status.total = clen;
|
||||
}
|
||||
xhr.send();
|
||||
};
|
||||
function onloadprogress(reader, controller) {
|
||||
return reader.read().then(function (result) {
|
||||
if (load_status.done) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (result.value) {
|
||||
controller.enqueue(result.value);
|
||||
load_status.loaded += result.value.length;
|
||||
}
|
||||
if (!result.done) {
|
||||
return onloadprogress(reader, controller);
|
||||
}
|
||||
load_status.done = true;
|
||||
return Promise.resolve();
|
||||
});
|
||||
}
|
||||
const reader = response.body.getReader();
|
||||
return new Response(new ReadableStream({
|
||||
start: function (controller) {
|
||||
onloadprogress(reader, controller).then(function () {
|
||||
controller.close();
|
||||
});
|
||||
},
|
||||
}), { headers: response.headers });
|
||||
}
|
||||
|
||||
function loadFetch(file, tracker, fileSize, raw) {
|
||||
tracker[file] = {
|
||||
total: fileSize || 0,
|
||||
loaded: 0,
|
||||
done: false,
|
||||
};
|
||||
return fetch(file).then(function (response) {
|
||||
if (!response.ok) {
|
||||
return Promise.reject(new Error(`Failed loading file '${file}'`));
|
||||
}
|
||||
const tr = getTrackedResponse(response, tracker[file]);
|
||||
if (raw) {
|
||||
return Promise.resolve(tr);
|
||||
}
|
||||
return tr.arrayBuffer();
|
||||
});
|
||||
}
|
||||
|
||||
function retry(func, attempts = 1) {
|
||||
function onerror(err) {
|
||||
if (attempts <= 1) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(function () {
|
||||
retry(func, attempts - 1).then(resolve).catch(reject);
|
||||
}, 1000);
|
||||
});
|
||||
}
|
||||
return func().catch(onerror);
|
||||
}
|
||||
|
||||
const DOWNLOAD_ATTEMPTS_MAX = 4;
|
||||
const loadingFiles = {};
|
||||
|
@ -63,7 +88,7 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
|
|||
|
||||
Object.keys(loadingFiles).forEach(function (file) {
|
||||
const stat = loadingFiles[file];
|
||||
if (!stat.final) {
|
||||
if (!stat.done) {
|
||||
progressIsFinal = false;
|
||||
}
|
||||
if (!totalIsValid || stat.total === 0) {
|
||||
|
@ -92,21 +117,19 @@ const Preloader = /** @constructor */ function () { // eslint-disable-line no-un
|
|||
progressFunc = callback;
|
||||
};
|
||||
|
||||
this.loadPromise = function (file) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
loadXHR(resolve, reject, file, loadingFiles, DOWNLOAD_ATTEMPTS_MAX);
|
||||
});
|
||||
this.loadPromise = function (file, fileSize, raw = false) {
|
||||
return retry(loadFetch.bind(null, file, loadingFiles, fileSize, raw), DOWNLOAD_ATTEMPTS_MAX);
|
||||
};
|
||||
|
||||
this.preloadedFiles = [];
|
||||
this.preload = function (pathOrBuffer, destPath) {
|
||||
this.preload = function (pathOrBuffer, destPath, fileSize) {
|
||||
let buffer = null;
|
||||
if (typeof pathOrBuffer === 'string') {
|
||||
const me = this;
|
||||
return this.loadPromise(pathOrBuffer).then(function (xhr) {
|
||||
return this.loadPromise(pathOrBuffer, fileSize).then(function (buf) {
|
||||
me.preloadedFiles.push({
|
||||
path: destPath || pathOrBuffer,
|
||||
buffer: xhr.response,
|
||||
buffer: buf,
|
||||
});
|
||||
return Promise.resolve();
|
||||
});
|
||||
|
|
|
@ -671,7 +671,7 @@ const GodotDisplay = {
|
|||
document.head.appendChild(link);
|
||||
}
|
||||
const old_icon = GodotDisplay.window_icon;
|
||||
const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
|
||||
const png = new Blob([GodotRuntime.heapSlice(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
|
||||
GodotDisplay.window_icon = URL.createObjectURL(png);
|
||||
link.href = GodotDisplay.window_icon;
|
||||
if (old_icon) {
|
||||
|
@ -711,7 +711,7 @@ const GodotDisplay = {
|
|||
const shape = GodotRuntime.parseString(p_shape);
|
||||
const old_shape = GodotDisplayCursor.cursors[shape];
|
||||
if (p_len > 0) {
|
||||
const png = new Blob([GodotRuntime.heapCopy(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
|
||||
const png = new Blob([GodotRuntime.heapSlice(HEAPU8, p_ptr, p_len)], { type: 'image/png' });
|
||||
const url = URL.createObjectURL(png);
|
||||
GodotDisplayCursor.cursors[shape] = {
|
||||
url: url,
|
||||
|
|
258
platform/javascript/js/libs/library_godot_fetch.js
Normal file
258
platform/javascript/js/libs/library_godot_fetch.js
Normal file
|
@ -0,0 +1,258 @@
|
|||
/*************************************************************************/
|
||||
/* library_godot_fetch.js */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
const GodotFetch = {
|
||||
$GodotFetch__deps: ['$GodotRuntime'],
|
||||
$GodotFetch: {
|
||||
|
||||
onread: function (id, result) {
|
||||
const obj = IDHandler.get(id);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
if (result.value) {
|
||||
obj.chunks.push(result.value);
|
||||
}
|
||||
obj.reading = false;
|
||||
obj.done = result.done;
|
||||
},
|
||||
|
||||
onresponse: function (id, response) {
|
||||
const obj = IDHandler.get(id);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
let size = -1;
|
||||
let compressed = false;
|
||||
let chunked = false;
|
||||
response.headers.forEach(function (value, header) {
|
||||
const v = value.toLowerCase().trim();
|
||||
const h = header.toLowerCase().trim();
|
||||
if (h === 'content-encoding') {
|
||||
compressed = true;
|
||||
size = -1;
|
||||
} else if (h === 'content-length') {
|
||||
const len = Number.parseInt(value, 10);
|
||||
if (!Number.isNaN(len) && !compressed) {
|
||||
size = len;
|
||||
}
|
||||
} else if (h === 'transfer-encoding' && v === 'chunked') {
|
||||
chunked = true;
|
||||
}
|
||||
});
|
||||
obj.bodySize = size;
|
||||
obj.status = response.status;
|
||||
obj.response = response;
|
||||
obj.reader = response.body.getReader();
|
||||
obj.chunked = chunked;
|
||||
},
|
||||
|
||||
onerror: function (id, err) {
|
||||
GodotRuntime.error(err);
|
||||
const obj = IDHandler.get(id);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
obj.error = err;
|
||||
},
|
||||
|
||||
create: function (method, url, headers, body) {
|
||||
const obj = {
|
||||
request: null,
|
||||
response: null,
|
||||
reader: null,
|
||||
error: null,
|
||||
done: false,
|
||||
reading: false,
|
||||
status: 0,
|
||||
chunks: [],
|
||||
bodySize: -1,
|
||||
};
|
||||
const id = IDHandler.add(obj);
|
||||
const init = {
|
||||
method: method,
|
||||
headers: headers,
|
||||
body: body,
|
||||
};
|
||||
obj.request = fetch(url, init);
|
||||
obj.request.then(GodotFetch.onresponse.bind(null, id)).catch(GodotFetch.onerror.bind(null, id));
|
||||
return id;
|
||||
},
|
||||
|
||||
free: function (id) {
|
||||
const obj = IDHandler.get(id);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
IDHandler.remove(id);
|
||||
if (!obj.request) {
|
||||
return;
|
||||
}
|
||||
// Try to abort
|
||||
obj.request.then(function (response) {
|
||||
response.abort();
|
||||
}).catch(function (e) { /* nothing to do */ });
|
||||
},
|
||||
|
||||
read: function (id) {
|
||||
const obj = IDHandler.get(id);
|
||||
if (!obj) {
|
||||
return;
|
||||
}
|
||||
if (obj.reader && !obj.reading) {
|
||||
if (obj.done) {
|
||||
obj.reader = null;
|
||||
return;
|
||||
}
|
||||
obj.reading = true;
|
||||
obj.reader.read().then(GodotFetch.onread.bind(null, id)).catch(GodotFetch.onerror.bind(null, id));
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
godot_js_fetch_create__sig: 'iii',
|
||||
godot_js_fetch_create: function (p_method, p_url, p_headers, p_headers_size, p_body, p_body_size) {
|
||||
const method = GodotRuntime.parseString(p_method);
|
||||
const url = GodotRuntime.parseString(p_url);
|
||||
const headers = GodotRuntime.parseStringArray(p_headers, p_headers_size);
|
||||
const body = p_body_size ? GodotRuntime.heapSlice(HEAP8, p_body, p_body_size) : null;
|
||||
return GodotFetch.create(method, url, headers.map(function (hv) {
|
||||
const idx = hv.indexOf(':');
|
||||
if (idx <= 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
hv.slice(0, idx).trim(),
|
||||
hv.slice(idx + 1).trim(),
|
||||
];
|
||||
}).filter(function (v) {
|
||||
return v.length === 2;
|
||||
}), body);
|
||||
},
|
||||
|
||||
godot_js_fetch_state_get__sig: 'ii',
|
||||
godot_js_fetch_state_get: function (p_id) {
|
||||
const obj = IDHandler.get(p_id);
|
||||
if (!obj) {
|
||||
return -1;
|
||||
}
|
||||
if (obj.error) {
|
||||
return -1;
|
||||
}
|
||||
if (!obj.response) {
|
||||
return 0;
|
||||
}
|
||||
if (obj.reader) {
|
||||
return 1;
|
||||
}
|
||||
if (obj.done) {
|
||||
return 2;
|
||||
}
|
||||
return -1;
|
||||
},
|
||||
|
||||
godot_js_fetch_http_status_get__sig: 'ii',
|
||||
godot_js_fetch_http_status_get: function (p_id) {
|
||||
const obj = IDHandler.get(p_id);
|
||||
if (!obj || !obj.response) {
|
||||
return 0;
|
||||
}
|
||||
return obj.status;
|
||||
},
|
||||
|
||||
godot_js_fetch_read_headers__sig: 'iii',
|
||||
godot_js_fetch_read_headers: function (p_id, p_parse_cb, p_ref) {
|
||||
const obj = IDHandler.get(p_id);
|
||||
if (!obj || !obj.response) {
|
||||
return 1;
|
||||
}
|
||||
const cb = GodotRuntime.get_func(p_parse_cb);
|
||||
const arr = [];
|
||||
obj.response.headers.forEach(function (v, h) {
|
||||
arr.push(`${h}:${v}`);
|
||||
});
|
||||
const c_ptr = GodotRuntime.allocStringArray(arr);
|
||||
cb(arr.length, c_ptr, p_ref);
|
||||
GodotRuntime.freeStringArray(c_ptr, arr.length);
|
||||
return 0;
|
||||
},
|
||||
|
||||
godot_js_fetch_read_chunk__sig: 'ii',
|
||||
godot_js_fetch_read_chunk: function (p_id, p_buf, p_buf_size) {
|
||||
const obj = IDHandler.get(p_id);
|
||||
if (!obj || !obj.response) {
|
||||
return 0;
|
||||
}
|
||||
let to_read = p_buf_size;
|
||||
const chunks = obj.chunks;
|
||||
while (to_read && chunks.length) {
|
||||
const chunk = obj.chunks[0];
|
||||
if (chunk.length > to_read) {
|
||||
GodotRuntime.heapCopy(HEAP8, chunk.slice(0, to_read), p_buf);
|
||||
chunks[0] = chunk.slice(to_read);
|
||||
to_read = 0;
|
||||
} else {
|
||||
GodotRuntime.heapCopy(HEAP8, chunk, p_buf);
|
||||
to_read -= chunk.length;
|
||||
chunks.pop();
|
||||
}
|
||||
}
|
||||
if (!chunks.length) {
|
||||
GodotFetch.read(p_id);
|
||||
}
|
||||
return p_buf_size - to_read;
|
||||
},
|
||||
|
||||
godot_js_fetch_body_length_get__sig: 'ii',
|
||||
godot_js_fetch_body_length_get: function (p_id) {
|
||||
const obj = IDHandler.get(p_id);
|
||||
if (!obj || !obj.response) {
|
||||
return -1;
|
||||
}
|
||||
return obj.bodySize;
|
||||
},
|
||||
|
||||
godot_js_fetch_is_chunked__sig: 'ii',
|
||||
godot_js_fetch_is_chunked: function (p_id) {
|
||||
const obj = IDHandler.get(p_id);
|
||||
if (!obj || !obj.response) {
|
||||
return -1;
|
||||
}
|
||||
return obj.chunked ? 1 : 0;
|
||||
},
|
||||
|
||||
godot_js_fetch_free__sig: 'vi',
|
||||
godot_js_fetch_free: function (id) {
|
||||
GodotFetch.free(id);
|
||||
},
|
||||
};
|
||||
|
||||
autoAddDeps(GodotFetch, '$GodotFetch');
|
||||
mergeInto(LibraryManager.library, GodotFetch);
|
|
@ -1,142 +0,0 @@
|
|||
/*************************************************************************/
|
||||
/* http_request.js */
|
||||
/*************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/*************************************************************************/
|
||||
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
|
||||
/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/*************************************************************************/
|
||||
|
||||
const GodotHTTPRequest = {
|
||||
$GodotHTTPRequest__deps: ['$GodotRuntime'],
|
||||
$GodotHTTPRequest: {
|
||||
requests: [],
|
||||
|
||||
getUnusedRequestId: function () {
|
||||
const idMax = GodotHTTPRequest.requests.length;
|
||||
for (let potentialId = 0; potentialId < idMax; ++potentialId) {
|
||||
if (GodotHTTPRequest.requests[potentialId] instanceof XMLHttpRequest) {
|
||||
continue;
|
||||
}
|
||||
return potentialId;
|
||||
}
|
||||
GodotHTTPRequest.requests.push(null);
|
||||
return idMax;
|
||||
},
|
||||
|
||||
setupRequest: function (xhr) {
|
||||
xhr.responseType = 'arraybuffer';
|
||||
},
|
||||
},
|
||||
|
||||
godot_xhr_new__sig: 'i',
|
||||
godot_xhr_new: function () {
|
||||
const newId = GodotHTTPRequest.getUnusedRequestId();
|
||||
GodotHTTPRequest.requests[newId] = new XMLHttpRequest();
|
||||
GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]);
|
||||
return newId;
|
||||
},
|
||||
|
||||
godot_xhr_reset__sig: 'vi',
|
||||
godot_xhr_reset: function (xhrId) {
|
||||
GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest();
|
||||
GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]);
|
||||
},
|
||||
|
||||
godot_xhr_free__sig: 'vi',
|
||||
godot_xhr_free: function (xhrId) {
|
||||
GodotHTTPRequest.requests[xhrId].abort();
|
||||
GodotHTTPRequest.requests[xhrId] = null;
|
||||
},
|
||||
|
||||
godot_xhr_open__sig: 'viiiii',
|
||||
godot_xhr_open: function (xhrId, method, url, p_user, p_password) {
|
||||
const user = p_user > 0 ? GodotRuntime.parseString(p_user) : null;
|
||||
const password = p_password > 0 ? GodotRuntime.parseString(p_password) : null;
|
||||
GodotHTTPRequest.requests[xhrId].open(GodotRuntime.parseString(method), GodotRuntime.parseString(url), true, user, password);
|
||||
},
|
||||
|
||||
godot_xhr_set_request_header__sig: 'viii',
|
||||
godot_xhr_set_request_header: function (xhrId, header, value) {
|
||||
GodotHTTPRequest.requests[xhrId].setRequestHeader(GodotRuntime.parseString(header), GodotRuntime.parseString(value));
|
||||
},
|
||||
|
||||
godot_xhr_send__sig: 'viii',
|
||||
godot_xhr_send: function (xhrId, p_ptr, p_len) {
|
||||
let data = null;
|
||||
if (p_ptr && p_len) {
|
||||
data = GodotRuntime.heapCopy(HEAP8, p_ptr, p_len);
|
||||
}
|
||||
GodotHTTPRequest.requests[xhrId].send(data);
|
||||
},
|
||||
|
||||
godot_xhr_abort__sig: 'vi',
|
||||
godot_xhr_abort: function (xhrId) {
|
||||
GodotHTTPRequest.requests[xhrId].abort();
|
||||
},
|
||||
|
||||
godot_xhr_get_status__sig: 'ii',
|
||||
godot_xhr_get_status: function (xhrId) {
|
||||
return GodotHTTPRequest.requests[xhrId].status;
|
||||
},
|
||||
|
||||
godot_xhr_get_ready_state__sig: 'ii',
|
||||
godot_xhr_get_ready_state: function (xhrId) {
|
||||
return GodotHTTPRequest.requests[xhrId].readyState;
|
||||
},
|
||||
|
||||
godot_xhr_get_response_headers_length__sig: 'ii',
|
||||
godot_xhr_get_response_headers_length: function (xhrId) {
|
||||
const headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
|
||||
return headers === null ? 0 : GodotRuntime.strlen(headers);
|
||||
},
|
||||
|
||||
godot_xhr_get_response_headers__sig: 'viii',
|
||||
godot_xhr_get_response_headers: function (xhrId, dst, len) {
|
||||
const str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders();
|
||||
if (str === null) {
|
||||
return;
|
||||
}
|
||||
GodotRuntime.stringToHeap(str, dst, len);
|
||||
},
|
||||
|
||||
godot_xhr_get_response_length__sig: 'ii',
|
||||
godot_xhr_get_response_length: function (xhrId) {
|
||||
const body = GodotHTTPRequest.requests[xhrId].response;
|
||||
return body === null ? 0 : body.byteLength;
|
||||
},
|
||||
|
||||
godot_xhr_get_response__sig: 'viii',
|
||||
godot_xhr_get_response: function (xhrId, dst, len) {
|
||||
let buf = GodotHTTPRequest.requests[xhrId].response;
|
||||
if (buf === null) {
|
||||
return;
|
||||
}
|
||||
buf = new Uint8Array(buf).subarray(0, len);
|
||||
HEAPU8.set(buf, dst);
|
||||
},
|
||||
};
|
||||
|
||||
autoAddDeps(GodotHTTPRequest, '$GodotHTTPRequest');
|
||||
mergeInto(LibraryManager.library, GodotHTTPRequest);
|
|
@ -72,11 +72,16 @@ const GodotRuntime = {
|
|||
return p_heap.subarray(p_ptr / bytes, p_ptr / bytes + p_len);
|
||||
},
|
||||
|
||||
heapCopy: function (p_heap, p_ptr, p_len) {
|
||||
heapSlice: function (p_heap, p_ptr, p_len) {
|
||||
const bytes = p_heap.BYTES_PER_ELEMENT;
|
||||
return p_heap.slice(p_ptr / bytes, p_ptr / bytes + p_len);
|
||||
},
|
||||
|
||||
heapCopy: function (p_dst, p_src, p_ptr) {
|
||||
const bytes = p_src.BYTES_PER_ELEMENT;
|
||||
return p_dst.set(p_src, p_ptr / bytes);
|
||||
},
|
||||
|
||||
/*
|
||||
* Strings
|
||||
*/
|
||||
|
@ -84,6 +89,15 @@ const GodotRuntime = {
|
|||
return UTF8ToString(p_ptr); // eslint-disable-line no-undef
|
||||
},
|
||||
|
||||
parseStringArray: function (p_ptr, p_size) {
|
||||
const strings = [];
|
||||
const ptrs = GodotRuntime.heapSub(HEAP32, p_ptr, p_size); // TODO wasm64
|
||||
ptrs.forEach(function (ptr) {
|
||||
strings.push(GodotRuntime.parseString(ptr));
|
||||
});
|
||||
return strings;
|
||||
},
|
||||
|
||||
strlen: function (p_str) {
|
||||
return lengthBytesUTF8(p_str); // eslint-disable-line no-undef
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue