Skip to content

Instantly share code, notes, and snippets.

@adamjs
Last active January 16, 2020 22:48
Show Gist options
  • Save adamjs/f8897e8b764a46de24ff45b03e91f515 to your computer and use it in GitHub Desktop.
Save adamjs/f8897e8b764a46de24ff45b03e91f515 to your computer and use it in GitHub Desktop.
Async callback example using JavaScript Promises (Ultralight 1.1)
#include "MyApp.h"
int main() {
MyApp app;
app.Run();
return 0;
}
#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>
)";
}
#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_;
};
#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_;
};
#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