479 lines
13 KiB
C
479 lines
13 KiB
C
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
#include <netinet/in.h>
|
|
#include <unistd.h>
|
|
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
|
|
#include <SDL3/SDL.h>
|
|
|
|
#include "timer.h"
|
|
#include "bufsize.h"
|
|
#include "packet.h"
|
|
#include "timespec.h"
|
|
#include "ping_pong.h"
|
|
#include "gpio.h"
|
|
|
|
#define DEST_PORT 4321
|
|
#define DEST_ADDR "fd00::2"
|
|
|
|
#define PORT 1234
|
|
|
|
struct glyph {
|
|
int32_t width;
|
|
int32_t height;
|
|
|
|
int32_t horiBearingX;
|
|
int32_t horiBearingY;
|
|
int32_t horiAdvance;
|
|
|
|
SDL_Texture * texture;
|
|
};
|
|
|
|
static struct glyph glyphs[0x80 - 0x20] = {0};
|
|
int32_t face_height;
|
|
|
|
int
|
|
load_outline_char(SDL_Renderer * renderer,
|
|
const FT_Face face,
|
|
const FT_Int32 load_flags,
|
|
const FT_Render_Mode render_mode,
|
|
const FT_ULong char_code)
|
|
{
|
|
FT_Error error;
|
|
FT_UInt glyph_index = FT_Get_Char_Index(face, char_code);
|
|
|
|
error = FT_Load_Glyph(face, glyph_index, load_flags);
|
|
if (error) {
|
|
printf("FT_Load_Glyph %s\n", FT_Error_String(error));
|
|
return -1;
|
|
}
|
|
|
|
error = FT_Render_Glyph(face->glyph, render_mode);
|
|
if (error) {
|
|
printf("FT_Render_Glyph %s\n", FT_Error_String(error));
|
|
return -1;
|
|
}
|
|
|
|
struct glyph * glyph = &glyphs[char_code - 0x20];
|
|
|
|
glyph->width = face->glyph->bitmap.width;
|
|
glyph->height = face->glyph->bitmap.rows;
|
|
glyph->horiBearingX = face->glyph->metrics.horiBearingX;
|
|
glyph->horiBearingY = face->glyph->metrics.horiBearingY;
|
|
glyph->horiAdvance = face->glyph->metrics.horiAdvance;
|
|
|
|
if (face->glyph->bitmap.pitch != 0) {
|
|
SDL_Surface * surface = SDL_CreateSurface(face->glyph->bitmap.width,
|
|
face->glyph->bitmap.rows,
|
|
SDL_PIXELFORMAT_RGBA8888);
|
|
|
|
SDL_LockSurface(surface);
|
|
|
|
for (int y = 0; y < face->glyph->bitmap.rows; y++) {
|
|
for (int x = 0; x < face->glyph->bitmap.width; x++) {
|
|
int s_ix = y * face->glyph->bitmap.pitch + x;
|
|
int d_ix = y * surface->pitch + x * 4;
|
|
uint8_t gray = face->glyph->bitmap.buffer[s_ix];
|
|
((uint8_t *)surface->pixels)[d_ix + 0] = gray;
|
|
((uint8_t *)surface->pixels)[d_ix + 1] = gray;
|
|
((uint8_t *)surface->pixels)[d_ix + 2] = gray;
|
|
((uint8_t *)surface->pixels)[d_ix + 3] = 255;
|
|
}
|
|
}
|
|
|
|
SDL_UnlockSurface(surface);
|
|
|
|
if (glyph->texture != NULL)
|
|
SDL_DestroyTexture(glyph->texture);
|
|
glyph->texture = SDL_CreateTextureFromSurface(renderer, surface);
|
|
if (glyph->texture == NULL) {
|
|
printf("%s\n", SDL_GetError());
|
|
}
|
|
assert(glyph->texture != NULL);
|
|
|
|
SDL_DestroySurface(surface);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int load_font(SDL_Renderer * renderer, int font_size)
|
|
{
|
|
FT_Library library;
|
|
FT_Face face;
|
|
FT_Error error;
|
|
|
|
error = FT_Init_FreeType(&library);
|
|
if (error) {
|
|
printf("FT_Init_FreeType\n");
|
|
return -1;
|
|
}
|
|
|
|
error = FT_New_Face(library, "/home/bilbo/timer/DejaVuSansMono.ttf", 0, &face);
|
|
if (error) {
|
|
printf("FT_New_Face\n");
|
|
return -1;
|
|
}
|
|
|
|
error = FT_Set_Pixel_Sizes(face, 0, font_size);
|
|
if (error) {
|
|
printf("FT_Set_Pixel_Sizes: %s %d\n", FT_Error_String(error), error);
|
|
return -1;
|
|
}
|
|
|
|
for (int char_code = 0x20; char_code <= 0x7f; char_code++) {
|
|
load_outline_char(renderer, face, FT_LOAD_DEFAULT, FT_RENDER_MODE_NORMAL, char_code);
|
|
}
|
|
|
|
printf("loaded size %ld\n", face->size->metrics.height >> 6);
|
|
face_height = face->size->metrics.height;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void render(SDL_Renderer * renderer, int window_width, int window_height, const char * s, int length)
|
|
{
|
|
int32_t advance = (window_width - (window_width * 5 / 100)) << 6;
|
|
for (int i = (length - 1); i >= 0; i--) {
|
|
char c = s[i];
|
|
struct glyph * glyph = &glyphs[c - 0x20];
|
|
advance -= glyph->horiAdvance;
|
|
if (glyph->texture != NULL) {
|
|
double x = (double)(advance + glyph->horiBearingX) / 64.f;
|
|
double y = (window_height / 2) + (face_height >> 6) / 3 + ((double)glyph->horiBearingY / -64.f);
|
|
SDL_FRect srect = {
|
|
.x = 0.f,
|
|
.y = 0.f,
|
|
.w = glyph->width,
|
|
.h = glyph->height
|
|
};
|
|
SDL_FRect drect = {
|
|
.x = x,
|
|
.y = y,
|
|
.w = glyph->width,
|
|
.h = glyph->height
|
|
};
|
|
SDL_RenderTexture(renderer, glyph->texture, &srect, &drect);
|
|
}
|
|
}
|
|
}
|
|
|
|
int max(int a, int b) {
|
|
return a > b ? a : b;
|
|
}
|
|
|
|
int handle_buf(int sockfd,
|
|
void * buf, ssize_t length,
|
|
struct timer_state * timer_state,
|
|
struct link_state * link_state)
|
|
{
|
|
struct event * evt = (struct event *)buf;
|
|
|
|
if (evt->sequence != 0) {
|
|
if (evt->sequence == link_state->recv_sequence) {
|
|
printf("ignore duplicate seq %d; recv %ld type %d\n", evt->sequence, link_state->recv_sequence, evt->type);
|
|
return 0;
|
|
} else {
|
|
printf("type %d seq %d\n", evt->sequence, evt->type);
|
|
link_state->recv_sequence = evt->sequence;
|
|
}
|
|
}
|
|
|
|
switch (evt->type) {
|
|
case EVENT_TYPE__TIME_STOP:
|
|
{
|
|
if (length != (sizeof (struct packet_stopwatch))) {
|
|
printf("handle_buf: packet_stopwatch: invalid length\n");
|
|
return 0;
|
|
}
|
|
|
|
struct packet_stopwatch * time_pkt = (struct packet_stopwatch *)buf;
|
|
memcpy(&timer_state->time, &time_pkt->stopwatch_time, (sizeof (struct stopwatch_time)));
|
|
timer_state->status = TIMER_STOPPED;
|
|
packet_send_ack(sockfd, &link_state->dest_addr, evt->sequence);
|
|
}
|
|
break;
|
|
case EVENT_TYPE__TIME_START:
|
|
{
|
|
if (length != (sizeof (struct packet_start))) {
|
|
printf("handle_buf: time start: invalid length\n");
|
|
return 0;
|
|
}
|
|
|
|
int ret = clock_gettime(CLOCK_MONOTONIC_RAW, &timer_state->counter.start);
|
|
assert(ret != -1);
|
|
timer_state->counter.offset = timespec_div(&link_state->remote_average_rtt, 2);
|
|
printf("counter_offset %ld.%09ld\n", timer_state->counter.offset.tv_sec, timer_state->counter.offset.tv_nsec);
|
|
timer_state->status = TIMER_RUNNING;
|
|
packet_send_ack(sockfd, &link_state->dest_addr, evt->sequence);
|
|
}
|
|
break;
|
|
case EVENT_TYPE__TIME_RESUME:
|
|
{
|
|
if (length != (sizeof (struct packet_resume))) {
|
|
printf("handle_buf: time resume: invalid length\n");
|
|
return 0;
|
|
}
|
|
struct packet_resume * resume_pkt = (struct packet_resume *)buf;
|
|
printf("resume offset %ld.%09ld\n", resume_pkt->offset.tv_sec, resume_pkt->offset.tv_nsec);
|
|
int ret = clock_gettime(CLOCK_MONOTONIC_RAW, &timer_state->counter.start);
|
|
assert(ret != -1);
|
|
timer_state->counter.offset = timespec_div(&link_state->remote_average_rtt, 2);
|
|
timer_state->counter.offset = timespec_add(&timer_state->counter.offset, &resume_pkt->offset);
|
|
printf("counter offset %ld.%09ld\n", timer_state->counter.offset.tv_sec, timer_state->counter.offset.tv_nsec);
|
|
timer_state->status = TIMER_RUNNING;
|
|
packet_send_ack(sockfd, &link_state->dest_addr, evt->sequence);
|
|
}
|
|
break;
|
|
case EVENT_TYPE__PING: [[fallthrough]];
|
|
case EVENT_TYPE__PONG: [[fallthrough]];
|
|
case EVENT_TYPE__AVERAGE_RTT:
|
|
{
|
|
int ret = handle_ping_pong(sockfd,
|
|
buf, length,
|
|
link_state);
|
|
if (ret == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
printf("unhandled event %d\n", evt->type);
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int handle_sockfd(int sockfd,
|
|
struct timer_state * timer_state,
|
|
struct link_state * link_state)
|
|
{
|
|
char buf[BUFSIZE];
|
|
struct sockaddr_in6 src_addr;
|
|
socklen_t addrlen = (sizeof (struct sockaddr_in6));
|
|
|
|
while (1) {
|
|
ssize_t recv_len = recvfrom(sockfd,
|
|
buf, (sizeof (buf)),
|
|
0,
|
|
(struct sockaddr *)&src_addr, &addrlen);
|
|
if (recv_len == -1) {
|
|
if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
//perror("sock eagain");
|
|
return 0;
|
|
} else {
|
|
perror("recvfrom sock");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
//char src_addr_str[INET6_ADDRSTRLEN];
|
|
//inet_ntop(AF_INET6, &src_addr.sin6_addr, src_addr_str, INET6_ADDRSTRLEN);
|
|
//printf("received packet from %s:%d\n", src_addr_str, ntohs(src_addr.sin6_port));
|
|
//printf("length: %ld\n", recv_len);
|
|
|
|
int ret = handle_buf(sockfd, buf, recv_len, timer_state, link_state);
|
|
if (ret == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char * format_base10(char * buf, int value, int digits)
|
|
{
|
|
static int table[10] = {
|
|
1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000
|
|
};
|
|
|
|
if (digits <= 0)
|
|
return buf;
|
|
|
|
int power = table[digits - 1];
|
|
for (int i = 0; i < digits; i++) {
|
|
buf[i] = '0' + ((value / power) % 10);
|
|
power /= 10;
|
|
}
|
|
|
|
return buf + digits;
|
|
}
|
|
|
|
int main()
|
|
{
|
|
int ret;
|
|
SDL_Window * window;
|
|
SDL_Renderer * renderer;
|
|
|
|
ret = SDL_Init(SDL_INIT_VIDEO);
|
|
assert(ret == 0);
|
|
|
|
window = SDL_CreateWindow("timer",
|
|
512, // w
|
|
512, // h
|
|
SDL_WINDOW_RESIZABLE);// | SDL_WINDOW_MAXIMIZED);
|
|
|
|
int num_drivers = SDL_GetNumRenderDrivers();
|
|
printf("available drivers:\n");
|
|
for (int i = 0; i < num_drivers; i++) {
|
|
const char * s = SDL_GetRenderDriver(i);
|
|
printf(" %s\n", s);
|
|
}
|
|
renderer = SDL_CreateRenderer(window, "opengles2");
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
|
|
|
SDL_PropertiesID props = SDL_GetRendererProperties(renderer);
|
|
|
|
const char * name = SDL_GetRendererName(renderer);
|
|
assert(name != NULL);
|
|
printf("renderer: %s\n", name);
|
|
const SDL_PixelFormatEnum * formats = SDL_GetProperty(props, SDL_PROP_RENDERER_TEXTURE_FORMATS_POINTER, NULL);
|
|
assert(formats != NULL);
|
|
while (*formats != SDL_PIXELFORMAT_UNKNOWN) {
|
|
printf("%s\n", SDL_GetPixelFormatName(*formats++));
|
|
}
|
|
|
|
int last_width = -1;
|
|
|
|
uint64_t ticks = SDL_GetTicks();
|
|
uint64_t last_ping_tick = 0;
|
|
const int min_length = 5;
|
|
int max_length = min_length;
|
|
|
|
/*
|
|
socket
|
|
*/
|
|
|
|
int sockfd = socket(AF_INET6, SOCK_DGRAM | SOCK_NONBLOCK, IPPROTO_UDP);
|
|
if (sockfd == -1) {
|
|
perror("socket");
|
|
return -1;
|
|
}
|
|
|
|
struct sockaddr_in6 sockaddr;
|
|
sockaddr.sin6_family = AF_INET6;
|
|
sockaddr.sin6_port = htons(PORT);
|
|
sockaddr.sin6_addr = in6addr_any;
|
|
|
|
ret = bind(sockfd, (struct sockaddr *)&sockaddr, (sizeof (struct sockaddr_in6)));
|
|
if (ret == -1) {
|
|
perror("bind");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
*/
|
|
|
|
struct timer_state timer_state = {0};
|
|
struct link_state link_state = {0};
|
|
struct gpio_state gpio_state = {0};
|
|
|
|
link_state.dest_addr.sin6_family = AF_INET6;
|
|
link_state.dest_addr.sin6_port = htons(DEST_PORT);
|
|
ret = inet_pton(AF_INET6, DEST_ADDR, &link_state.dest_addr.sin6_addr);
|
|
assert(ret == 1);
|
|
|
|
gpio_open("/dev/gpiochip0", &gpio_state);
|
|
// ignored gpio_open error
|
|
|
|
while (1) {
|
|
handle_sockfd(sockfd, &timer_state, &link_state);
|
|
|
|
char str_buf[20 * 2 + 1 + 1];
|
|
switch (timer_state.status) {
|
|
case TIMER_STOPPED:
|
|
{
|
|
char * bufi = str_buf;
|
|
bufi = format_base10(bufi,
|
|
timer_state.time.integer_value,
|
|
timer_state.time.integer_digits);
|
|
*bufi++ = '.';
|
|
bufi = format_base10(bufi,
|
|
timer_state.time.fraction_value,
|
|
timer_state.time.fraction_digits);
|
|
*bufi = 0;
|
|
}
|
|
break;
|
|
case TIMER_RUNNING:
|
|
{
|
|
struct timespec now;
|
|
int ret = clock_gettime(CLOCK_MONOTONIC_RAW, &now);
|
|
assert(ret != -1);
|
|
|
|
struct timespec real_start = timespec_sub(&timer_state.counter.start, &timer_state.counter.offset);
|
|
struct timespec duration = timespec_sub(&now, &real_start);
|
|
|
|
snprintf(str_buf, (sizeof (str_buf)) - 1,
|
|
"%ld.%02ld",
|
|
duration.tv_sec,
|
|
duration.tv_nsec / (1000000000 / 100)); // 2 digits
|
|
}
|
|
break;
|
|
default:
|
|
str_buf[0] = 0;
|
|
break;
|
|
}
|
|
int length = strlen(str_buf);
|
|
|
|
SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
|
|
SDL_RenderClear(renderer);
|
|
int window_width;
|
|
int window_height;
|
|
ret = SDL_GetWindowSizeInPixels(window, &window_width, &window_height);
|
|
assert(ret == 0);
|
|
if ((window_width != last_width) || (length > max_length)) {
|
|
max_length = max(min_length, length);
|
|
last_width = window_width;
|
|
|
|
int divisor = max(1, max_length);
|
|
int font_size = (window_width * 150 / 100) / divisor;
|
|
printf("fs %d %d max_length: %d\n", font_size, window_width, max_length);
|
|
load_font(renderer, font_size);
|
|
}
|
|
|
|
render(renderer, window_width, window_height, str_buf, length);
|
|
|
|
while (SDL_GetTicks() - ticks < (1000 / 60)) { SDL_Delay(1); }
|
|
SDL_RenderPresent(renderer);
|
|
ticks = SDL_GetTicks();
|
|
if (ticks - last_ping_tick > PING_INTERVAL * 1000) {
|
|
//printf("send ping\n");
|
|
int ret = packet_send_ping(sockfd, &link_state.dest_addr);
|
|
if (ret == -1) {
|
|
return -1;
|
|
}
|
|
last_ping_tick = ticks;
|
|
gpio_set_values_from_link_states(&gpio_state,
|
|
&link_state,
|
|
1);
|
|
}
|
|
|
|
SDL_Event event;
|
|
while (SDL_PollEvent(&event)) {
|
|
switch (event.type) {
|
|
case SDL_EVENT_QUIT:
|
|
goto exit;
|
|
case SDL_EVENT_KEY_DOWN:
|
|
if (event.key.keysym.sym == SDLK_ESCAPE)
|
|
goto exit;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
SDL_DestroyRenderer(renderer);
|
|
SDL_DestroyWindow(window);
|
|
SDL_Quit();
|
|
close(sockfd);
|
|
}
|