diff options
Diffstat (limited to 'platform/javascript')
21 files changed, 1011 insertions, 1427 deletions
diff --git a/platform/javascript/SCsub b/platform/javascript/SCsub index f01d9367d2..8d505a5829 100644 --- a/platform/javascript/SCsub +++ b/platform/javascript/SCsub @@ -6,8 +6,8 @@ javascript_files = [ "os_javascript.cpp", "audio_driver_javascript.cpp", "javascript_main.cpp", - "audio_server_javascript.cpp", "power_javascript.cpp", + "http_client_javascript.cpp", "javascript_eval.cpp", ] @@ -19,33 +19,23 @@ javascript_objects = [] for x in javascript_files: javascript_objects.append(env_javascript.Object(x)) -env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_audio_server_mix_function','_main_after_fs_sync','_send_notification']\""]) +env.Append(LINKFLAGS=["-s", "EXPORTED_FUNCTIONS=\"['_main','_main_after_fs_sync','_send_notification']\""]) -# output file name without file extension -basename = "godot" + env["PROGSUFFIX"] target_dir = env.Dir("#bin") +build = env.Program(['#bin/godot', target_dir.File('godot' + env['PROGSUFFIX'] + '.wasm')], javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js'); -zip_dir = target_dir.Dir('.javascript_zip') -zip_files = env.InstallAs(zip_dir.File('godot.html'), '#misc/dist/html/default.html') - -implicit_targets = [] -if env['wasm']: - wasm = target_dir.File(basename + '.wasm') - implicit_targets.append(wasm) - zip_files.append(InstallAs(zip_dir.File('godot.wasm'), wasm)) - prejs = env.File('pre_wasm.js') -else: - asmjs_files = [target_dir.File(basename + '.asm.js'), target_dir.File(basename + '.js.mem')] - implicit_targets.extend(asmjs_files) - zip_files.append(InstallAs([zip_dir.File('godot.asm.js'), zip_dir.File('godot.mem')], asmjs_files)) - prejs = env.File('pre_asmjs.js') - -js = env.Program(['#bin/godot'] + implicit_targets, javascript_objects, PROGSUFFIX=env['PROGSUFFIX'] + '.js')[0]; -zip_files.append(InstallAs(zip_dir.File('godot.js'), js)) +js_libraries = [] +js_libraries.append(env.File('http_request.js')) +for lib in js_libraries: + env.Append(LINKFLAGS=['--js-library', lib.path]) +env.Depends(build, js_libraries) +prejs = env.File('pre.js') postjs = env.File('engine.js') -env.Depends(js, [prejs, postjs]) env.Append(LINKFLAGS=['--pre-js', prejs.path]) env.Append(LINKFLAGS=['--post-js', postjs.path]) +env.Depends(build, [prejs, postjs]) +zip_dir = target_dir.Dir('.javascript_zip') +zip_files = env.InstallAs([zip_dir.File('godot.js'), zip_dir.File('godot.wasm'), zip_dir.File('godot.html')], build + ['#misc/dist/html/default.html']) Zip('#bin/godot', zip_files, ZIPSUFFIX=env['PROGSUFFIX'] + env['ZIPSUFFIX'], ZIPROOT=zip_dir, ZIPCOMSTR="Archving $SOURCES as $TARGET") diff --git a/platform/javascript/api/api.cpp b/platform/javascript/api/api.cpp new file mode 100644 index 0000000000..f2b2ca40bf --- /dev/null +++ b/platform/javascript/api/api.cpp @@ -0,0 +1,73 @@ +/*************************************************************************/ +/* api.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#include "api.h" +#include "engine.h" +#include "javascript_eval.h" + +static JavaScript *javascript_eval; + +void register_javascript_api() { + + ClassDB::register_virtual_class<JavaScript>(); + javascript_eval = memnew(JavaScript); + Engine::get_singleton()->add_singleton(Engine::Singleton("JavaScript", javascript_eval)); +} + +void unregister_javascript_api() { + + memdelete(javascript_eval); +} + +JavaScript *JavaScript::singleton = NULL; + +JavaScript *JavaScript::get_singleton() { + + return singleton; +} + +JavaScript::JavaScript() { + + ERR_FAIL_COND(singleton != NULL); + singleton = this; +} + +JavaScript::~JavaScript() {} + +void JavaScript::_bind_methods() { + + ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, DEFVAL(false)); +} + +#if !defined(JAVASCRIPT_ENABLED) || !defined(JAVASCRIPT_EVAL_ENABLED) +Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { + + return Variant(); +} +#endif diff --git a/platform/javascript/api/api.h b/platform/javascript/api/api.h new file mode 100644 index 0000000000..53cd9239fc --- /dev/null +++ b/platform/javascript/api/api.h @@ -0,0 +1,31 @@ +/*************************************************************************/ +/* api.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +void register_javascript_api(); +void unregister_javascript_api(); diff --git a/platform/javascript/javascript_eval.h b/platform/javascript/api/javascript_eval.h index ed7cf383da..4d0b0b21ff 100644 --- a/platform/javascript/javascript_eval.h +++ b/platform/javascript/api/javascript_eval.h @@ -27,8 +27,6 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ -#ifdef JAVASCRIPT_EVAL_ENABLED - #ifndef JAVASCRIPT_EVAL_H #define JAVASCRIPT_EVAL_H @@ -52,4 +50,3 @@ public: }; #endif // JAVASCRIPT_EVAL_H -#endif // JAVASCRIPT_EVAL_ENABLED diff --git a/platform/javascript/audio_driver_javascript.cpp b/platform/javascript/audio_driver_javascript.cpp index 4c0e5fd966..9633472cd2 100644 --- a/platform/javascript/audio_driver_javascript.cpp +++ b/platform/javascript/audio_driver_javascript.cpp @@ -29,31 +29,86 @@ /*************************************************************************/ #include "audio_driver_javascript.h" -#include <string.h> +#include <emscripten.h> -#define MAX_NUMBER_INTERFACES 3 -#define MAX_NUMBER_OUTPUT_DEVICES 6 - -/* Structure for passing information to callback function */ - -//AudioDriverJavaScript* AudioDriverJavaScript::s_ad=NULL; +AudioDriverJavaScript *AudioDriverJavaScript::singleton_js = NULL; const char *AudioDriverJavaScript::get_name() const { return "JavaScript"; } +extern "C" EMSCRIPTEN_KEEPALIVE void js_audio_driver_mix_function(int p_frames) { + + //print_line("MIXI! "+itos(p_frames)); + AudioDriverJavaScript::singleton_js->mix_to_js(p_frames); +} + +void AudioDriverJavaScript::mix_to_js(int p_frames) { + + int todo = p_frames; + int offset = 0; + + while (todo) { + + int tomix = MIN(todo, INTERNAL_BUFFER_SIZE); + + audio_server_process(p_frames, stream_buffer); + for (int i = 0; i < tomix * internal_buffer_channels; i++) { + internal_buffer[i] = float(stream_buffer[i] >> 16) / 32768.0; + } + + /* clang-format off */ + EM_ASM_ARGS({ + var data = HEAPF32.subarray($0 / 4, $0 / 4 + $2 * 2); + + for (var channel = 0; channel < _as_output_buffer.numberOfChannels; channel++) { + var outputData = _as_output_buffer.getChannelData(channel); + // Loop through samples + for (var sample = 0; sample < $2; sample++) { + // make output equal to the same as the input + outputData[sample + $1] = data[sample * 2 + channel]; + } + } + }, internal_buffer, offset, tomix); + /* clang-format on */ + + todo -= tomix; + offset += tomix; + } +} + Error AudioDriverJavaScript::init() { return OK; } void AudioDriverJavaScript::start() { + + internal_buffer = memnew_arr(float, INTERNAL_BUFFER_SIZE *internal_buffer_channels); + stream_buffer = memnew_arr(int32_t, INTERNAL_BUFFER_SIZE * 4); //max 4 channels + + /* clang-format off */ + mix_rate = EM_ASM_INT({ + _as_audioctx = new (window.AudioContext || window.webkitAudioContext); + _as_script_node = _as_audioctx.createScriptProcessor($0, 0, $1); + _as_script_node.connect(_as_audioctx.destination); + console.log(_as_script_node.bufferSize); + var jsAudioDriverMixFunction = cwrap('js_audio_driver_mix_function', null, ['number']); + + _as_script_node.onaudioprocess = function(audioProcessingEvent) { + // The output buffer contains the samples that will be modified and played + _as_output_buffer = audioProcessingEvent.outputBuffer; + jsAudioDriverMixFunction([_as_output_buffer.getChannelData(0).length]); + }; + return _as_audioctx.sampleRate; + }, INTERNAL_BUFFER_SIZE, internal_buffer_channels); + /* clang-format on */ } int AudioDriverJavaScript::get_mix_rate() const { - return 44100; + return mix_rate; } AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { @@ -63,7 +118,7 @@ AudioDriver::SpeakerMode AudioDriverJavaScript::get_speaker_mode() const { void AudioDriverJavaScript::lock() { - /* + /*no locking, as threads are not supported if (active && mutex) mutex->lock(); */ @@ -71,7 +126,7 @@ void AudioDriverJavaScript::lock() { void AudioDriverJavaScript::unlock() { - /* + /*no locking, as threads are not supported if (active && mutex) mutex->unlock(); */ @@ -81,4 +136,8 @@ void AudioDriverJavaScript::finish() { } AudioDriverJavaScript::AudioDriverJavaScript() { + + internal_buffer_channels = 2; + mix_rate = DEFAULT_MIX_RATE; + singleton_js = this; } diff --git a/platform/javascript/audio_driver_javascript.h b/platform/javascript/audio_driver_javascript.h index c5cebe800f..b265c4e030 100644 --- a/platform/javascript/audio_driver_javascript.h +++ b/platform/javascript/audio_driver_javascript.h @@ -32,10 +32,21 @@ #include "servers/audio_server.h" -#include "os/mutex.h" - class AudioDriverJavaScript : public AudioDriver { + + enum { + INTERNAL_BUFFER_SIZE = 4096, + }; + + int mix_rate; + float *internal_buffer; + int internal_buffer_channels; + int32_t *stream_buffer; + public: + void mix_to_js(int p_frames); + static AudioDriverJavaScript *singleton_js; + virtual const char *get_name() const; virtual Error init(); diff --git a/platform/javascript/audio_server_javascript.cpp b/platform/javascript/audio_server_javascript.cpp deleted file mode 100644 index ab9f66ce5b..0000000000 --- a/platform/javascript/audio_server_javascript.cpp +++ /dev/null @@ -1,853 +0,0 @@ -/*************************************************************************/ -/* audio_server_javascript.cpp */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2017 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. */ -/*************************************************************************/ -#include "audio_server_javascript.h" - -// FIXME: Needs to be ported to the new AudioServer API in 3.0 -#if 0 -#include "emscripten.h" - -AudioMixer *AudioServerJavascript::get_mixer() { - - return NULL; -} - -void AudioServerJavascript::audio_mixer_chunk_callback(int p_frames){ - - -} - - -RID AudioServerJavascript::sample_create(SampleFormat p_format, bool p_stereo, int p_length) { - - Sample *sample = memnew( Sample ); - sample->format=p_format; - sample->stereo=p_stereo; - sample->length=p_length; - sample->loop_begin=0; - sample->loop_end=p_length; - sample->loop_format=SAMPLE_LOOP_NONE; - sample->mix_rate=44100; - sample->index=-1; - - return sample_owner.make_rid(sample); - -} - -void AudioServerJavascript::sample_set_description(RID p_sample, const String& p_description){ - - -} -String AudioServerJavascript::sample_get_description(RID p_sample) const{ - - return String(); -} - -AudioServerJavascript::SampleFormat AudioServerJavascript::sample_get_format(RID p_sample) const{ - - return SAMPLE_FORMAT_PCM8; -} -bool AudioServerJavascript::sample_is_stereo(RID p_sample) const{ - - const Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND_V(!sample,false); - return sample->stereo; - -} -int AudioServerJavascript::sample_get_length(RID p_sample) const{ - const Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND_V(!sample,0); - return sample->length; -} -const void* AudioServerJavascript::sample_get_data_ptr(RID p_sample) const{ - - return NULL; -} - -void AudioServerJavascript::sample_set_data(RID p_sample, const PoolVector<uint8_t>& p_buffer){ - - Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND(!sample); - int chans = sample->stereo?2:1; - - Vector<float> buffer; - buffer.resize(sample->length*chans); - PoolVector<uint8_t>::Read r=p_buffer.read(); - if (sample->format==SAMPLE_FORMAT_PCM8) { - const int8_t*ptr = (const int8_t*)r.ptr(); - for(int i=0;i<sample->length*chans;i++) { - buffer[i]=ptr[i]/128.0; - } - } else if (sample->format==SAMPLE_FORMAT_PCM16){ - const int16_t*ptr = (const int16_t*)r.ptr(); - for(int i=0;i<sample->length*chans;i++) { - buffer[i]=ptr[i]/32768.0; - } - } else { - ERR_EXPLAIN("Unsupported for now"); - ERR_FAIL(); - } - - sample->tmp_data=buffer; - - - -} -PoolVector<uint8_t> AudioServerJavascript::sample_get_data(RID p_sample) const{ - - - return PoolVector<uint8_t>(); -} - -void AudioServerJavascript::sample_set_mix_rate(RID p_sample,int p_rate){ - Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND(!sample); - sample->mix_rate=p_rate; - -} - -int AudioServerJavascript::sample_get_mix_rate(RID p_sample) const{ - const Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND_V(!sample,0); - return sample->mix_rate; -} - - -void AudioServerJavascript::sample_set_loop_format(RID p_sample,SampleLoopFormat p_format){ - - Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND(!sample); - sample->loop_format=p_format; - -} - -AudioServerJavascript::SampleLoopFormat AudioServerJavascript::sample_get_loop_format(RID p_sample) const { - - const Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND_V(!sample,SAMPLE_LOOP_NONE); - return sample->loop_format; -} - -void AudioServerJavascript::sample_set_loop_begin(RID p_sample,int p_pos){ - - Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND(!sample); - sample->loop_begin=p_pos; - -} -int AudioServerJavascript::sample_get_loop_begin(RID p_sample) const{ - - const Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND_V(!sample,0); - return sample->loop_begin; -} - -void AudioServerJavascript::sample_set_loop_end(RID p_sample,int p_pos){ - - Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND(!sample); - sample->loop_end=p_pos; - -} -int AudioServerJavascript::sample_get_loop_end(RID p_sample) const{ - - const Sample *sample = sample_owner.get(p_sample); - ERR_FAIL_COND_V(!sample,0); - return sample->loop_end; -} - - -/* VOICE API */ - -RID AudioServerJavascript::voice_create(){ - - Voice *voice = memnew( Voice ); - - voice->index=voice_base; - voice->volume=1.0; - voice->pan=0.0; - voice->pan_depth=.0; - voice->pan_height=0.0; - voice->chorus=0; - voice->reverb_type=REVERB_SMALL; - voice->reverb=0; - voice->mix_rate=-1; - voice->positional=false; - voice->active=false; - - /* clang-format off */ - EM_ASM_( { - _as_voices[$0] = null; - _as_voice_gain[$0] = _as_audioctx.createGain(); - _as_voice_pan[$0] = _as_audioctx.createStereoPanner(); - _as_voice_gain[$0].connect(_as_voice_pan[$0]); - _as_voice_pan[$0].connect(_as_audioctx.destination); - }, voice_base); - /* clang-format on */ - - voice_base++; - - return voice_owner.make_rid( voice ); -} - -void AudioServerJavascript::voice_play(RID p_voice, RID p_sample){ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND(!voice); - Sample *sample=sample_owner.get(p_sample); - ERR_FAIL_COND(!sample); - - // due to how webaudio works, sample cration is deferred until used - // sorry! WebAudio absolutely sucks - - - if (sample->index==-1) { - //create sample if not created - ERR_FAIL_COND(sample->tmp_data.size()==0); - sample->index=sample_base; - /* clang-format off */ - EM_ASM_({ - _as_samples[$0] = _as_audioctx.createBuffer($1, $2, $3); - }, sample_base, sample->stereo ? 2 : 1, sample->length, sample->mix_rate); - /* clang-format on */ - - sample_base++; - int chans = sample->stereo?2:1; - - - for(int i=0;i<chans;i++) { - /* clang-format off */ - EM_ASM_({ - _as_edited_buffer = _as_samples[$0].getChannelData($1); - }, sample->index, i); - /* clang-format on */ - - for(int j=0;j<sample->length;j++) { - /* clang-format off */ - EM_ASM_({ - _as_edited_buffer[$0] = $1; - }, j, sample->tmp_data[j * chans + i]); - /* clang-format on */ - } - } - - sample->tmp_data.clear(); - } - - - voice->sample_mix_rate=sample->mix_rate; - if (voice->mix_rate==-1) { - voice->mix_rate=voice->sample_mix_rate; - } - - float freq_diff = Math::log(float(voice->mix_rate)/float(voice->sample_mix_rate))/Math::log(2.0); - int detune = int(freq_diff*1200.0); - - /* clang-format off */ - EM_ASM_({ - if (_as_voices[$0] !== null) { - _as_voices[$0].stop(); //stop and byebye - } - _as_voices[$0] = _as_audioctx.createBufferSource(); - _as_voices[$0].connect(_as_voice_gain[$0]); - _as_voices[$0].buffer = _as_samples[$1]; - _as_voices[$0].loopStart.value = $1; - _as_voices[$0].loopEnd.value = $2; - _as_voices[$0].loop.value = $3; - _as_voices[$0].detune.value = $6; - _as_voice_pan[$0].pan.value = $4; - _as_voice_gain[$0].gain.value = $5; - _as_voices[$0].start(); - _as_voices[$0].onended = function() { - _as_voices[$0].disconnect(_as_voice_gain[$0]); - _as_voices[$0] = null; - } - }, voice->index, sample->index, sample->mix_rate * sample->loop_begin, sample->mix_rate * sample->loop_end, sample->loop_format != SAMPLE_LOOP_NONE, voice->pan, voice->volume * fx_volume_scale, detune); - /* clang-format on */ - - voice->active=true; -} - -void AudioServerJavascript::voice_set_volume(RID p_voice, float p_volume){ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND(!voice); - - voice->volume=p_volume; - - if (voice->active) { - /* clang-format off */ - EM_ASM_({ - _as_voice_gain[$0].gain.value = $1; - }, voice->index, voice->volume * fx_volume_scale); - /* clang-format on */ - } - -} -void AudioServerJavascript::voice_set_pan(RID p_voice, float p_pan, float p_depth,float height){ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND(!voice); - - voice->pan=p_pan; - voice->pan_depth=p_depth; - voice->pan_height=height; - - if (voice->active) { - /* clang-format off */ - EM_ASM_({ - _as_voice_pan[$0].pan.value = $1; - }, voice->index, voice->pan); - /* clang-format on */ - } -} -void AudioServerJavascript::voice_set_filter(RID p_voice, FilterType p_type, float p_cutoff, float p_resonance, float p_gain){ - -} -void AudioServerJavascript::voice_set_chorus(RID p_voice, float p_chorus ){ - -} -void AudioServerJavascript::voice_set_reverb(RID p_voice, ReverbRoomType p_room_type, float p_reverb){ - -} -void AudioServerJavascript::voice_set_mix_rate(RID p_voice, int p_mix_rate){ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND(!voice); - - voice->mix_rate=p_mix_rate; - - if (voice->active) { - - float freq_diff = Math::log(float(voice->mix_rate)/float(voice->sample_mix_rate))/Math::log(2.0); - int detune = int(freq_diff*1200.0); - /* clang-format off */ - EM_ASM_({ - _as_voices[$0].detune.value = $1; - }, voice->index, detune); - /* clang-format on */ - } -} -void AudioServerJavascript::voice_set_positional(RID p_voice, bool p_positional){ - -} - -float AudioServerJavascript::voice_get_volume(RID p_voice) const{ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND_V(!voice,0); - - return voice->volume; -} -float AudioServerJavascript::voice_get_pan(RID p_voice) const{ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND_V(!voice,0); - - return voice->pan; -} -float AudioServerJavascript::voice_get_pan_depth(RID p_voice) const{ - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND_V(!voice,0); - - return voice->pan_depth; -} -float AudioServerJavascript::voice_get_pan_height(RID p_voice) const{ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND_V(!voice,0); - - return voice->pan_height; -} -AudioServerJavascript::FilterType AudioServerJavascript::voice_get_filter_type(RID p_voice) const{ - - return FILTER_NONE; -} -float AudioServerJavascript::voice_get_filter_cutoff(RID p_voice) const{ - - return 0; -} -float AudioServerJavascript::voice_get_filter_resonance(RID p_voice) const{ - - return 0; -} -float AudioServerJavascript::voice_get_chorus(RID p_voice) const{ - - return 0; -} -AudioServerJavascript::ReverbRoomType AudioServerJavascript::voice_get_reverb_type(RID p_voice) const{ - - return REVERB_SMALL; -} -float AudioServerJavascript::voice_get_reverb(RID p_voice) const{ - - return 0; -} - -int AudioServerJavascript::voice_get_mix_rate(RID p_voice) const{ - - return 44100; -} - -bool AudioServerJavascript::voice_is_positional(RID p_voice) const{ - - return false; -} - -void AudioServerJavascript::voice_stop(RID p_voice){ - - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND(!voice); - - if (voice->active) { - /* clang-format off */ - EM_ASM_({ - if (_as_voices[$0] !== null) { - _as_voices[$0].stop(); - _as_voices[$0].disconnect(_as_voice_gain[$0]); - _as_voices[$0] = null; - } - }, voice->index); - /* clang-format on */ - - voice->active=false; - } - - -} -bool AudioServerJavascript::voice_is_active(RID p_voice) const{ - Voice* voice=voice_owner.get(p_voice); - ERR_FAIL_COND_V(!voice,false); - - return voice->active; -} - -/* STREAM API */ - -RID AudioServerJavascript::audio_stream_create(AudioStream *p_stream) { - - - Stream *s = memnew(Stream); - s->audio_stream=p_stream; - s->event_stream=NULL; - s->active=false; - s->E=NULL; - s->volume_scale=1.0; - p_stream->set_mix_rate(webaudio_mix_rate); - - return stream_owner.make_rid(s); -} - -RID AudioServerJavascript::event_stream_create(EventStream *p_stream) { - - - Stream *s = memnew(Stream); - s->audio_stream=NULL; - s->event_stream=p_stream; - s->active=false; - s->E=NULL; - s->volume_scale=1.0; - //p_stream->set_mix_rate(AudioDriverJavascript::get_singleton()->get_mix_rate()); - - return stream_owner.make_rid(s); - - -} - - -void AudioServerJavascript::stream_set_active(RID p_stream, bool p_active) { - - - Stream *s = stream_owner.get(p_stream); - ERR_FAIL_COND(!s); - - if (s->active==p_active) - return; - - s->active=p_active; - if (p_active) - s->E=active_audio_streams.push_back(s); - else { - active_audio_streams.erase(s->E); - s->E=NULL; - } -} - -bool AudioServerJavascript::stream_is_active(RID p_stream) const { - - Stream *s = stream_owner.get(p_stream); - ERR_FAIL_COND_V(!s,false); - return s->active; -} - -void AudioServerJavascript::stream_set_volume_scale(RID p_stream, float p_scale) { - - Stream *s = stream_owner.get(p_stream); - ERR_FAIL_COND(!s); - s->volume_scale=p_scale; - -} - -float AudioServerJavascript::stream_set_volume_scale(RID p_stream) const { - - Stream *s = stream_owner.get(p_stream); - ERR_FAIL_COND_V(!s,0); - return s->volume_scale; - -} - - -/* Audio Physics API */ - -void AudioServerJavascript::free(RID p_id){ - - if (voice_owner.owns(p_id)) { - Voice* voice=voice_owner.get(p_id); - ERR_FAIL_COND(!voice); - - if (voice->active) { - /* clang-format off */ - EM_ASM_({ - if (_as_voices[$0] !== null) { - _as_voices[$0].stop(); - _as_voices[$0].disconnect(_as_voice_gain[$0]); - } - }, voice->index); - /* clang-format on */ - } - - /* clang-format off */ - EM_ASM_({ - delete _as_voices[$0]; - _as_voice_gain[$0].disconnect(_as_voice_pan[$0]); - delete _as_voice_gain[$0]; - _as_voice_pan[$0].disconnect(_as_audioctx.destination); - delete _as_voice_pan[$0]; - }, voice->index); - /* clang-format on */ - - voice_owner.free(p_id); - memdelete(voice); - - } else if (sample_owner.owns(p_id)) { - - Sample *sample = sample_owner.get(p_id); - ERR_FAIL_COND(!sample); - - /* clang-format off */ - EM_ASM_({ - delete _as_samples[$0]; - }, sample->index); - /* clang-format on */ - - sample_owner.free(p_id); - memdelete(sample); - - } else if (stream_owner.owns(p_id)) { - - - Stream *s=stream_owner.get(p_id); - - if (s->active) { - stream_set_active(p_id,false); - } - - memdelete(s); - stream_owner.free(p_id); - } -} - -extern "C" { - - -void audio_server_mix_function(int p_frames) { - - //print_line("MIXI! "+itos(p_frames)); - static_cast<AudioServerJavascript*>(AudioServerJavascript::get_singleton())->mix_to_js(p_frames); -} - -} - -void AudioServerJavascript::mix_to_js(int p_frames) { - - - //process in chunks to make sure to never process more than INTERNAL_BUFFER_SIZE - int todo=p_frames; - int offset=0; - - while(todo) { - - int tomix=MIN(todo,INTERNAL_BUFFER_SIZE); - driver_process_chunk(tomix); - - /* clang-format off */ - EM_ASM_({ - var data = HEAPF32.subarray($0 / 4, $0 / 4 + $2 * 2); - - for (var channel = 0; channel < _as_output_buffer.numberOfChannels; channel++) { - var outputData = _as_output_buffer.getChannelData(channel); - // Loop through samples - for (var sample = 0; sample < $2; sample++) { - // make output equal to the same as the input - outputData[sample + $1] = data[sample * 2 + channel]; - } - } - }, internal_buffer, offset, tomix); - /* clang-format on */ - - todo-=tomix; - offset+=tomix; - } -} - -void AudioServerJavascript::init(){ - - /* - // clang-format off - EM_ASM( - console.log('server is ' + audio_server); - ); - // clang-format on - */ - - - //int latency = GLOBAL_DEF("javascript/audio_latency",16384); - - internal_buffer_channels=2; - internal_buffer = memnew_arr(float,INTERNAL_BUFFER_SIZE*internal_buffer_channels); - stream_buffer = memnew_arr(int32_t,INTERNAL_BUFFER_SIZE*4); //max 4 channels - - stream_volume=0.3; - - int buffer_latency=16384; - - /* clang-format off */ - EM_ASM_( { - _as_script_node = _as_audioctx.createScriptProcessor($0, 0, 2); - _as_script_node.connect(_as_audioctx.destination); - console.log(_as_script_node.bufferSize); - - _as_script_node.onaudioprocess = function(audioProcessingEvent) { - // The output buffer contains the samples that will be modified and played - _as_output_buffer = audioProcessingEvent.outputBuffer; - audio_server_mix_function(_as_output_buffer.getChannelData(0).length); - } - }, buffer_latency); - /* clang-format on */ - - -} - -void AudioServerJavascript::finish(){ - -} -void AudioServerJavascript::update(){ - - for(List<Stream*>::Element *E=active_audio_streams.front();E;) { //stream might be removed durnig this callback - - List<Stream*>::Element *N=E->next(); - - if (E->get()->audio_stream) - E->get()->audio_stream->update(); - - E=N; - } -} - -/* MISC config */ - -void AudioServerJavascript::lock(){ - -} -void AudioServerJavascript::unlock(){ - -} -int AudioServerJavascript::get_default_channel_count() const{ - - return 1; -} -int AudioServerJavascript::get_default_mix_rate() const{ - - return 44100; -} - -void AudioServerJavascript::set_stream_global_volume_scale(float p_volume){ - - stream_volume_scale=p_volume; -} -void AudioServerJavascript::set_fx_global_volume_scale(float p_volume){ - - fx_volume_scale=p_volume; -} -void AudioServerJavascript::set_event_voice_global_volume_scale(float p_volume){ - -} - -float AudioServerJavascript::get_stream_global_volume_scale() const{ - return 1; -} -float AudioServerJavascript::get_fx_global_volume_scale() const{ - - return 1; -} -float AudioServerJavascript::get_event_voice_global_volume_scale() const{ - - return 1; -} - -uint32_t AudioServerJavascript::read_output_peak() const{ - - return 0; -} - -AudioServerJavascript *AudioServerJavascript::singleton=NULL; - -AudioServer *AudioServerJavascript::get_singleton() { - return singleton; -} - -double AudioServerJavascript::get_mix_time() const{ - - return 0; -} -double AudioServerJavascript::get_output_delay() const { - - return 0; -} - - -void AudioServerJavascript::driver_process_chunk(int p_frames) { - - - - int samples=p_frames*internal_buffer_channels; - - for(int i=0;i<samples;i++) { - internal_buffer[i]=0; - } - - - for(List<Stream*>::Element *E=active_audio_streams.front();E;E=E->next()) { - - ERR_CONTINUE(!E->get()->active); // bug? - - - AudioStream *as=E->get()->audio_stream; - if (!as) - continue; - - int channels=as->get_channel_count(); - if (channels==0) - continue; // does not want mix - if (!as->mix(stream_buffer,p_frames)) - continue; //nothing was mixed!! - - int32_t stream_vol_scale=(stream_volume*stream_volume_scale*E->get()->volume_scale)*(1<<STREAM_SCALE_BITS); - -#define STRSCALE(m_val) ((((m_val >> STREAM_SCALE_BITS) * stream_vol_scale) >> 8) / 8388608.0) - switch(channels) { - case 1: { - - for(int i=0;i<p_frames;i++) { - - internal_buffer[(i<<1)+0]+=STRSCALE(stream_buffer[i]); - internal_buffer[(i<<1)+1]+=STRSCALE(stream_buffer[i]); - } - } break; - case 2: { - - for(int i=0;i<p_frames*2;i++) { - - internal_buffer[i]+=STRSCALE(stream_buffer[i]); - } - } break; - case 4: { - - for(int i=0;i<p_frames;i++) { - - internal_buffer[(i<<2)+0]+=STRSCALE((stream_buffer[(i<<2)+0]+stream_buffer[(i<<2)+2])>>1); - internal_buffer[(i<<2)+1]+=STRSCALE((stream_buffer[(i<<2)+1]+stream_buffer[(i<<2)+3])>>1); - } - } break; - - - } - -#undef STRSCALE - } -} - - -/*void AudioServerSW::driver_process(int p_frames,int32_t *p_buffer) { - - - _output_delay=p_frames/double(AudioDriverSW::get_singleton()->get_mix_rate()); - //process in chunks to make sure to never process more than INTERNAL_BUFFER_SIZE - int todo=p_frames; - while(todo) { - - int tomix=MIN(todo,INTERNAL_BUFFER_SIZE); - driver_process_chunk(tomix,p_buffer); - p_buffer+=tomix; - todo-=tomix; - } - - -}*/ - -AudioServerJavascript::AudioServerJavascript() { - - singleton=this; - sample_base=1; - voice_base=1; - /* clang-format off */ - EM_ASM( - _as_samples = {}; - _as_voices = {}; - _as_voice_pan = {}; - _as_voice_gain = {}; - - _as_audioctx = new (window.AudioContext || window.webkitAudioContext)(); - - audio_server_mix_function = Module.cwrap('audio_server_mix_function', 'void', ['number']); - ); - /* clang-format on */ - - /* clang-format off */ - webaudio_mix_rate = EM_ASM_INT_V( - return _as_audioctx.sampleRate; - ); - /* clang-format on */ - print_line("WEBAUDIO MIX RATE: "+itos(webaudio_mix_rate)); - event_voice_scale=1.0; - fx_volume_scale=1.0; - stream_volume_scale=1.0; - -} -#endif diff --git a/platform/javascript/audio_server_javascript.h b/platform/javascript/audio_server_javascript.h deleted file mode 100644 index 0773459f56..0000000000 --- a/platform/javascript/audio_server_javascript.h +++ /dev/null @@ -1,229 +0,0 @@ -/*************************************************************************/ -/* audio_server_javascript.h */ -/*************************************************************************/ -/* This file is part of: */ -/* GODOT ENGINE */ -/* https://godotengine.org */ -/*************************************************************************/ -/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2017 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 AUDIO_SERVER_JAVASCRIPT_H -#define AUDIO_SERVER_JAVASCRIPT_H - -// FIXME: Needs to be ported to the new AudioServer API in 3.0 -#if 0 -#include "servers/audio_server.h" - -class AudioServerJavascript : public AudioServer { - - GDCLASS(AudioServerJavascript,AudioServer); - - enum { - INTERNAL_BUFFER_SIZE=4096, - STREAM_SCALE_BITS=12 - - }; - - AudioMixer *get_mixer(); - void audio_mixer_chunk_callback(int p_frames); - - struct Sample { - SampleFormat format; - SampleLoopFormat loop_format; - int loop_begin; - int loop_end; - int length; - int index; - int mix_rate; - bool stereo; - - Vector<float> tmp_data; - }; - - mutable RID_Owner<Sample> sample_owner; - int sample_base; - - struct Voice { - int index; - float volume; - float pan; - float pan_depth; - float pan_height; - - float chorus; - ReverbRoomType reverb_type; - float reverb; - - int mix_rate; - int sample_mix_rate; - bool positional; - - bool active; - - }; - - mutable RID_Owner<Voice> voice_owner; - - int voice_base; - - struct Stream { - bool active; - List<Stream*>::Element *E; - AudioStream *audio_stream; - EventStream *event_stream; - float volume_scale; - }; - - List<Stream*> active_audio_streams; - - //List<Stream*> event_streams; - - float * internal_buffer; - int internal_buffer_channels; - int32_t * stream_buffer; - - mutable RID_Owner<Stream> stream_owner; - - float stream_volume; - float stream_volume_scale; - - float event_voice_scale; - float fx_volume_scale; - - - void driver_process_chunk(int p_frames); - - int webaudio_mix_rate; - - - static AudioServerJavascript *singleton; -public: - - void mix_to_js(int p_frames); - /* SAMPLE API */ - - virtual RID sample_create(SampleFormat p_format, bool p_stereo, int p_length); - - virtual void sample_set_description(RID p_sample, const String& p_description); - virtual String sample_get_description(RID p_sample) const; - - virtual SampleFormat sample_get_format(RID p_sample) const; - virtual bool sample_is_stereo(RID p_sample) const; - virtual int sample_get_length(RID p_sample) const; - virtual const void* sample_get_data_ptr(RID p_sample) const; - - - virtual void sample_set_data(RID p_sample, const PoolVector<uint8_t>& p_buffer); - virtual PoolVector<uint8_t> sample_get_data(RID p_sample) const; - - virtual void sample_set_mix_rate(RID p_sample,int p_rate); - virtual int sample_get_mix_rate(RID p_sample) const; - - virtual void sample_set_loop_format(RID p_sample,SampleLoopFormat p_format); - virtual SampleLoopFormat sample_get_loop_format(RID p_sample) const; - - virtual void sample_set_loop_begin(RID p_sample,int p_pos); - virtual int sample_get_loop_begin(RID p_sample) const; - - virtual void sample_set_loop_end(RID p_sample,int p_pos); - virtual int sample_get_loop_end(RID p_sample) const; - - - /* VOICE API */ - - virtual RID voice_create(); - - virtual void voice_play(RID p_voice, RID p_sample); - - virtual void voice_set_volume(RID p_voice, float p_volume); - virtual void voice_set_pan(RID p_voice, float p_pan, float p_depth=0,float height=0); //pan and depth go from -1 to 1 - virtual void voice_set_filter(RID p_voice, FilterType p_type, float p_cutoff, float p_resonance, float p_gain=0); - virtual void voice_set_chorus(RID p_voice, float p_chorus ); - virtual void voice_set_reverb(RID p_voice, ReverbRoomType p_room_type, float p_reverb); - virtual void voice_set_mix_rate(RID p_voice, int p_mix_rate); - virtual void voice_set_positional(RID p_voice, bool p_positional); - - virtual float voice_get_volume(RID p_voice) const; - virtual float voice_get_pan(RID p_voice) const; //pan and depth go from -1 to 1 - virtual float voice_get_pan_depth(RID p_voice) const; //pan and depth go from -1 to 1 - virtual float voice_get_pan_height(RID p_voice) const; //pan and depth go from -1 to 1 - virtual FilterType voice_get_filter_type(RID p_voice) const; - virtual float voice_get_filter_cutoff(RID p_voice) const; - virtual float voice_get_filter_resonance(RID p_voice) const; - virtual float voice_get_chorus(RID p_voice) const; - virtual ReverbRoomType voice_get_reverb_type(RID p_voice) const; - virtual float voice_get_reverb(RID p_voice) const; - - virtual int voice_get_mix_rate(RID p_voice) const; - virtual bool voice_is_positional(RID p_voice) const; - - virtual void voice_stop(RID p_voice); - virtual bool voice_is_active(RID p_voice) const; - - /* STREAM API */ - - virtual RID audio_stream_create(AudioStream *p_stream); - virtual RID event_stream_create(EventStream *p_stream); - - virtual void stream_set_active(RID p_stream, bool p_active); - virtual bool stream_is_active(RID p_stream) const; - - virtual void stream_set_volume_scale(RID p_stream, float p_scale); - virtual float stream_set_volume_scale(RID p_stream) const; - - /* Audio Physics API */ - - virtual void free(RID p_id); - - virtual void init(); - virtual void finish(); - virtual void update(); - - /* MISC config */ - - virtual void lock(); - virtual void unlock(); - virtual int get_default_channel_count() const; - virtual int get_default_mix_rate() const; - - virtual void set_stream_global_volume_scale(float p_volume); - virtual void set_fx_global_volume_scale(float p_volume); - virtual void set_event_voice_global_volume_scale(float p_volume); - - virtual float get_stream_global_volume_scale() const; - virtual float get_fx_global_volume_scale() const; - virtual float get_event_voice_global_volume_scale() const; - - virtual uint32_t read_output_peak() const; - - static AudioServer *get_singleton(); - - virtual double get_mix_time() const; //useful for video -> audio sync - virtual double get_output_delay() const; - - - AudioServerJavascript(); -}; - -#endif // AUDIO_SERVER_JAVASCRIPT_H -#endif diff --git a/platform/javascript/detect.py b/platform/javascript/detect.py index cc29ad8956..8472c3ccab 100644 --- a/platform/javascript/detect.py +++ b/platform/javascript/detect.py @@ -13,13 +13,12 @@ def get_name(): def can_build(): - return ("EMSCRIPTEN_ROOT" in os.environ) + return ("EMSCRIPTEN_ROOT" in os.environ or "EMSCRIPTEN" in os.environ) def get_opts(): from SCons.Variables import BoolVariable return [ - BoolVariable('wasm', 'Compile to WebAssembly', False), BoolVariable('javascript_eval', 'Enable JavaScript eval interface', True), ] @@ -66,7 +65,10 @@ def configure(env): ## Compiler configuration env['ENV'] = os.environ - env.PrependENVPath('PATH', os.environ['EMSCRIPTEN_ROOT']) + if ("EMSCRIPTEN_ROOT" in os.environ): + env.PrependENVPath('PATH', os.environ['EMSCRIPTEN_ROOT']) + elif ("EMSCRIPTEN" in os.environ): + env.PrependENVPath('PATH', os.environ['EMSCRIPTEN']) env['CC'] = 'emcc' env['CXX'] = 'em++' env['LINK'] = 'emcc' @@ -100,20 +102,13 @@ def configure(env): ## Link flags - env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"']) + env.Append(LINKFLAGS=['-s', 'BINARYEN=1']) + env.Append(LINKFLAGS=['-s', 'ALLOW_MEMORY_GROWTH=1']) env.Append(LINKFLAGS=['-s', 'USE_WEBGL2=1']) + env.Append(LINKFLAGS=['-s', 'EXTRA_EXPORTED_RUNTIME_METHODS="[\'FS\']"']) - if env['wasm']: - env.Append(LINKFLAGS=['-s', 'BINARYEN=1']) - # In contrast to asm.js, enabling memory growth on WebAssembly has no - # major performance impact, and causes only a negligible increase in - # memory size. - env.Append(LINKFLAGS=['-s', 'ALLOW_MEMORY_GROWTH=1']) - env.extra_suffix = '.webassembly' + env.extra_suffix - else: - env.Append(LINKFLAGS=['-s', 'ASM_JS=1']) - env.Append(LINKFLAGS=['--separate-asm']) - env.Append(LINKFLAGS=['--memory-init-file', '1']) + env.Append(LINKFLAGS=['-s', 'INVOKE_RUN=0']) + env.Append(LINKFLAGS=['-s', 'NO_EXIT_RUNTIME=1']) # TODO: Move that to opus module's config if 'module_opus_enabled' in env and env['module_opus_enabled']: diff --git a/platform/javascript/engine.js b/platform/javascript/engine.js index 99d1c20bbd..dc4bdc7efb 100644 --- a/platform/javascript/engine.js +++ b/platform/javascript/engine.js @@ -5,7 +5,6 @@ (function() { var engine = Engine; - var USING_WASM = engine.USING_WASM; var DOWNLOAD_ATTEMPTS_MAX = 4; var basePath = null; @@ -32,87 +31,101 @@ this.rtenv = null; - var gameInitPromise = null; + var initPromise = null; var unloadAfterInit = true; - var memorySize = 268435456; + var preloadedFiles = []; + + var resizeCanvasOnStart = true; var progressFunc = null; - var pckProgressTracker = {}; + var preloadProgressTracker = {}; var lastProgress = { loaded: 0, total: 0 }; var canvas = null; + var executableName = null; + var locale = null; var stdout = null; var stderr = null; - this.initGame = function(mainPack) { - - if (!gameInitPromise) { + this.init = function(newBasePath) { - if (mainPack === undefined) { - if (basePath !== null) { - mainPack = basePath + '.pck'; - } else { - return Promise.reject(new Error("No main pack to load specified")); - } - } - if (basePath === null) - basePath = getBasePath(mainPack); - - gameInitPromise = Engine.initEngine().then( + if (!initPromise) { + initPromise = Engine.load(newBasePath).then( instantiate.bind(this) ); - var gameLoadPromise = loadPromise(mainPack, pckProgressTracker).then(function(xhr) { return xhr.response; }); - gameInitPromise = Promise.all([gameLoadPromise, gameInitPromise]).then(function(values) { - // resolve with pck - return new Uint8Array(values[0]); - }); - if (unloadAfterInit) - gameInitPromise.then(Engine.unloadEngine); requestAnimationFrame(animateProgress); + if (unloadAfterInit) + initPromise.then(Engine.unloadEngine); } - return gameInitPromise; + return initPromise; }; - function instantiate(initializer) { + function instantiate(wasmBuf) { - var rtenvOpts = { - noInitialRun: true, - thisProgram: getBaseName(basePath), + var rtenvProps = { engine: this, + ENV: {}, }; if (typeof stdout === 'function') - rtenvOpts.print = stdout; + rtenvProps.print = stdout; if (typeof stderr === 'function') - rtenvOpts.printErr = stderr; - if (typeof WebAssembly === 'object' && initializer instanceof ArrayBuffer) { - rtenvOpts.instantiateWasm = function(imports, onSuccess) { - WebAssembly.instantiate(initializer, imports).then(function(result) { - onSuccess(result.instance); - }); - return {}; - }; - } else if (initializer.asm && initializer.mem) { - rtenvOpts.asm = initializer.asm; - rtenvOpts.memoryInitializerRequest = initializer.mem; - rtenvOpts.TOTAL_MEMORY = memorySize; - } else { - throw new Error("Invalid initializer"); - } + rtenvProps.printErr = stderr; + rtenvProps.instantiateWasm = function(imports, onSuccess) { + WebAssembly.instantiate(wasmBuf, imports).then(function(result) { + onSuccess(result.instance); + }); + return {}; + }; return new Promise(function(resolve, reject) { - rtenvOpts.onRuntimeInitialized = resolve; - rtenvOpts.onAbort = reject; - rtenvOpts.engine.rtenv = Engine.RuntimeEnvironment(rtenvOpts); + rtenvProps.onRuntimeInitialized = resolve; + rtenvProps.onAbort = reject; + rtenvProps.engine.rtenv = Engine.RuntimeEnvironment(rtenvProps); }); } - this.start = function(mainPack) { + this.preloadFile = function(pathOrBuffer, bufferFilename) { + + if (pathOrBuffer instanceof ArrayBuffer) { + pathOrBuffer = new Uint8Array(pathOrBuffer); + } else if (ArrayBuffer.isView(pathOrBuffer)) { + pathOrBuffer = new Uint8Array(pathOrBuffer.buffer); + } + if (pathOrBuffer instanceof Uint8Array) { + preloadedFiles.push({ + name: bufferFilename, + buffer: pathOrBuffer + }); + return Promise.resolve(); + } else if (typeof pathOrBuffer === 'string') { + return loadPromise(pathOrBuffer, preloadProgressTracker).then(function(xhr) { + preloadedFiles.push({ + name: pathOrBuffer, + buffer: xhr.response + }); + }); + } else { + throw Promise.reject("Invalid object for preloading"); + } + }; + + this.start = function() { + + return this.init().then( + Function.prototype.apply.bind(synchronousStart, this, arguments) + ); + }; + + this.startGame = function(mainPack) { - return this.initGame(mainPack).then(synchronousStart.bind(this)); + executableName = getBaseName(mainPack); + return Promise.all([this.init(getBasePath(mainPack)), this.preloadFile(mainPack)]).then( + Function.prototype.apply.bind(synchronousStart, this, []) + ); }; - function synchronousStart(pckView) { - // TODO don't expect canvas when runninng as cli tool + function synchronousStart() { + if (canvas instanceof HTMLCanvasElement) { this.rtenv.canvas = canvas; } else { @@ -147,15 +160,33 @@ ev.preventDefault(); }, false); - this.rtenv.FS.createDataFile('/', this.rtenv.thisProgram + '.pck', pckView, true, true, true); - gameInitPromise = null; - this.rtenv.callMain(); + if (locale) { + this.rtenv.locale = locale; + } else { + this.rtenv.locale = navigator.languages ? navigator.languages[0] : navigator.language; + } + this.rtenv.locale = this.rtenv.locale.split('.')[0]; + this.rtenv.resizeCanvasOnStart = resizeCanvasOnStart; + + this.rtenv.thisProgram = executableName || getBaseName(basePath); + + preloadedFiles.forEach(function(file) { + this.rtenv.FS.createDataFile('/', file.name, new Uint8Array(file.buffer), true, true, true); + }, this); + + preloadedFiles = null; + initPromise = null; + this.rtenv.callMain(arguments); } this.setProgressFunc = function(func) { progressFunc = func; }; + this.setResizeCanvasOnStart = function(enabled) { + resizeCanvasOnStart = enabled; + }; + function animateProgress() { var loaded = 0; @@ -163,7 +194,7 @@ var totalIsValid = true; var progressIsFinal = true; - [loadingFiles, pckProgressTracker].forEach(function(tracker) { + [loadingFiles, preloadProgressTracker].forEach(function(tracker) { Object.keys(tracker).forEach(function(file) { if (!tracker[file].final) progressIsFinal = false; @@ -190,14 +221,20 @@ canvas = elem; }; - this.setAsmjsMemorySize = function(size) { - memorySize = size; + this.setExecutableName = function(newName) { + + executableName = newName; + }; + + this.setLocale = function(newLocale) { + + locale = newLocale; }; this.setUnloadAfterInit = function(enabled) { - if (enabled && !unloadAfterInit && gameInitPromise) { - gameInitPromise.then(Engine.unloadEngine); + if (enabled && !unloadAfterInit && initPromise) { + initPromise.then(Engine.unloadEngine); } unloadAfterInit = enabled; }; @@ -232,26 +269,16 @@ Engine.RuntimeEnvironment = engine.RuntimeEnvironment; - Engine.initEngine = function(newBasePath) { + Engine.load = function(newBasePath) { if (newBasePath !== undefined) basePath = getBasePath(newBasePath); if (engineLoadPromise === null) { - if (USING_WASM) { - if (typeof WebAssembly !== 'object') - return Promise.reject(new Error("Browser doesn't support WebAssembly")); - // TODO cache/retrieve module to/from idb - engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) { - return xhr.response; - }); - } else { - var asmjsPromise = loadPromise(basePath + '.asm.js').then(function(xhr) { - return asmjsModulePromise(xhr.response); - }); - var memPromise = loadPromise(basePath + '.mem'); - engineLoadPromise = Promise.all([asmjsPromise, memPromise]).then(function(values) { - return { asm: values[0], mem: values[1] }; - }); - } + if (typeof WebAssembly !== 'object') + return Promise.reject(new Error("Browser doesn't support WebAssembly")); + // TODO cache/retrieve module to/from idb + engineLoadPromise = loadPromise(basePath + '.wasm').then(function(xhr) { + return xhr.response; + }); engineLoadPromise = engineLoadPromise.catch(function(err) { engineLoadPromise = null; throw err; @@ -260,34 +287,7 @@ return engineLoadPromise; }; - function asmjsModulePromise(module) { - var elem = document.createElement('script'); - var script = new Blob([ - 'Engine.asm = (function() { var Module = {};', - module, - 'return Module.asm; })();' - ]); - var url = URL.createObjectURL(script); - elem.src = url; - return new Promise(function(resolve, reject) { - elem.addEventListener('load', function() { - URL.revokeObjectURL(url); - var asm = Engine.asm; - Engine.asm = undefined; - setTimeout(function() { - // delay to reclaim compilation memory - resolve(asm); - }, 1); - }); - elem.addEventListener('error', function() { - URL.revokeObjectURL(url); - reject("asm.js faiilure"); - }); - document.body.appendChild(elem); - }); - } - - Engine.unloadEngine = function() { + Engine.unload = function() { engineLoadPromise = null; }; @@ -306,7 +306,7 @@ if (!file.endsWith('.js')) { xhr.responseType = 'arraybuffer'; } - ['loadstart', 'progress', 'load', 'error', 'timeout', 'abort'].forEach(function(ev) { + ['loadstart', 'progress', 'load', 'error', 'abort'].forEach(function(ev) { xhr.addEventListener(ev, onXHREvent.bind(xhr, resolve, reject, file, tracker)); }); xhr.send(); @@ -321,7 +321,7 @@ this.abort(); return; } else { - loadXHR(resolve, reject, file); + setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000); } } @@ -348,12 +348,11 @@ break; case 'error': - case 'timeout': if (++tracker[file].attempts >= DOWNLOAD_ATTEMPTS_MAX) { tracker[file].final = true; reject(new Error("Failed loading file '" + file + "'")); } else { - loadXHR(resolve, reject, file); + setTimeout(loadXHR.bind(null, resolve, reject, file, tracker), 1000); } break; diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 4a97bf4c32..05b0fb3fbc 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -30,13 +30,12 @@ #include "editor/editor_node.h" #include "editor_export.h" #include "io/zip_io.h" +#include "main/splash.gen.h" #include "platform/javascript/logo.gen.h" #include "platform/javascript/run_icon.gen.h" #define EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE "webassembly_release.zip" #define EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG "webassembly_debug.zip" -#define EXPORT_TEMPLATE_ASMJS_RELEASE "javascript_release.zip" -#define EXPORT_TEMPLATE_ASMJS_DEBUG "javascript_debug.zip" class EditorExportPlatformJavaScript : public EditorExportPlatform { @@ -47,18 +46,11 @@ class EditorExportPlatformJavaScript : public EditorExportPlatform { bool runnable_when_last_polled; void _fix_html(Vector<uint8_t> &p_html, const Ref<EditorExportPreset> &p_preset, const String &p_name, bool p_debug); - void _fix_fsloader_js(Vector<uint8_t> &p_js, const String &p_pack_name, uint64_t p_pack_size); public: - enum Target { - TARGET_WEBASSEMBLY, - TARGET_ASMJS - }; - virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features); virtual void get_export_options(List<ExportOption> *r_options); - virtual bool get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const; virtual String get_name() const; virtual String get_os_name() const; @@ -90,17 +82,9 @@ void EditorExportPlatformJavaScript::_fix_html(Vector<uint8_t> &p_html, const Re String str_export; Vector<String> lines = str_template.split("\n"); - int memory_mb; - if (p_preset->get("options/target").operator int() != TARGET_ASMJS) - // WebAssembly allows memory growth, so start with a reasonable default - memory_mb = 1 << 4; - else - memory_mb = 1 << (p_preset->get("options/memory_size").operator int() + 5); - for (int i = 0; i < lines.size(); i++) { String current_line = lines[i]; - current_line = current_line.replace("$GODOT_TOTAL_MEMORY", itos(memory_mb * 1024 * 1024)); current_line = current_line.replace("$GODOT_BASENAME", p_name); current_line = current_line.replace("$GODOT_HEAD_INCLUDE", p_preset->get("html/head_include")); current_line = current_line.replace("$GODOT_DEBUG_ENABLED", p_debug ? "true" : "false"); @@ -129,24 +113,15 @@ void EditorExportPlatformJavaScript::get_preset_features(const Ref<EditorExportP void EditorExportPlatformJavaScript::get_export_options(List<ExportOption> *r_options) { - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "options/target", PROPERTY_HINT_ENUM, "WebAssembly,asm.js"), TARGET_WEBASSEMBLY)); - r_options->push_back(ExportOption(PropertyInfo(Variant::INT, "options/memory_size", PROPERTY_HINT_ENUM, "32 MB,64 MB,128 MB,256 MB,512 MB,1 GB"), 3)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/s3tc"), false)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc"), true)); r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "texture_format/etc2"), false)); + r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/custom_html_shell", PROPERTY_HINT_GLOBAL_FILE, "html"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "html/head_include", PROPERTY_HINT_MULTILINE_TEXT), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "zip"), "")); r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "zip"), "")); } -bool EditorExportPlatformJavaScript::get_option_visibility(const String &p_option, const Map<StringName, Variant> &p_options) const { - - if (p_option == "options/memory_size") { - return p_options["options/target"].operator int() == TARGET_ASMJS; - } - return true; -} - String EditorExportPlatformJavaScript::get_name() const { return "HTML5"; @@ -166,17 +141,10 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p r_missing_templates = false; - if (p_preset->get("options/target").operator int() == TARGET_WEBASSEMBLY) { - if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE) == String()) - r_missing_templates = true; - else if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG) == String()) - r_missing_templates = true; - } else { - if (find_export_template(EXPORT_TEMPLATE_ASMJS_RELEASE) == String()) - r_missing_templates = true; - else if (find_export_template(EXPORT_TEMPLATE_ASMJS_DEBUG) == String()) - r_missing_templates = true; - } + if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE) == String()) + r_missing_templates = true; + else if (find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG) == String()) + r_missing_templates = true; return !r_missing_templates; } @@ -187,9 +155,11 @@ String EditorExportPlatformJavaScript::get_binary_extension() const { } Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags) { + ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags); String custom_debug = p_preset->get("custom_template/debug"); String custom_release = p_preset->get("custom_template/release"); + String custom_html = p_preset->get("html/custom_html_shell"); String template_path = p_debug ? custom_debug : custom_release; @@ -197,17 +167,10 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese if (template_path == String()) { - if (p_preset->get("options/target").operator int() == TARGET_WEBASSEMBLY) { - if (p_debug) - template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG); - else - template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE); - } else { - if (p_debug) - template_path = find_export_template(EXPORT_TEMPLATE_ASMJS_DEBUG); - else - template_path = find_export_template(EXPORT_TEMPLATE_ASMJS_RELEASE); - } + if (p_debug) + template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_DEBUG); + else + template_path = find_export_template(EXPORT_TEMPLATE_WEBASSEMBLY_RELEASE); } if (template_path != String() && !FileAccess::exists(template_path)) { @@ -222,14 +185,6 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese return error; } - FileAccess *f = FileAccess::open(pck_path, FileAccess::READ); - if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not read file:\n") + pck_path); - return ERR_FILE_CANT_READ; - } - size_t pack_size = f->get_len(); - memdelete(f); - FileAccess *src_f = NULL; zlib_filefunc_def io = zipio_create_io_from_file(&src_f); unzFile pkg = unzOpen2(template_path.utf8().get_data(), &io); @@ -240,13 +195,17 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese return ERR_FILE_NOT_FOUND; } - int ret = unzGoToFirstFile(pkg); - while (ret == UNZ_OK) { + if (unzGoToFirstFile(pkg) != UNZ_OK) { + EditorNode::get_singleton()->show_warning(TTR("Invalid export template:\n") + template_path); + unzClose(pkg); + return ERR_FILE_CORRUPT; + } + do { //get filename unz_file_info info; char fname[16384]; - ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0); + unzGetCurrentFileInfo(pkg, &info, fname, 16384, NULL, 0, NULL, 0); String file = fname; @@ -262,20 +221,18 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese if (file == "godot.html") { + if (!custom_html.empty()) { + continue; + } _fix_html(data, p_preset, p_path.get_file().get_basename(), p_debug); file = p_path.get_file(); + } else if (file == "godot.js") { file = p_path.get_file().get_basename() + ".js"; } else if (file == "godot.wasm") { file = p_path.get_file().get_basename() + ".wasm"; - } else if (file == "godot.asm.js") { - - file = p_path.get_file().get_basename() + ".asm.js"; - } else if (file == "godot.mem") { - - file = p_path.get_file().get_basename() + ".mem"; } String dst = p_path.get_base_dir().plus_file(file); @@ -288,9 +245,50 @@ Error EditorExportPlatformJavaScript::export_project(const Ref<EditorExportPrese f->store_buffer(data.ptr(), data.size()); memdelete(f); - ret = unzGoToNextFile(pkg); + } while (unzGoToNextFile(pkg) == UNZ_OK); + unzClose(pkg); + + if (!custom_html.empty()) { + + FileAccess *f = FileAccess::open(custom_html, FileAccess::READ); + if (!f) { + 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.ptr(), buf.size()); + memdelete(f); + _fix_html(buf, p_preset, p_path.get_file().get_basename(), p_debug); + + 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()); + memdelete(f); } + Ref<Image> splash; + String splash_path = GLOBAL_GET("application/boot_splash/image"); + splash_path = splash_path.strip_edges(); + if (!splash_path.empty()) { + splash.instance(); + Error err = splash->load(splash_path); + if (err) { + EditorNode::get_singleton()->show_warning(TTR("Could not read boot splash image file:\n") + splash_path + "\nUsing default boot splash image"); + splash.unref(); + } + } + if (splash.is_null()) { + splash = Ref<Image>(memnew(Image(boot_splash_png))); + } + String png_path = p_path.get_base_dir().plus_file(p_path.get_file().get_basename() + ".png"); + if (splash->save_png(png_path) != OK) { + EditorNode::get_singleton()->show_warning(TTR("Could not write file:\n") + png_path); + return ERR_FILE_CANT_WRITE; + } return OK; } @@ -319,7 +317,7 @@ int EditorExportPlatformJavaScript::get_device_count() const { Error EditorExportPlatformJavaScript::run(const Ref<EditorExportPreset> &p_preset, int p_device, int p_debug_flags) { - String path = EditorSettings::get_singleton()->get_settings_path() + "/tmp/tmp_export.html"; + String path = EditorSettings::get_singleton()->get_cache_dir().plus_file("tmp_export.html"); Error err = export_project(p_preset, true, path, p_debug_flags); if (err) { return err; diff --git a/platform/javascript/http_client.h.inc b/platform/javascript/http_client.h.inc new file mode 100644 index 0000000000..9e4edf7848 --- /dev/null +++ b/platform/javascript/http_client.h.inc @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* http_client.h.inc */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ + +// HTTPClient's additional private members in the javascript platform + +Error prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers); + +int xhr_id; +int read_limit; +int response_read_offset; +Status status; + +String host; +int port; +bool use_tls; +String username; +String password; + +int polled_response_code; +String polled_response_header; +PoolByteArray polled_response; diff --git a/platform/javascript/http_client_javascript.cpp b/platform/javascript/http_client_javascript.cpp new file mode 100644 index 0000000000..0b105dcb40 --- /dev/null +++ b/platform/javascript/http_client_javascript.cpp @@ -0,0 +1,282 @@ +/*************************************************************************/ +/* http_client_javascript.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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. */ +/*************************************************************************/ +#include "http_request.h" +#include "io/http_client.h" + +Error HTTPClient::connect_to_host(const String &p_host, int p_port, bool p_ssl, bool p_verify_host) { + + close(); + if (p_ssl && !p_verify_host) { + WARN_PRINT("Disabling HTTPClient's host verification is not supported for the HTML5 platform, host will be verified"); + } + + host = p_host; + if (host.begins_with("http://")) { + host.replace_first("http://", ""); + } else if (host.begins_with("https://")) { + host.replace_first("https://", ""); + } + + status = host.is_valid_ip_address() ? STATUS_CONNECTING : STATUS_RESOLVING; + port = p_port; + use_tls = p_ssl; + return OK; +} + +void HTTPClient::set_connection(const Ref<StreamPeer> &p_connection) { + + ERR_EXPLAIN("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform"); + ERR_FAIL(); +} + +Ref<StreamPeer> HTTPClient::get_connection() const { + + ERR_EXPLAIN("Accessing an HTTPClient's StreamPeer is not supported for the HTML5 platform"); + ERR_FAIL_V(REF()); +} + +Error HTTPClient::prepare_request(Method p_method, const String &p_url, const Vector<String> &p_headers) { + + ERR_FAIL_INDEX_V(p_method, METHOD_MAX, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(status != STATUS_CONNECTED, ERR_INVALID_PARAMETER); + ERR_FAIL_COND_V(host.empty(), ERR_UNCONFIGURED); + ERR_FAIL_COND_V(port < 0, ERR_UNCONFIGURED); + + static const char *_methods[HTTPClient::METHOD_MAX] = { + "GET", + "HEAD", + "POST", + "PUT", + "DELETE", + "OPTIONS", + "TRACE", + "CONNECT" + }; + + 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.empty() ? NULL : username.utf8().get_data(), + password.empty() ? NULL : password.utf8().get_data()); + + 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()); + } + response_read_offset = 0; + status = STATUS_REQUESTING; + return OK; +} + +Error HTTPClient::request_raw(Method p_method, const String &p_url, const Vector<String> &p_headers, const PoolVector<uint8_t> &p_body) { + + Error err = prepare_request(p_method, p_url, p_headers); + if (err != OK) + return err; + PoolByteArray::Read read = p_body.read(); + godot_xhr_send_data(xhr_id, read.ptr(), p_body.size()); + return OK; +} + +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; + godot_xhr_send_string(xhr_id, p_body.utf8().get_data()); + return OK; +} + +void HTTPClient::close() { + + host = ""; + 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); +} + +HTTPClient::Status HTTPClient::get_status() const { + + return status; +} + +bool HTTPClient::has_response() const { + + return !polled_response_header.empty(); +} + +bool HTTPClient::is_response_chunked() const { + + // TODO evaluate using moz-chunked-arraybuffer, fetch & ReadableStream + return false; +} + +int HTTPClient::get_response_code() const { + + return polled_response_code; +} + +Error HTTPClient::get_response_headers(List<String> *r_response) { + + if (!polled_response_header.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(); + return OK; +} + +int HTTPClient::get_response_body_length() const { + + return polled_response.size(); +} + +PoolByteArray HTTPClient::read_response_body_chunk() { + + ERR_FAIL_COND_V(status != STATUS_BODY, PoolByteArray()); + + int to_read = MIN(read_limit, polled_response.size() - response_read_offset); + PoolByteArray chunk; + chunk.resize(to_read); + PoolByteArray::Write write = chunk.write(); + PoolByteArray::Read read = polled_response.read(); + memcpy(write.ptr(), read.ptr() + response_read_offset, to_read); + write = PoolByteArray::Write(); + read = PoolByteArray::Read(); + response_read_offset += to_read; + + if (response_read_offset == polled_response.size()) { + status = STATUS_CONNECTED; + polled_response.resize(0); + polled_response_code = 0; + polled_response_header = String(); + godot_xhr_reset(xhr_id); + } + + return chunk; +} + +void HTTPClient::set_blocking_mode(bool p_enable) { + + ERR_EXPLAIN("HTTPClient blocking mode is not supported for the HTML5 platform"); + ERR_FAIL_COND(p_enable); +} + +bool HTTPClient::is_blocking_mode_enabled() const { + + return false; +} + +void HTTPClient::set_read_chunk_size(int p_size) { + + read_limit = p_size; +} + +Error HTTPClient::poll() { + + switch (status) { + + case STATUS_DISCONNECTED: + return ERR_UNCONFIGURED; + + case STATUS_RESOLVING: + status = STATUS_CONNECTING; + return OK; + + case STATUS_CONNECTING: + status = STATUS_CONNECTED; + return OK; + + case STATUS_CONNECTED: + case STATUS_BODY: + return OK; + + case STATUS_CONNECTION_ERROR: + return ERR_CONNECTION_ERROR; + + case STATUS_REQUESTING: + polled_response_code = godot_xhr_get_status(xhr_id); + int response_length = godot_xhr_get_response_length(xhr_id); + if (response_length == 0) { + godot_xhr_ready_state_t ready_state = godot_xhr_get_ready_state(xhr_id); + if (ready_state == XHR_READY_STATE_HEADERS_RECEIVED || ready_state == XHR_READY_STATE_LOADING) { + return OK; + } else { + status = STATUS_CONNECTION_ERROR; + return ERR_CONNECTION_ERROR; + } + } + + status = STATUS_BODY; + + PoolByteArray bytes; + int len = godot_xhr_get_response_headers_length(xhr_id); + bytes.resize(len); + PoolByteArray::Write write = bytes.write(); + godot_xhr_get_response_headers(xhr_id, reinterpret_cast<char *>(write.ptr()), len); + write = PoolByteArray::Write(); + + PoolByteArray::Read read = bytes.read(); + polled_response_header = String::utf8(reinterpret_cast<const char *>(read.ptr())); + read = PoolByteArray::Read(); + + polled_response.resize(response_length); + write = polled_response.write(); + godot_xhr_get_response(xhr_id, write.ptr(), response_length); + write = PoolByteArray::Write(); + break; + } + return OK; +} + +HTTPClient::HTTPClient() { + + xhr_id = godot_xhr_new(); + read_limit = 4096; + status = STATUS_DISCONNECTED; + port = -1; + use_tls = false; + polled_response_code = 0; +} + +HTTPClient::~HTTPClient() { + + godot_xhr_free(xhr_id); +} diff --git a/platform/javascript/http_request.h b/platform/javascript/http_request.h new file mode 100644 index 0000000000..06d9239004 --- /dev/null +++ b/platform/javascript/http_request.h @@ -0,0 +1,74 @@ +/*************************************************************************/ +/* http_request.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 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 bool 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 = NULL, const char *p_password = NULL); + +extern void godot_xhr_set_request_header(int p_xhr_id, const char *p_header, const char *p_value); + +extern void godot_xhr_send_null(int p_xhr_id); +extern void godot_xhr_send_string(int p_xhr_id, const char *p_data); +extern void godot_xhr_send_data(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 */ diff --git a/platform/javascript/http_request.js b/platform/javascript/http_request.js new file mode 100644 index 0000000000..f30240b41b --- /dev/null +++ b/platform/javascript/http_request.js @@ -0,0 +1,145 @@ +/*************************************************************************/ +/* http_request.js */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* http://www.godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* */ +/* 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. */ +/*************************************************************************/ +var GodotHTTPRequest = { + + $GodotHTTPRequest: { + + requests: [], + + getUnusedRequestId: function() { + var idMax = GodotHTTPRequest.requests.length; + for (var 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: function() { + var newId = GodotHTTPRequest.getUnusedRequestId(); + GodotHTTPRequest.requests[newId] = new XMLHttpRequest; + GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[newId]); + return newId; + }, + + godot_xhr_reset: function(xhrId) { + GodotHTTPRequest.requests[xhrId] = new XMLHttpRequest; + GodotHTTPRequest.setupRequest(GodotHTTPRequest.requests[xhrId]); + }, + + godot_xhr_free: function(xhrId) { + GodotHTTPRequest.requests[xhrId].abort(); + GodotHTTPRequest.requests[xhrId] = null; + }, + + godot_xhr_open: function(xhrId, method, url, user, password) { + user = user > 0 ? UTF8ToString(user) : null; + password = password > 0 ? UTF8ToString(password) : null; + GodotHTTPRequest.requests[xhrId].open(UTF8ToString(method), UTF8ToString(url), true, user, password); + }, + + godot_xhr_set_request_header: function(xhrId, header, value) { + GodotHTTPRequest.requests[xhrId].setRequestHeader(UTF8ToString(header), UTF8ToString(value)); + }, + + godot_xhr_send_null: function(xhrId) { + GodotHTTPRequest.requests[xhrId].send(); + }, + + godot_xhr_send_string: function(xhrId, strPtr) { + if (!strPtr) { + Module.printErr("Failed to send string per XHR: null pointer"); + return; + } + GodotHTTPRequest.requests[xhrId].send(UTF8ToString(strPtr)); + }, + + godot_xhr_send_data: function(xhrId, ptr, len) { + if (!ptr) { + Module.printErr("Failed to send data per XHR: null pointer"); + return; + } + if (len < 0) { + Module.printErr("Failed to send data per XHR: buffer length less than 0"); + return; + } + GodotHTTPRequest.requests[xhrId].send(HEAPU8.subarray(ptr, ptr + len)); + }, + + godot_xhr_abort: function(xhrId) { + GodotHTTPRequest.requests[xhrId].abort(); + }, + + godot_xhr_get_status: function(xhrId) { + return GodotHTTPRequest.requests[xhrId].status; + }, + + godot_xhr_get_ready_state: function(xhrId) { + return GodotHTTPRequest.requests[xhrId].readyState; + }, + + godot_xhr_get_response_headers_length: function(xhrId) { + var headers = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); + return headers === null ? 0 : lengthBytesUTF8(headers); + }, + + godot_xhr_get_response_headers: function(xhrId, dst, len) { + var str = GodotHTTPRequest.requests[xhrId].getAllResponseHeaders(); + if (str === null) + return; + var buf = new Uint8Array(len + 1); + stringToUTF8Array(str, buf, 0, buf.length); + buf = buf.subarray(0, -1); + HEAPU8.set(buf, dst); + }, + + godot_xhr_get_response_length: function(xhrId) { + var body = GodotHTTPRequest.requests[xhrId].response; + return body === null ? 0 : body.byteLength; + }, + + godot_xhr_get_response: function(xhrId, dst, len) { + var 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); diff --git a/platform/javascript/javascript_eval.cpp b/platform/javascript/javascript_eval.cpp index 74f8d80a76..a755dcb5c4 100644 --- a/platform/javascript/javascript_eval.cpp +++ b/platform/javascript/javascript_eval.cpp @@ -29,34 +29,44 @@ /*************************************************************************/ #ifdef JAVASCRIPT_EVAL_ENABLED -#include "javascript_eval.h" +#include "api/javascript_eval.h" #include "emscripten.h" -JavaScript *JavaScript::singleton = NULL; +extern "C" EMSCRIPTEN_KEEPALIVE uint8_t *resize_poolbytearray_and_open_write(PoolByteArray *p_arr, PoolByteArray::Write *r_write, int p_len) { -JavaScript *JavaScript::get_singleton() { - - return singleton; + p_arr->resize(p_len); + *r_write = p_arr->write(); + return r_write->ptr(); } Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { union { - int i; + bool b; double d; char *s; } js_data[4]; + + PoolByteArray arr; + PoolByteArray::Write arr_write; + /* clang-format off */ Variant::Type return_type = static_cast<Variant::Type>(EM_ASM_INT({ + const CODE = $0; + const USE_GLOBAL_EXEC_CONTEXT = $1; + const PTR = $2; + const ELEM_LEN = $3; + const BYTEARRAY_PTR = $4; + const BYTEARRAY_WRITE_PTR = $5; var eval_ret; try { - if ($3) { // p_use_global_exec_context + if (USE_GLOBAL_EXEC_CONTEXT) { // indirect eval call grants global execution context var global_eval = eval; - eval_ret = global_eval(UTF8ToString($2)); + eval_ret = global_eval(UTF8ToString(CODE)); } else { - eval_ret = eval(UTF8ToString($2)); + eval_ret = eval(UTF8ToString(CODE)); } } catch (e) { Module.printErr(e); @@ -66,16 +76,11 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { switch (typeof eval_ret) { case 'boolean': - // bitwise op yields 32-bit int - setValue($0, eval_ret|0, 'i32'); + setValue(PTR, eval_ret, 'i32'); return 1; // BOOL case 'number': - if ((eval_ret|0)===eval_ret) { - setValue($0, eval_ret|0, 'i32'); - return 2; // INT - } - setValue($0, eval_ret, 'double'); + setValue(PTR, eval_ret, 'double'); return 3; // REAL case 'string': @@ -85,7 +90,7 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { if (array_ptr===0) { throw new Error('String allocation failed (probably out of memory)'); } - setValue($0, array_ptr|0 , '*'); + setValue(PTR, array_ptr , '*'); stringToUTF8(eval_ret, array_ptr, array_len); return 4; // STRING } catch (e) { @@ -102,41 +107,50 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { break; } - else if (typeof eval_ret.x==='number' && typeof eval_ret.y==='number') { - setValue($0, eval_ret.x, 'double'); - setValue($0+$1, eval_ret.y, 'double'); + if (ArrayBuffer.isView(eval_ret) && !(eval_ret instanceof Uint8Array)) { + eval_ret = new Uint8Array(eval_ret.buffer); + } + else if (eval_ret instanceof ArrayBuffer) { + eval_ret = new Uint8Array(eval_ret); + } + if (eval_ret instanceof Uint8Array) { + var bytes_ptr = ccall('resize_poolbytearray_and_open_write', 'number', ['number', 'number' ,'number'], [BYTEARRAY_PTR, BYTEARRAY_WRITE_PTR, eval_ret.length]); + HEAPU8.set(eval_ret, bytes_ptr); + return 20; // POOL_BYTE_ARRAY + } + + if (typeof eval_ret.x==='number' && typeof eval_ret.y==='number') { + setValue(PTR, eval_ret.x, 'double'); + setValue(PTR + ELEM_LEN, eval_ret.y, 'double'); if (typeof eval_ret.z==='number') { - setValue($0+$1*2, eval_ret.z, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.z, 'double'); return 7; // VECTOR3 } else if (typeof eval_ret.width==='number' && typeof eval_ret.height==='number') { - setValue($0+$1*2, eval_ret.width, 'double'); - setValue($0+$1*3, eval_ret.height, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.width, 'double'); + setValue(PTR + ELEM_LEN*3, eval_ret.height, 'double'); return 6; // RECT2 } return 5; // VECTOR2 } - else if (typeof eval_ret.r==='number' && typeof eval_ret.g==='number' && typeof eval_ret.b==='number') { - // assume 8-bit rgb components since we're on the web - setValue($0, eval_ret.r, 'double'); - setValue($0+$1, eval_ret.g, 'double'); - setValue($0+$1*2, eval_ret.b, 'double'); - setValue($0+$1*3, typeof eval_ret.a==='number' ? eval_ret.a : 1, 'double'); + if (typeof eval_ret.r === 'number' && typeof eval_ret.g === 'number' && typeof eval_ret.b === 'number') { + setValue(PTR, eval_ret.r, 'double'); + setValue(PTR + ELEM_LEN, eval_ret.g, 'double'); + setValue(PTR + ELEM_LEN*2, eval_ret.b, 'double'); + setValue(PTR + ELEM_LEN*3, typeof eval_ret.a === 'number' ? eval_ret.a : 1, 'double'); return 14; // COLOR } break; } return 0; // NIL - }, js_data, sizeof *js_data, p_code.utf8().get_data(), p_use_global_exec_context)); + }, p_code.utf8().get_data(), p_use_global_exec_context, js_data, sizeof *js_data, &arr, &arr_write)); /* clang-format on */ switch (return_type) { case Variant::BOOL: - return !!js_data->i; - case Variant::INT: - return js_data->i; + return js_data->b; case Variant::REAL: return js_data->d; case Variant::STRING: { @@ -153,23 +167,12 @@ Variant JavaScript::eval(const String &p_code, bool p_use_global_exec_context) { case Variant::RECT2: return Rect2(js_data[0].d, js_data[1].d, js_data[2].d, js_data[3].d); case Variant::COLOR: - return Color(js_data[0].d / 255., js_data[1].d / 255., js_data[2].d / 255., js_data[3].d); + return Color(js_data[0].d, js_data[1].d, js_data[2].d, js_data[3].d); + case Variant::POOL_BYTE_ARRAY: + arr_write = PoolByteArray::Write(); + return arr; } return Variant(); } -void JavaScript::_bind_methods() { - - ClassDB::bind_method(D_METHOD("eval", "code", "use_global_execution_context"), &JavaScript::eval, false); -} - -JavaScript::JavaScript() { - - ERR_FAIL_COND(singleton != NULL); - singleton = this; -} - -JavaScript::~JavaScript() { -} - #endif // JAVASCRIPT_EVAL_ENABLED diff --git a/platform/javascript/javascript_main.cpp b/platform/javascript/javascript_main.cpp index ed4f416cfd..5c5d608524 100644 --- a/platform/javascript/javascript_main.cpp +++ b/platform/javascript/javascript_main.cpp @@ -61,7 +61,6 @@ int main(int argc, char *argv[]) { // run the 'main_after_fs_sync' function /* clang-format off */ EM_ASM( - Module.noExitRuntime = true; FS.mkdir('/userfs'); FS.mount(IDBFS, {}, '/userfs'); FS.syncfs(true, function(err) { diff --git a/platform/javascript/os_javascript.cpp b/platform/javascript/os_javascript.cpp index f6446e77da..d5c675d9e0 100644 --- a/platform/javascript/os_javascript.cpp +++ b/platform/javascript/os_javascript.cpp @@ -29,8 +29,8 @@ /*************************************************************************/ #include "os_javascript.h" +#include "core/engine.h" #include "core/io/file_access_buffered_fa.h" -#include "core/project_settings.h" #include "dom_keys.h" #include "drivers/gles3/rasterizer_gles3.h" #include "drivers/unix/dir_access_unix.h" @@ -64,11 +64,6 @@ const char *OS_JavaScript::get_video_driver_name(int p_driver) const { return "GLES3"; } -OS::VideoMode OS_JavaScript::get_default_video_mode() const { - - return OS::VideoMode(); -} - int OS_JavaScript::get_audio_driver_count() const { return 1; @@ -85,10 +80,6 @@ void OS_JavaScript::initialize_core() { FileAccess::make_default<FileAccessBufferedFA<FileAccessUnix> >(FileAccess::ACCESS_RESOURCES); } -void OS_JavaScript::initialize_logger() { - _set_logger(memnew(StdLogger)); -} - void OS_JavaScript::set_opengl_extensions(const char *p_gl_extensions) { ERR_FAIL_COND(!p_gl_extensions); @@ -171,14 +162,15 @@ static EM_BOOL _mousebutton_callback(int event_type, const EmscriptenMouseEvent } int mask = _input->get_mouse_button_mask(); + int button_flag = 1 << (ev->get_button_index() - 1); if (ev->is_pressed()) { // since the event is consumed, focus manually if (!is_canvas_focused()) { focus_canvas(); } - mask |= ev->get_button_index(); - } else if (mask & ev->get_button_index()) { - mask &= ~ev->get_button_index(); + mask |= button_flag; + } else if (mask & button_flag) { + mask &= ~button_flag; } else { // release event, but press was outside the canvas, so ignore return false; @@ -442,25 +434,23 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i video_mode = p_desired; // can't fulfil fullscreen request due to browser security video_mode.fullscreen = false; - set_window_size(Size2(p_desired.width, p_desired.height)); + /* clang-format off */ + bool resize_canvas_on_start = EM_ASM_INT_V( + return Module.resizeCanvasOnStart; + ); + /* clang-format on */ + if (resize_canvas_on_start) { + set_window_size(Size2(video_mode.width, video_mode.height)); + } else { + Size2 canvas_size = get_window_size(); + video_mode.width = canvas_size.width; + video_mode.height = canvas_size.height; + } - // find locale, emscripten only sets "C" char locale_ptr[16]; /* clang-format off */ - EM_ASM_({ - var locale = ""; - if (Module.locale) { - // best case: server-side script reads Accept-Language early and - // defines the locale to be read here - locale = Module.locale; - } else { - // no luck, use what the JS engine can tell us - // if this turns out not compatible enough, add tests for - // browserLanguage, systemLanguage and userLanguage - locale = navigator.languages ? navigator.languages[0] : navigator.language; - } - locale = locale.split('.')[0]; - stringToUTF8(locale, $0, 16); + EM_ASM_ARGS({ + stringToUTF8(Module.locale, $0, 16); }, locale_ptr); /* clang-format on */ setenv("LANG", locale_ptr, true); @@ -480,11 +470,6 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i print_line("Init Physicsserver"); - physics_server = memnew(PhysicsServerSW); - physics_server->init(); - physics_2d_server = memnew(Physics2DServerSW); - physics_2d_server->init(); - input = memnew(InputDefault); _input = input; @@ -521,11 +506,6 @@ void OS_JavaScript::initialize(const VideoMode &p_desired, int p_video_driver, i #undef SET_EM_CALLBACK #undef EM_CHECK -#ifdef JAVASCRIPT_EVAL_ENABLED - javascript_eval = memnew(JavaScript); - ProjectSettings::get_singleton()->add_singleton(ProjectSettings::Singleton("JavaScript", javascript_eval)); -#endif - visual_server->init(); } @@ -898,14 +878,13 @@ String OS_JavaScript::get_resource_dir() const { return "/"; //javascript has it's own filesystem for resources inside the APK } -String OS_JavaScript::get_data_dir() const { +String OS_JavaScript::get_user_data_dir() const { /* - if (get_data_dir_func) - return get_data_dir_func(); + if (get_user_data_dir_func) + return get_user_data_dir_func(); */ return "/userfs"; - //return ProjectSettings::get_singleton()->get_singleton_object("GodotOS")->call("get_data_dir"); }; String OS_JavaScript::get_executable_path() const { @@ -1003,7 +982,7 @@ bool OS_JavaScript::is_userfs_persistent() const { return idbfs_available; } -OS_JavaScript::OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func) { +OS_JavaScript::OS_JavaScript(const char *p_execpath, GetUserDataDirFunc p_get_user_data_dir_func) { set_cmdline(p_execpath, get_cmdline_args()); main_loop = NULL; gl_extensions = NULL; @@ -1011,7 +990,7 @@ OS_JavaScript::OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_d soft_fs_enabled = false; canvas_size_adjustment_requested = false; - get_data_dir_func = p_get_data_dir_func; + get_user_data_dir_func = p_get_user_data_dir_func; FileAccessUnix::close_notification_func = _close_notification_funcs; idbfs_available = false; diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h index 1c939d3fd5..a95b069d03 100644 --- a/platform/javascript/os_javascript.h +++ b/platform/javascript/os_javascript.h @@ -31,21 +31,17 @@ #define OS_JAVASCRIPT_H #include "audio_driver_javascript.h" -#include "audio_server_javascript.h" #include "drivers/unix/os_unix.h" -#include "javascript_eval.h" #include "main/input_default.h" #include "os/input.h" #include "os/main_loop.h" #include "power_javascript.h" #include "servers/audio_server.h" -#include "servers/physics/physics_server_sw.h" -#include "servers/physics_2d/physics_2d_server_sw.h" #include "servers/visual/rasterizer.h" #include <emscripten/html5.h> -typedef String (*GetDataDirFunc)(); +typedef String (*GetUserDataDirFunc)(); class OS_JavaScript : public OS_Unix { @@ -54,8 +50,6 @@ class OS_JavaScript : public OS_Unix { int64_t last_sync_time; VisualServer *visual_server; - PhysicsServer *physics_server; - Physics2DServer *physics_2d_server; AudioDriverJavaScript audio_driver_javascript; const char *gl_extensions; @@ -68,14 +62,10 @@ class OS_JavaScript : public OS_Unix { CursorShape cursor_shape; MainLoop *main_loop; - GetDataDirFunc get_data_dir_func; + GetUserDataDirFunc get_user_data_dir_func; PowerJavascript *power_manager; -#ifdef JAVASCRIPT_EVAL_ENABLED - JavaScript *javascript_eval; -#endif - static void _close_notification_funcs(const String &p_file, int p_flags); void process_joypads(); @@ -88,12 +78,9 @@ public: virtual int get_video_driver_count() const; virtual const char *get_video_driver_name(int p_driver) const; - virtual VideoMode get_default_video_mode() const; - virtual int get_audio_driver_count() const; virtual const char *get_audio_driver_name(int p_driver) const; - virtual void initialize_logger(); virtual void initialize_core(); virtual void initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver); @@ -153,7 +140,7 @@ public: void set_opengl_extensions(const char *p_gl_extensions); virtual Error shell_open(String p_uri); - virtual String get_data_dir() const; + virtual String get_user_data_dir() const; String get_executable_path() const; virtual String get_resource_dir() const; @@ -172,7 +159,7 @@ public: void set_idbfs_available(bool p_idbfs_available); - OS_JavaScript(const char *p_execpath, GetDataDirFunc p_get_data_dir_func); + OS_JavaScript(const char *p_execpath, GetUserDataDirFunc p_get_user_data_dir_func); ~OS_JavaScript(); }; diff --git a/platform/javascript/pre_wasm.js b/platform/javascript/pre.js index be4383c8c9..311aa44fda 100644 --- a/platform/javascript/pre_wasm.js +++ b/platform/javascript/pre.js @@ -1,3 +1,2 @@ var Engine = { - USING_WASM: true, RuntimeEnvironment: function(Module) { diff --git a/platform/javascript/pre_asmjs.js b/platform/javascript/pre_asmjs.js deleted file mode 100644 index 3c497721b6..0000000000 --- a/platform/javascript/pre_asmjs.js +++ /dev/null @@ -1,3 +0,0 @@ -var Engine = { - USING_WASM: false, - RuntimeEnvironment: function(Module) { |