Browse Source

Add off-main-thread page loading

main
Jens Pitkänen 11 months ago
parent
commit
8c5f14d62f
8 changed files with 193 additions and 93 deletions
  1. +1
    -1
      configure
  2. +1
    -1
      configure.bat
  3. +66
    -0
      src/browser.c
  4. +37
    -0
      src/browser.h
  5. +48
    -69
      src/main.c
  6. +11
    -0
      src/net.c
  7. +6
    -20
      src/text.c
  8. +23
    -2
      src/text.h

+ 1
- 1
configure View File

@ -13,7 +13,7 @@ fi
rm Makefile
echo "OUTDIR=./build-linux-$(uname -m)" >> Makefile
echo "EXE=nemini" >> Makefile
echo "OBJS=src/main.o src/net.o src/socket.o src/url.o src/error.o src/gemini.o src/text.o src/str.o" >> Makefile
echo "OBJS=src/main.o src/net.o src/socket.o src/url.o src/error.o src/gemini.o src/text.o src/str.o src/browser.o" >> Makefile
# See CFLAGS definition above
echo "CFLAGS=$CFLAGS" >> Makefile
echo "LIBS=$(pkg-config --libs sdl2 libssl libcrypto)" >> Makefile


+ 1
- 1
configure.bat View File

@ -21,7 +21,7 @@ if exist Makefile (del Makefile)
echo OUTDIR=build-windows-x86>>Makefile
echo EXE=nemini.exe>>Makefile
echo BITS=x86>>Makefile
echo OBJS=src\main.obj src\net.obj src\socket.obj src\url.obj src\error.obj src\gemini.obj src\text.obj src\str.obj>>Makefile
echo OBJS=src\main.obj src\net.obj src\socket.obj src\url.obj src\error.obj src\gemini.obj src\text.obj src\str.obj src\browser.obj>>Makefile
echo CFLAGS=/Iinclude /TC /O2 /GS /guard:cf /nologo>> Makefile
echo LIBS=/link /DEBUG:FULL /subsystem:windows /nologo lib\$(BITS)\SDL2main.lib lib\$(BITS)\SDL2.lib lib\$(BITS)\*crypto*.lib lib\$(BITS)\*ssl*.lib shell32.lib Ws2_32.lib>>Makefile
echo OUTFLAG=/Fe>>Makefile


+ 66
- 0
src/browser.c View File

@ -0,0 +1,66 @@
// SPDX-FileCopyrightText: 2020 Jens Pitkanen <jens@neon.moe>
// SPDX-License-Identifier: GPL-3.0-or-later
#include <SDL.h>
#include "stretchy_buffer.h"
#include "browser.h"
#include "error.h"
#include "net.h"
#include "text.h"
static enum loading_status current_status;
static struct loaded_page *loaded_pages = NULL;
void browser_set_status(enum loading_status new_status) {
current_status = new_status;
}
enum loading_status browser_get_status(void) {
return current_status;
}
static int load_page(void *data) {
const char *url = (char *)data;
SDL_Log("Starting to load page from: %s", url);
Uint32 response_start = SDL_GetTicks();
struct gemini_response res = {0};
int result = net_request(url, &res);
if (result == ERR_NONE) {
SDL_Surface *surface = NULL;
text_render(&surface, res.body, (int)(600 * 1), 1);
gemini_response_free(res);
struct loaded_page page = {0};
page.surface = surface;
sb_push(loaded_pages, page);
browser_set_status(LOADING_DONE);
SDL_Log("Finished loading after %.3f seconds.", (SDL_GetTicks() - response_start) / 1000.0);
}
return 0;
}
enum nemini_error browser_start_loading(const char *url) {
SDL_Thread *thread = SDL_CreateThread(load_page, "GeminiLoader", (void *)url);
if (thread != NULL) {
SDL_DetachThread(thread);
return ERR_NONE;
} else {
return ERR_SDL;
}
}
struct loaded_page *get_browser_page(void) {
return &sb_last(loaded_pages);
}
void browser_free(void) {
int page_count = sb_count(loaded_pages);
for (int i = 0; i < page_count; i++) {
SDL_FreeSurface(loaded_pages[i].surface);
SDL_DestroyTexture(loaded_pages[i].texture);
}
}

