Last active
January 16, 2020 22:48
-
-
Save adamjs/f8897e8b764a46de24ff45b03e91f515 to your computer and use it in GitHub Desktop.
Async callback example using JavaScript Promises (Ultralight 1.1)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "MyApp.h" | |
int main() { | |
MyApp app; | |
app.Run(); | |
return 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include "MyApp.h" | |
#include <chrono> | |
#include <thread> | |
#define WINDOW_WIDTH 600 | |
#define WINDOW_HEIGHT 400 | |
const char* htmlString(); | |
MyApp::MyApp() { | |
app_ = App::Create(); | |
window_ = Window::Create(app_->main_monitor(), WINDOW_WIDTH, WINDOW_HEIGHT, | |
false, kWindowFlags_Titled | kWindowFlags_Resizable); | |
window_->SetTitle("MyApp"); | |
app_->set_window(*window_.get()); | |
overlay_ = Overlay::Create(*window_.get(), 1, 1, 0, 0); | |
OnResize(window_->width(), window_->height()); | |
overlay_->view()->LoadHTML(htmlString()); | |
app_->set_listener(this); | |
window_->set_listener(this); | |
overlay_->view()->set_load_listener(this); | |
} | |
MyApp::~MyApp() { | |
} | |
void MyApp::Run() { | |
app_->Run(); | |
} | |
void MyApp::OnUpdate() { | |
while (!task_queue_.empty()) { | |
Task task = task_queue_.pop(); | |
task(); | |
} | |
} | |
void MyApp::OnClose() { | |
} | |
void MyApp::OnResize(uint32_t width, uint32_t height) { | |
overlay_->Resize(width, height); | |
} | |
void MyApp::OnFinishLoading(View* caller) { | |
} | |
void MyApp::OnDOMReady(View* caller) { | |
SetJSContext(caller->js_context()); | |
JSObject global = JSGlobalObject(); | |
global["StartAsyncTask"] = BindJSCallbackWithRetval(&MyApp::StartAsyncTask); | |
} | |
JSValue MyApp::StartAsyncTask(const JSObject& thisObject, const JSArgs& args) { | |
if (args.size() == 3) { | |
JSValue obj = args[0]; | |
JSValue resolve = args[1]; | |
JSValue reject = args[2]; | |
if (resolve.IsFunction()) { | |
// | |
// Create a JSFunction instance on the heap that points to our resolve | |
// callback in JavaScript. We are using JSFunction to keep the underlying | |
// JSObjectRef alive (prevent it from being GC'd) and pass it across threads. | |
// | |
// Note: You should probably use std::unique_ptr<JSFunction> here instead | |
// but C++11 has issues with lambda capture of moves/unique_ptr. | |
// I'm just using a bare pointer here for max compatibility. | |
// | |
JSFunction* resolve_func = new JSFunction(resolve.ToFunction()); | |
// | |
// We are going to call this function later (outside of MyApp::StartAsyncTask) | |
// which means this inner JSContext in this C++ callback will be invalid. | |
// | |
// Update our function's JSContext now to use our View's global JSContext. | |
// | |
resolve_func->set_context(overlay_->view()->js_context()); | |
// | |
// Define our async task and post it on the worker thread. | |
// | |
worker_thread_.PostTask([=] { | |
// You would do work on your child thread here. | |
// Sleep for a quick second just for theatrics. | |
std::this_thread::sleep_for(std::chrono::seconds(1)); | |
// Our worker thread's task is now done, post a task on the main thread | |
task_queue_.push([=] { | |
// | |
// We are now on main thread again (this task should be dispatched | |
// from MyApp::OnUpdate), it is safe to call our JavaScript function. | |
// | |
// Call our resolve function in JavaScript with a little message. | |
// | |
(*resolve_func)({ "Hello from async task!" }); | |
// Make sure to delete our function so we don't leak. | |
delete resolve_func; | |
}); | |
}); | |
} | |
} | |
return JSValue(); | |
} | |
const char* htmlString() { | |
return R"( | |
<html> | |
<head> | |
<style type="text/css"> | |
body { | |
font-family: -apple-system, 'Segoe UI', Ubuntu, Arial, sans-serif; | |
text-align: center; | |
background: linear-gradient(#FFF, #DDD); | |
padding: 2em; | |
} | |
#message { | |
padding-top: 2em; | |
color: black; | |
font-weight: bold; | |
font-size: 24px; | |
} | |
</style> | |
<script type="text/javascript"> | |
function HandleButton(evt) { | |
var obj = { someJson:2 }; | |
return new Promise(function(resolve, reject) { | |
StartAsyncTask(obj, resolve, reject) | |
}).then(function(message) { | |
// Display the result in our 'message' div element | |
document.getElementById('message').innerHTML = message; | |
}); | |
} | |
</script> | |
</head> | |
<body> | |
<button onclick="HandleButton(event);">Start the async task!</button> | |
<div id="message"></div> | |
</body> | |
</html> | |
)"; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <AppCore/AppCore.h> | |
#include "TaskQueue.h" | |
#include "WorkerThread.h" | |
typedef std::function<void()> Task; | |
using namespace ultralight; | |
class MyApp : public AppListener, | |
public WindowListener, | |
public LoadListener { | |
public: | |
MyApp(); | |
virtual ~MyApp(); | |
// Start the run loop. | |
virtual void Run(); | |
// This is called continuously from the app's main loop. Update logic here. | |
virtual void OnUpdate() override; | |
// This is called when the window is closing. | |
virtual void OnClose() override; | |
// This is called whenever the window resizes. | |
virtual void OnResize(uint32_t width, uint32_t height) override; | |
// This is called when the page finishes a load in the main frame. | |
virtual void OnFinishLoading(View* caller) override; | |
// This is called when the DOM has loaded in the main frame. Update JS here. | |
virtual void OnDOMReady(View* caller) override; | |
JSValue StartAsyncTask(const JSObject& thisObject, const JSArgs& args); | |
protected: | |
RefPtr<App> app_; | |
RefPtr<Window> window_; | |
RefPtr<Overlay> overlay_; | |
TaskQueue<Task> task_queue_; | |
WorkerThread<Task> worker_thread_; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include <queue> | |
#include <mutex> | |
#include <condition_variable> | |
// A thread-safe task queue. | |
template <class T> | |
class TaskQueue | |
{ | |
public: | |
TaskQueue(void) | |
: queue_() | |
, queue_lock_() | |
, wait_for_task_() | |
{} | |
~TaskQueue(void) | |
{} | |
// Push element to back of queue. | |
void push(T t) | |
{ | |
std::lock_guard<std::mutex> lock(queue_lock_); | |
queue_.push(t); | |
wait_for_task_.notify_one(); | |
} | |
// Pop element from front. | |
// Blocks until a task is available. | |
T pop() | |
{ | |
std::unique_lock<std::mutex> lock(queue_lock_); | |
while (queue_.empty()) | |
wait_for_task_.wait(lock); | |
T val = queue_.front(); | |
queue_.pop(); | |
return val; | |
} | |
bool empty() | |
{ | |
std::unique_lock<std::mutex> lock(queue_lock_); | |
return queue_.empty(); | |
} | |
private: | |
std::queue<T> queue_; | |
mutable std::mutex queue_lock_; | |
std::condition_variable wait_for_task_; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#include "TaskQueue.h" | |
#include <thread> | |
#include <functional> | |
#include <memory> | |
template <class TaskType> | |
class WorkerThread { | |
public: | |
WorkerThread() : is_running_(true) { | |
thread_.reset(new std::thread(&WorkerThread::Run, this)); | |
} | |
~WorkerThread() { | |
PostTask(std::bind(&WorkerThread::Stop, this)); | |
thread_->join(); | |
} | |
void PostTask(TaskType task) { | |
task_queue_.push(task); | |
} | |
protected: | |
void Run() { | |
while (is_running_) { | |
Task task = task_queue_.pop(); | |
task(); | |
} | |
} | |
void Stop() { | |
is_running_ = false; | |
} | |
std::unique_ptr<std::thread> thread_; | |
TaskQueue<TaskType> task_queue_; | |
bool is_running_; | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment