mirror of
https://github.com/libretro/libretro-puzzlescript.git
synced 2026-11-13 20:14:54 -10:00
462 lines
12 KiB
C++
462 lines
12 KiB
C++
|
|
#include <atomic>
|
|
#include <cassert>
|
|
#include <chrono>
|
|
#include <cstring>
|
|
#include <cstdlib>
|
|
#include <memory>
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "libretro.h"
|
|
|
|
namespace bundled {
|
|
#include "bundled.h"
|
|
}
|
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#include "stb_image.h"
|
|
|
|
#include "duktape.h"
|
|
|
|
// Callbacks
|
|
static retro_log_printf_t log_cb;
|
|
static retro_video_refresh_t video_cb;
|
|
static retro_input_poll_t input_poll_cb;
|
|
static retro_input_state_t input_state_cb;
|
|
static retro_environment_t environ_cb;
|
|
static retro_audio_sample_t audio_cb;
|
|
static retro_audio_sample_batch_t audio_batch_cb;
|
|
|
|
// Font data
|
|
static uint16_t *sysfont_data;
|
|
static int sysfont_width;
|
|
static int sysfont_height;
|
|
|
|
// JavaScript context
|
|
static duk_context *js_ctx;
|
|
|
|
// Graphics
|
|
constexpr int graphics_fps{30};
|
|
constexpr int graphics_width{640};
|
|
constexpr int graphics_height{480};
|
|
constexpr int graphics_stride{graphics_width};
|
|
constexpr int graphics_framebuffer_len{graphics_stride * graphics_height};
|
|
// Format is fixed RGB 565
|
|
uint16_t graphics_framebuffer[graphics_framebuffer_len];
|
|
|
|
// Audio
|
|
constexpr int audio_framerate{44100};
|
|
constexpr int audio_buffer_len{audio_framerate / graphics_fps * 2};
|
|
int16_t audio_buffer[audio_buffer_len];
|
|
|
|
// Sprites
|
|
struct SpriteInstance {
|
|
int id;
|
|
int x;
|
|
int y;
|
|
};
|
|
|
|
class Sprite {
|
|
public:
|
|
int width{-1};
|
|
int height{-1};
|
|
std::vector<uint16_t> data{};
|
|
|
|
Sprite(int width, int height) :
|
|
width(width),
|
|
height(height),
|
|
data(width * height) {
|
|
}
|
|
|
|
~Sprite() {
|
|
}
|
|
|
|
void draw(int x, int y, int w, int h) {
|
|
std::cout << "width=" << width << " w=" << w << std::endl;
|
|
assert(w == width);
|
|
assert(h == height);
|
|
for (int r = 0; r < h; r++) {
|
|
for (int c = 0; c < w; c++) {
|
|
*(graphics_framebuffer + y * graphics_stride + r * graphics_stride + x + c) = data[r * w + c];
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
// Sprite Lists
|
|
std::vector<Sprite> sprites{};
|
|
|
|
unsigned retro_api_version()
|
|
{
|
|
return RETRO_API_VERSION;
|
|
}
|
|
|
|
void retro_cheat_reset()
|
|
{
|
|
// Empty
|
|
}
|
|
|
|
void retro_cheat_set(unsigned /*index*/, bool /*enabled*/, const char */*code*/)
|
|
{
|
|
// Empty
|
|
}
|
|
|
|
bool retro_load_game(const struct retro_game_info */*info*/)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool retro_load_game_special(unsigned /*game_type*/, const struct retro_game_info */*info*/, size_t /*num_info*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void retro_unload_game()
|
|
{
|
|
// Empty
|
|
}
|
|
|
|
unsigned retro_get_region()
|
|
{
|
|
return RETRO_REGION_NTSC;
|
|
}
|
|
|
|
void retro_set_controller_port_device(unsigned /*port*/, unsigned /*device*/)
|
|
{
|
|
// Empty
|
|
}
|
|
|
|
|
|
void *retro_get_memory_data(unsigned /*id*/)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
size_t retro_get_memory_size(unsigned /*id*/)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
size_t retro_serialize_size()
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool retro_serialize(void */*data*/, size_t /*size*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool retro_unserialize(const void */*data*/, size_t /*size*/)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// libretro global setters
|
|
void retro_set_environment(retro_environment_t cb)
|
|
{
|
|
environ_cb = cb;
|
|
|
|
// Allow running core with no game selected
|
|
bool no_rom = true;
|
|
cb(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME, &no_rom);
|
|
|
|
static const struct retro_controller_description controllers[] = {
|
|
{ "PuzzleScript Controller", RETRO_DEVICE_SUBCLASS(RETRO_DEVICE_JOYPAD, 0) },
|
|
};
|
|
|
|
static const struct retro_controller_info ports[] = {
|
|
{ controllers, 1 },
|
|
{ NULL, 0 },
|
|
};
|
|
|
|
cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
|
|
}
|
|
|
|
void retro_set_audio_sample_batch(retro_audio_sample_batch_t cb)
|
|
{
|
|
audio_batch_cb = cb;
|
|
}
|
|
|
|
void retro_set_video_refresh(retro_video_refresh_t cb)
|
|
{
|
|
video_cb = cb;
|
|
}
|
|
|
|
void retro_set_audio_sample(retro_audio_sample_t cb)
|
|
{
|
|
audio_cb = cb;
|
|
}
|
|
|
|
void retro_set_input_poll(retro_input_poll_t cb)
|
|
{
|
|
input_poll_cb = cb;
|
|
}
|
|
|
|
void retro_set_input_state(retro_input_state_t cb)
|
|
{
|
|
input_state_cb = cb;
|
|
}
|
|
|
|
void draw_letter(int x, int y, int letter)
|
|
{
|
|
uint16_t *fb = &graphics_framebuffer[x + graphics_stride*y];
|
|
uint16_t *rom = &sysfont_data[letter * 9];
|
|
for (int r = 0; r < 9; r++) {
|
|
for (int c = 0; c < 9; c++) {
|
|
*(fb + r * graphics_stride + c) = *(rom + r * 256 * 9 + c);
|
|
}
|
|
}
|
|
}
|
|
|
|
duk_ret_t native_print(duk_context *ctx)
|
|
{
|
|
duk_push_string(ctx, " ");
|
|
duk_insert(ctx, 0);
|
|
duk_join(ctx, duk_get_top(ctx) - 1);
|
|
std::string msg{duk_safe_to_string(ctx, -1)};
|
|
// Do actual print operation
|
|
std::cout << msg << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
duk_ret_t native_create_context(duk_context *ctx)
|
|
{
|
|
int width{duk_get_int(ctx, 0)};
|
|
int height{duk_get_int(ctx, 1)};
|
|
// Add new sprite
|
|
sprites.emplace_back(width, height);
|
|
// Return existing number of sprites already in list
|
|
duk_push_int(ctx, sprites.size() - 1);
|
|
return 1;
|
|
}
|
|
|
|
duk_ret_t native_clear_contexts(duk_context */*ctx*/)
|
|
{
|
|
sprites.clear();
|
|
return 0;
|
|
}
|
|
|
|
uint16_t webcolor(std::string hexcolor)
|
|
{
|
|
std::string r;
|
|
std::string g;
|
|
std::string b;
|
|
uint16_t ri{0};
|
|
uint16_t gi{0};
|
|
uint16_t bi{0};
|
|
if (hexcolor.size() == 4) {
|
|
r = hexcolor.substr(1, 1) + hexcolor.substr(1, 1);
|
|
g = hexcolor.substr(2, 1) + hexcolor.substr(2, 1);
|
|
b = hexcolor.substr(3, 1) + hexcolor.substr(3, 1);
|
|
} else if (hexcolor.size() >= 7) {
|
|
r = hexcolor.substr(1, 2);
|
|
g = hexcolor.substr(3, 2);
|
|
b = hexcolor.substr(5, 2);
|
|
} else {
|
|
throw std::invalid_argument("Illegal color");
|
|
}
|
|
std::stringstream sr;
|
|
sr << std::hex << r;
|
|
sr >> ri;
|
|
std::stringstream sg;
|
|
sg << std::hex << g;
|
|
sg >> gi;
|
|
std::stringstream sb;
|
|
sb << std::hex << b;
|
|
sb >> bi;
|
|
return ((ri >> (8 - 5)) << (6 + 5)) | ((gi >> (8 - 6)) << 5) | (bi >> (8 - 5));
|
|
}
|
|
|
|
duk_ret_t native_fill_rect(duk_context *ctx) {
|
|
int contextid{duk_get_int(ctx, 0)};
|
|
std::string fill{duk_safe_to_string(ctx, 1)};
|
|
int x{duk_get_int(ctx, 2)};
|
|
int y{duk_get_int(ctx, 3)};
|
|
int w{duk_get_int(ctx, 4)};
|
|
int h{duk_get_int(ctx, 5)};
|
|
uint16_t color{webcolor(fill)};
|
|
Sprite &sprite{sprites[contextid]};
|
|
int pitch{sprite.width};
|
|
for (int j = 0; j < h; j++) {
|
|
for (int i = 0; i < w; i++) {
|
|
sprite.data[x + i + pitch * j + pitch * y] = color;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void js_fatal_handler(void */*udata*/, const char *msg)
|
|
{
|
|
std::cerr << "******* JS ERROR: " << msg << std::endl;
|
|
abort();
|
|
}
|
|
|
|
std::thread logic_thread;
|
|
|
|
uint8_t g_offset{0};
|
|
std::atomic<bool> logic_thread_active{true};
|
|
|
|
void logic_update()
|
|
{
|
|
using namespace std::chrono_literals;
|
|
|
|
while(logic_thread_active) {
|
|
g_offset++;
|
|
std::this_thread::sleep_for(100ms);
|
|
}
|
|
}
|
|
|
|
void retro_init()
|
|
{
|
|
/* set up some logging */
|
|
struct retro_log_callback log;
|
|
unsigned level = 4;
|
|
|
|
if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {
|
|
log_cb = log.log;
|
|
} else {
|
|
log_cb = NULL;
|
|
}
|
|
// the performance level is guide to frontend to give an idea of how intensive this core is to run
|
|
environ_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL, &level);
|
|
|
|
int channels{0};
|
|
stbi_uc *data = stbi_load_from_memory(bundled::data_sysfont_png, bundled::data_sysfont_png_len, &sysfont_width, &sysfont_height, &channels, 0);
|
|
sysfont_data = new uint16_t[256 * 9 * sysfont_height]();
|
|
|
|
std::cout << "sysfont is " << sysfont_width << " x " << sysfont_height << std::endl;
|
|
std::cout << "sysfont_data =" << (void*)sysfont_data << std::endl;
|
|
for (int r = 0; r < 9; r++) {
|
|
for (int c = 0; c < 9; c++) {
|
|
std::cout << (int)*(sysfont_data + c * 3 + r * 3 * sysfont_width) << " ";
|
|
}
|
|
std::cout << "\n";
|
|
}
|
|
std::cout << std::endl;
|
|
for (int r = 0; r < 9; r++) {
|
|
for (int ch = 0; ch < sysfont_width / 9; ch++) {
|
|
for (int c = 0; c < 9; c++) {
|
|
stbi_uc col = *(data + r * sysfont_width * 3 + ch * 9 * 3 + c * 3);
|
|
if (col) {
|
|
sysfont_data[r * 256 * 9 + (ch + 33) * 9 + c] = 0xffff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Setup duktape
|
|
js_ctx = duk_create_heap(nullptr, nullptr, nullptr, nullptr, js_fatal_handler);
|
|
|
|
// Register print function
|
|
duk_push_c_function(js_ctx, native_print, DUK_VARARGS);
|
|
duk_put_global_string(js_ctx, "print");
|
|
duk_push_c_function(js_ctx, native_create_context, 2);
|
|
duk_put_global_string(js_ctx, "native_create_context");
|
|
duk_push_c_function(js_ctx, native_clear_contexts, 0);
|
|
duk_put_global_string(js_ctx, "native_clear_contexts");
|
|
duk_push_c_function(js_ctx, native_fill_rect, 6);
|
|
duk_put_global_string(js_ctx, "native_fill_rect");
|
|
|
|
duk_eval_string(js_ctx, "id = native_create_context(64, 64);");
|
|
duk_eval_string(js_ctx, "native_fill_rect(id, '#ff0000', 0, 0, 16, 64);");
|
|
duk_eval_string(js_ctx, "native_fill_rect(id, '#00ff00', 16, 0, 16, 64);");
|
|
duk_eval_string(js_ctx, "native_fill_rect(id, '#0000ff', 32, 0, 16, 64);");
|
|
|
|
logic_thread = std::thread(logic_update);
|
|
}
|
|
|
|
void retro_deinit()
|
|
{
|
|
std::cout << "retro_deinit" << std::endl;
|
|
sprites.clear();
|
|
std::cout << "retro_deinit calling duk_destroy_heap" << std::endl;
|
|
duk_destroy_heap(js_ctx);
|
|
logic_thread_active = false;
|
|
logic_thread.join();
|
|
}
|
|
|
|
void retro_get_system_info(struct retro_system_info *info)
|
|
{
|
|
memset(info, 0, sizeof(*info));
|
|
info->library_name = "PuzzleScript";
|
|
info->library_version = "0.2";
|
|
info->need_fullpath = false;
|
|
info->valid_extensions = "pz";
|
|
}
|
|
|
|
void retro_get_system_av_info(struct retro_system_av_info *info)
|
|
{
|
|
int pixel_format = RETRO_PIXEL_FORMAT_RGB565;
|
|
|
|
*info = retro_system_av_info{};
|
|
info->timing.fps = graphics_fps;
|
|
info->timing.sample_rate = audio_framerate;
|
|
info->geometry.base_width = graphics_width;
|
|
info->geometry.base_height = graphics_height;
|
|
info->geometry.max_width = graphics_width;
|
|
info->geometry.max_height = graphics_height;
|
|
// Don't request any specific aspect ratio
|
|
|
|
// the performance level is guide to frontend to give an idea of how intensive this core is to run
|
|
environ_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixel_format);
|
|
}
|
|
|
|
void retro_reset()
|
|
{
|
|
// Do stuff
|
|
}
|
|
|
|
int x{graphics_width / 2};
|
|
int y{graphics_height / 2};
|
|
|
|
// Run a single frame
|
|
void retro_run()
|
|
{
|
|
// Run one frame
|
|
|
|
// Get input for frame
|
|
input_poll_cb();
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_UP)) {
|
|
y--;
|
|
}
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_DOWN)) {
|
|
y++;
|
|
}
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_LEFT)) {
|
|
x--;
|
|
}
|
|
if (input_state_cb(0, RETRO_DEVICE_JOYPAD, 0, RETRO_DEVICE_ID_JOYPAD_RIGHT)) {
|
|
x++;
|
|
}
|
|
|
|
// Render audio frame
|
|
audio_batch_cb(audio_buffer, audio_buffer_len / 2);
|
|
|
|
// Render video frame
|
|
for (int row = 0; row < graphics_height; row++) {
|
|
for (int col = 0; col < graphics_width; col++) {
|
|
uint8_t r = col % 256;
|
|
uint8_t g = row % 256;
|
|
uint8_t b = (row + col + g_offset) % 256;
|
|
graphics_framebuffer[row * graphics_stride + col] = ((r >> 3) << (5 + 6)) | ((g >> 2) << 5) | (b >> 3);
|
|
}
|
|
}
|
|
for (int h = 0; h < 20; h++) {
|
|
for (int w = 0; w < 20; w++) {
|
|
graphics_framebuffer[(h + y)* graphics_stride + (w + x)] = 0x00000000;
|
|
}
|
|
}
|
|
for (auto &sprite : sprites) {
|
|
sprite.draw(0, 0, sprite.width, sprite.height);
|
|
}
|
|
|
|
// Try print function
|
|
duk_eval_string(js_ctx, "print('Hello world!', id);");
|
|
|
|
video_cb(graphics_framebuffer, graphics_width, graphics_height, sizeof(uint16_t) * graphics_stride);
|
|
}
|