+ 37
- 0
src/browser.h View File

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2020 Jens Pitkanen <jens@neon.moe>
// SPDX-License-Identifier: GPL-3.0-or-later
#ifndef NEMINI_BROWSER_H
#define NEMINI_BROWSER_H
#include <SDL.h>
#include "error.h"
struct loaded_page {
struct loaded_page *parent;
// When loading, surface is set. After the first render, the
// surface is loaded into texture memory, and freed.
// So surface == null XOR texture == null.
SDL_Surface *surface;
SDL_Texture *texture;
};
enum loading_status {
LOADING_CONNECTING,
LOADING_ESTABLISHING_TLS,
LOADING_SENDING_REQUEST,
LOADING_RECEIVING_HEADER,
LOADING_RECEIVING_BODY,
LOADING_LAYOUT,
LOADING_RASTERIZING,
LOADING_DONE,
};
void browser_set_status(enum loading_status new_status);
enum loading_status browser_get_status(void);
enum nemini_error browser_start_loading(const char *url);
struct loaded_page *get_browser_page(void);
void browser_free(void);
#endif

+ 48
- 69
src/main.c View File

@ -9,6 +9,7 @@
#include "error.h"
#include "gemini.h"
#include "text.h"
#include "browser.h"
int get_refresh_rate(SDL_Window *);
bool get_scale(SDL_Window *, SDL_Renderer *, float *, float *);
@ -57,62 +58,13 @@ int main(int argc, char **argv) {
SDL_SetWindowTitle(window, "Nemini");
// TODO: Move this entire section to a "load page" module (remember frees too)
// TODO: Actually parse the mime type
Uint32 response_start = SDL_GetTicks();
struct gemini_response res = {0};
SDL_Texture *gemtext_texture = NULL;
const char *url;
if (argc < 2) {
url = "gemini://192.168.1.106/";
browser_set_status(LOADING_CONNECTING);
if (argc >= 2) {
browser_start_loading(argv[1]);
} else {
url = argv[1];
}
int result = net_request(url, &res);
if (result != ERR_NONE) {
char buffer[1024];
SDL_snprintf(buffer, 1024, "net_request() returned an error: %s",
get_nemini_err_str(result));
SDL_Log("%s", buffer);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"error", buffer, window);
return 1;
SDL_Log("Usage: %s <url>", argv[0]);
return 0;
}
SDL_Log("Got response in %.3f seconds", (SDL_GetTicks() - response_start) / 1000.0);
if (res.status / 10 != 2) {
SDL_Log("Response status is not 2X, not rendering into text.");
} else if (SDL_strcmp(res.meta.mime_type, "text/gemini") != 0) {
SDL_Log("Response status is not 2X, not rendering into text.");
} else {
Uint32 render_start = SDL_GetTicks();
float scale_x, scale_y;
get_scale(window, renderer, &scale_x, &scale_y);
SDL_Surface *surface = NULL;
result = text_render(&surface, res.body,
(int)(600 * scale_x), scale_x);
if (result != ERR_NONE) {
char buffer[1024];
SDL_snprintf(buffer, 1024, "text_render() returned an error: %s (%d)",
get_nemini_err_str(result), result);
SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "%s", buffer);
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,
"error", buffer, window);
return 1;
} else if (surface == NULL) {
SDL_Log("Texture rendering still failed!");
return 1;
} else {
SDL_Log("Text rendered successfully in %.3f seconds", (SDL_GetTicks() - render_start) / 1000.0);
gemtext_texture = SDL_CreateTextureFromSurface(renderer, surface);
if (gemtext_texture == NULL) {
SDL_Log("Could not create texture from surface: %s", SDL_GetError());
return 1;
}
SDL_FreeSurface(surface);
}
}
bool running = true;
int refresh_rate = 60;
@ -140,18 +92,47 @@ int main(int argc, char **argv) {
SDL_SetRenderDrawColor(renderer, 0xDD, 0xDD, 0xDD, 0xDD);
SDL_RenderClear(renderer);
Uint32 t_format;
int t_access, t_width, t_height;
SDL_QueryTexture(gemtext_texture, &t_format, &t_access,
&t_width, &t_height);
t_width /= scale_x;
t_height /= scale_y;
SDL_Rect dst_rect = {0};
dst_rect.x = (width - t_width) / 2;
dst_rect.y = 8;
dst_rect.w = t_width;
dst_rect.h = t_height;
SDL_RenderCopy(renderer, gemtext_texture, NULL, &dst_rect);
enum loading_status page_status = browser_get_status();
if (page_status == LOADING_DONE) {
struct loaded_page *page = get_browser_page();
if (page->texture == NULL) {
page->texture = SDL_CreateTextureFromSurface(renderer, page->surface);
if (page->texture != NULL) {
SDL_FreeSurface(page->surface);
page->surface = NULL;
}
} else {
Uint32 t_format;
int t_access, t_width, t_height;
SDL_QueryTexture(page->texture, &t_format, &t_access,
&t_width, &t_height);
t_width /= scale_x;
t_height /= scale_y;
SDL_Rect dst_rect = {0};
dst_rect.x = (width - t_width) / 2;
dst_rect.y = 8;
dst_rect.w = t_width;
dst_rect.h = t_height;
SDL_RenderCopy(renderer, page->texture, NULL, &dst_rect);
}
} else {
SDL_SetRenderDrawColor(renderer, 0x55, 0xAA, 0x55, 0xBB);
for (int s = 0; s < LOADING_DONE; s++) {
SDL_Rect loading_rect = {0};
loading_rect.x = (width - 40 * LOADING_DONE) / 2
+ s * 40;
loading_rect.y = (height - 32) / 2;
loading_rect.w = 32;
loading_rect.h = 32;
if (s < (int)page_status) {
SDL_RenderFillRect(renderer, &loading_rect);
} else {
SDL_RenderDrawRect(renderer, &loading_rect);
}
}
}
SDL_RenderPresent(renderer);
@ -182,9 +163,7 @@ int main(int argc, char **argv) {
}
}
gemini_response_free(res);
SDL_DestroyTexture(gemtext_texture);
browser_free();
text_renderer_free();
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);


+ 11
- 0
src/net.c View File

@ -13,6 +13,7 @@
#include "socket.h"
#include "url.h"
#include "gemini.h"
#include "browser.h"
static SSL_CTX *ctx;
@ -49,6 +50,8 @@ void net_free(void) {
enum nemini_error net_request(const char *url, struct gemini_response *result) {
enum nemini_error err = ERR_NONE;
browser_set_status(LOADING_CONNECTING);
char *host, *port, *resource;
int parse_status = parse_gemini_url(url, &host, &port, &resource);
if (parse_status != ERR_NONE) { return parse_status; }
@ -60,6 +63,8 @@ enum nemini_error net_request(const char *url, struct gemini_response *result) {
goto free_up_to_url;
}
browser_set_status(LOADING_ESTABLISHING_TLS);
SSL *ssl = SSL_new(ctx);
if (ssl == NULL) { return ERR_UNEXPECTED; }
@ -97,6 +102,8 @@ enum nemini_error net_request(const char *url, struct gemini_response *result) {
BIO_set_ssl(bio_ssl, ssl, 0);
BIO_push(bio_buffered, bio_ssl);
browser_set_status(LOADING_SENDING_REQUEST);
int url_length = SDL_strlen(url);
char *request = SDL_malloc(url_length + 3);
SDL_memcpy(request, url, url_length);
@ -106,6 +113,8 @@ enum nemini_error net_request(const char *url, struct gemini_response *result) {
goto free_up_to_bio;
}
browser_set_status(LOADING_RECEIVING_HEADER);
// Length: <STATUS><SPACE><META><CR><LF><NUL>
char gemini_header[2 + 1 + 1024 + 1 + 1 + 1] = {0};
int header_bytes = BIO_gets(bio_buffered, gemini_header, sizeof(gemini_header));
@ -130,6 +139,8 @@ enum nemini_error net_request(const char *url, struct gemini_response *result) {
meta[meta_length] = '\0';
res.meta.meta = meta;
browser_set_status(LOADING_RECEIVING_BODY);
// Only input (2X) responses have a body.
if (gemini_header[0] == '2') {
void *body = NULL;


+ 6
- 20
src/text.c View File

@ -20,6 +20,8 @@
#include "error.h"
#include "str.h"
#include "ctx.h"
#include "text.h"
#include "browser.h"
#include "font_atkinson.ttf.h"
#include "font_mono.ttf.h"
@ -62,26 +64,6 @@ enum nemini_error text_renderer_init(void) {
void text_renderer_free(void) {
}
enum line_type {
GEMINI_TEXT,
GEMINI_LINK,
GEMINI_PREFORMATTED,
GEMINI_HEADING_BIG,
GEMINI_HEADING_MEDIUM,
GEMINI_HEADING_SMALL,
GEMINI_UNORDERED_LIST,
GEMINI_QUOTE,
};
// text/gemini is rendered one line at a time, with wrapping. The
// "lines" from the Gemini spec are called paragraphs in Nemini, to
// differentiate them from actual visual lines.
struct text_paragraph {
struct nemini_string string;
struct nemini_string link; // Empty for all lines except links.
enum line_type type;
};
// Returns the substring of string found after skipping whitespace
// after index "offset". I.e. for the string "# foo" with offset 1
// would return "foo".
@ -243,6 +225,8 @@ struct glyph_blueprint {
enum nemini_error text_render(SDL_Surface **result, const char *text,
int width, float scale) {
browser_set_status(LOADING_LAYOUT);
struct nemini_string string;
enum nemini_error stringify_status = nemini_string_from(text, &string);
if (stringify_status != ERR_NONE) {
@ -409,6 +393,8 @@ enum nemini_error text_render(SDL_Surface **result, const char *text,
}
sb_free(paragraphs);
browser_set_status(LOADING_RASTERIZING);
int height = (int)y_cursor;
SDL_Surface *surface = NULL;
surface = SDL_CreateRGBSurfaceWithFormat(0, width, height, 32,


+ 23
- 2
src/text.h View File

@ -5,10 +5,31 @@
#define NEMINI_TEXT_H
#include "error.h"
#include "str.h"
enum line_type {
GEMINI_TEXT,
GEMINI_LINK,
GEMINI_PREFORMATTED,
GEMINI_HEADING_BIG,
GEMINI_HEADING_MEDIUM,
GEMINI_HEADING_SMALL,
GEMINI_UNORDERED_LIST,
GEMINI_QUOTE,
};
// text/gemini is rendered one line at a time, with wrapping. The
// "lines" from the Gemini spec are called paragraphs in Nemini, to
// differentiate them from actual visual lines.
struct text_paragraph {
struct nemini_string string;
struct nemini_string link; // Empty for all lines except links.
enum line_type type;
};
enum nemini_error text_renderer_init(void);
void text_renderer_free(void);
enum nemini_error text_render(SDL_Surface **result, const char *text,
int width, float scale);
enum nemini_error text_render(SDL_Surface **result,
const char *text, int width, float scale);
#endif

Loading…
Cancel
Save