hot code reloading examples

pull/1054/head
Alexander Medvednikov 2019-07-07 21:46:21 +02:00
parent af19aa5096
commit de8dc4cddb
9 changed files with 210 additions and 70 deletions

View File

@ -115,6 +115,10 @@ fn new_fn(pkg string, is_public bool) *Fn {
fn (p mut Parser) fn_decl() {
p.fgen('fn ')
is_pub := p.tok == PUB
is_live := p.attr == 'live' && !p.pref.is_so
if is_live && !p.pref.is_live {
p.error('run `v -live program.v` if you want to use [live] functions')
}
if is_pub {
p.next()
}
@ -277,16 +281,12 @@ fn (p mut Parser) fn_decl() {
// }
mut fn_name_cgen := p.table.cgen_name(f)
// Start generation of the function body
is_live := p.pref.is_live && f.name != 'main' && f.name != 'reload_so'
skip_main_in_test := f.name == 'main' && p.pref.is_test
if !is_c && !is_live && !is_sig && !is_fn_header && !skip_main_in_test {
if p.pref.obfuscate {
p.genln('; // ${f.name}')
p.genln('; // $f.name')
}
p.genln('$typ $fn_name_cgen($str_args) {')
// if f.name == 'WinMain' {
// typ = 'int'
// }
}
if is_fn_header {
p.genln('$typ $fn_name_cgen($str_args);')
@ -328,8 +328,8 @@ fn (p mut Parser) fn_decl() {
}
}
// Live code reloading? Load all fns from .so
if is_live && p.first_run() {
// p.cgen.consts_init.push('$fn_name_cgen = dlsym(lib, "$fn_name_cgen");')
if is_live && p.first_run() && p.pkg == 'main' {
//println('ADDING SO FN $fn_name_cgen')
p.cgen.so_fns << fn_name_cgen
fn_name_cgen = '(* $fn_name_cgen )'
}
@ -361,8 +361,10 @@ fn (p mut Parser) fn_decl() {
}
// We are in live code reload mode, call the .so loader in bg
if p.pref.is_live {
file_base := p.file_path.replace('.v', '')
so_name := file_base + '.so'
p.genln('
load_so("bounce.so");
load_so("$so_name");
pthread_t _thread_so;
pthread_create(&_thread_so , NULL, &reload_so, NULL); ')
}

View File

@ -374,16 +374,47 @@ string _STR_TMP(const char *fmt, ...) {
cgen.genln('return g_test_ok == 0; }')
}
}
// Hot code reloading
if v.pref.is_live {
cgen.genln(' int load_so(byteptr path) {
printf("load_so %s\\n", path); dlclose(live_lib); live_lib = dlopen(path, RTLD_LAZY);
if (!live_lib) {puts("open failed"); exit(1); return 0;}
')
file := v.dir
file_base := v.dir.replace('.v', '')
so_name := file_base + '.so'
// Need to build .so file before building the live application
// The live app needs to load this .so file on initialization.
os.system('v -o $file_base -shared $file')
cgen.genln('
#include <dlfcn.h>
void* live_lib;
int load_so(byteptr path) {
//printf("load_so %s\\n", path);
if (live_lib) dlclose(live_lib);
live_lib = dlopen(path, RTLD_LAZY);
if (!live_lib) {puts("open failed"); exit(1); return 0;}
')
for so_fn in cgen.so_fns {
cgen.genln('$so_fn = dlsym(live_lib, "$so_fn"); ')
}
cgen.genln('return 1; }')
cgen.genln('return 1; }
void reload_so() {
int last = 0;
while (1) {
int now = os__file_last_mod_unix(tos2("$file"));
if (now != last) {
//v -o bounce -shared bounce.v
os__system(tos2("v -o $file_base -shared $file"));
last = now;
load_so("$so_name");
}
time__sleep_ms(1000);
}
}
' )
}
if v.pref.is_so {
cgen.genln(' int load_so(byteptr path) { return 0; }')
}
cgen.save()
if v.pref.is_verbose {
v.log('flags=')
@ -432,13 +463,13 @@ fn (c &V) cc_windows_cross() {
}
mut libs := ''
if c.pref.build_mode == .default_mode {
libs = '"$TmpPath/vlib/builtin.o"'
libs = '$TmpPath/vlib/builtin.o'
if !os.file_exists(libs) {
println('`builtin.o` not found')
exit(1)
}
for imp in c.table.imports {
libs += ' "$TmpPath/vlib/${imp}.o"'
libs += ' $TmpPath/vlib/${imp}.o'
}
}
args += ' $c.out_name_c '
@ -500,13 +531,11 @@ fn (v mut V) cc() {
v.log('cc() isprod=$v.pref.is_prod outname=$v.out_name')
mut a := ['-w', '-march=native']// arguments for the C compiler
flags := v.table.flags.join(' ')
/*
mut shared := ''
//mut shared := ''
if v.pref.is_so {
a << '-shared'// -Wl,-z,defs'
v.out_name = v.out_name + '.so'
}
*/
if v.pref.is_prod {
a << '-O2'
}
@ -521,7 +550,7 @@ fn (v mut V) cc() {
//
}
else if v.pref.build_mode == .default_mode {
libs = '"$TmpPath/vlib/builtin.o"'
libs = '$TmpPath/vlib/builtin.o'
if !os.file_exists(libs) {
println('`builtin.o` not found')
exit(1)
@ -530,7 +559,7 @@ fn (v mut V) cc() {
if imp == 'webview' {
continue
}
libs += ' "$TmpPath/vlib/${imp}.o"'
libs += ' $TmpPath/vlib/${imp}.o'
}
}
// -I flags
@ -562,7 +591,7 @@ mut args := ''
// else {
a << '-o $v.out_name'
// The C file we are compiling
a << '"$TmpPath/$v.out_name_c"'
a << '$TmpPath/$v.out_name_c'
// }
// Min macos version is mandatory I think?
if v.os == MAC {
@ -717,7 +746,7 @@ fn (v mut V) add_user_v_files() {
// Parse lib imports
if v.pref.build_mode == .default_mode {
for i := 0; i < v.table.imports.len; i++ {
pkg := v.module_path(v.table.imports[i])
pkg := v.table.imports[i]
vfiles := v.v_files_from_dir('$TmpPath/vlib/$pkg')
// Add all imports referenced by these libs
for file in vfiles {
@ -730,7 +759,7 @@ fn (v mut V) add_user_v_files() {
// TODO this used to crash compiler?
// for pkg in v.table.imports {
for i := 0; i < v.table.imports.len; i++ {
pkg := v.module_path(v.table.imports[i])
pkg := v.table.imports[i]
idir := os.getwd()
mut import_path := '$idir/$pkg'
if(!os.file_exists(import_path)) {
@ -749,8 +778,7 @@ fn (v mut V) add_user_v_files() {
println(v.table.imports)
}
// Only now add all combined lib files
for _pkg in v.table.imports {
pkg := v.module_path(_pkg)
for pkg in v.table.imports {
idir := os.getwd()
mut module_path := '$idir/$pkg'
// If we are in default mode, we don't parse vlib .v files, but header .vh files in
@ -791,15 +819,6 @@ fn get_arg(joined_args, arg, def string) string {
return res
}
fn (v &V) module_path(pkg string) string {
// submodule support
if pkg.contains('.') {
// return pkg.replace('.', path_sep)
return pkg.replace('.', '/')
}
return pkg
}
fn (v &V) log(s string) {
if !v.pref.is_verbose {
return

View File

@ -63,6 +63,7 @@ mut:
vroot string
is_c_struct_init bool
can_chash bool
attr string
}
const (
@ -179,7 +180,11 @@ fn (p mut Parser) parse() {
p.fn_decl()
case TIP:
p.type_decl()
case STRUCT, INTERFACE, UNION, LSBR:// `[` can only mean an [attribute] before the struct definition
case LSBR:
// `[` can only mean an [attribute] before a function
// or a struct definition
p.attribute()
case STRUCT, INTERFACE, UNION, LSBR:
p.struct_decl()
case CONST:
p.const_decl()
@ -192,8 +197,8 @@ fn (p mut Parser) parse() {
// $if, $else
p.comp_time()
case GLOBAL:
if !p.pref.translated && !p.builtin_pkg && !p.building_v() {
p.error('__global is only allowed in translated code')
if !p.pref.translated && !p.pref.is_live && !p.builtin_pkg && !p.building_v() {
//p.error('__global is only allowed in translated code')
}
p.next()
name := p.check_name()
@ -289,21 +294,7 @@ fn (p mut Parser) import_statement() {
if p.tok != NAME {
p.error('bad import format')
}
mut pkg := p.lit.trim_space()
// submodule support
// limit depth to 4 for now
max_module_depth := 4
mut depth := 1
for p.peek() == DOT {
p.next() // SKIP DOT
p.next() // SUBMODULE
submodule := p.lit.trim_space()
pkg = pkg + '.' + submodule
depth++
if depth > max_module_depth {
panic('Sorry. Module depth of $max_module_depth exceeded: $pkg ($submodule is too deep).')
}
}
pkg := p.lit.trim_space()
p.next()
p.fgenln(' ' + pkg)
// Make sure there are no duplicate imports
@ -1142,13 +1133,6 @@ fn (p mut Parser) var_decl() {
p.next()
p.check(LCBR)
p.genln('if (!$tmp .ok) {')
p.register_var(Var {
name: 'err'
typ: 'string'
is_mut: false
is_used: true
})
p.genln('string err = $tmp . error;')
p.statements()
p.genln('$typ $name = *($typ*) $tmp . data;')
if !p.returns && p.prev_tok2 != CONTINUE && p.prev_tok2 != BREAK {
@ -2658,13 +2642,6 @@ fn (p mut Parser) chash() {
if is_sig {
// p.cgen.nogen = true
}
if hash == 'live' {
if p.pref.is_so {
return
}
p.pref.is_live = true
return
}
if hash.starts_with('flag ') {
mut flag := hash.right(5)
// No the right os? Skip!
@ -3184,6 +3161,23 @@ fn (p &Parser) building_v() bool {
return p.file_path.contains('v/compiler') || cur_dir.contains('v/compiler')
}
fn (p mut Parser) attribute() {
p.check(LSBR)
p.attr = p.check_name()
p.check(RSBR)
if p.tok == FUNC {
p.fn_decl()
p.attr = ''
return
}
else if p.tok == STRUCT {
p.struct_decl()
p.attr = ''
return
}
p.error('bad attribute usage')
}
///////////////////////////////////////////////////////////////////////////////

View File

@ -626,7 +626,10 @@ fn (s mut Scanner) ident_char() string {
len--
c := s.text.substr(start + 1, s.pos)
if len != 1 {
u := c.ustring()
if u.len != 1 {
s.error('invalid character literal (more than one character: $len)')
}
}
return c
}

View File

@ -0,0 +1,88 @@
// Build this example with
// v -live bounce.v
module main
import gx
import gl
import gg
import glfw
import time
struct Game {
mut:
vg *gg.GG
x int
y int
dy int
dx int
height int
width int
main_wnd *glfw.Window
draw_fn voidptr
}
fn main() {
glfw.init()
width := 600
height := 300
mut game := &Game{
vg: 0
dx: 3
dy: 3
height: height
width: width
}
mut window := glfw.create_window(glfw.WinCfg {
width: width,
height: height,
borderless: false,
title: 'Hot code reloading demo'
ptr: game
})
//window.onkeydown(key_down)
game.main_wnd = window
window.make_context_current()
gg.init()
game.vg = gg.new_context(gg.Cfg {
width: width
height: height
font_size: 20
use_ortho: true
})
println('Starting game loop...')
go game.run()
for {
gl.clear()
gl.clear_color(255, 255, 255, 255)
game.draw()
window.swap_buffers()
glfw.wait_events()
}
}
const (
W = 50
)
//[live] TODO segfaults
fn (ctx &Game) draw() {
ctx.vg.draw_rect(ctx.x, ctx.y, W, W, gx.rgb(0, 0, 255))
}
fn (ctx mut Game) run() {
for {
ctx.x += ctx.dx
ctx.y += ctx.dy
if ctx.y >= ctx.height - W || ctx.y <= 0 {
ctx.dy = - ctx.dy
}
if ctx.x >= ctx.width - W || ctx.x <= 0 {
ctx.dx = - ctx.dx
}
// Refresh
time.sleep_ms(30)
glfw.post_empty_event()
}
}

View File

@ -0,0 +1,22 @@
// Build this example with
// v -live message.v
module main
import time
import os
[live]
fn print_message() {
println('Hello! Modify this message while the program is running.')
}
fn main() {
for {
print_message()
time.sleep_ms(500)
}
}

View File

@ -641,7 +641,7 @@ pub fn (s string) ustring_tmp() ustring {
return res
}
fn (u ustring) substr(start, end int) string {
pub fn (u ustring) substr(start, end int) string {
start = u.runes[start]
if end >= u.runes.len {
end = u.s.len
@ -652,11 +652,11 @@ fn (u ustring) substr(start, end int) string {
return u.s.substr(start, end)
}
fn (u ustring) left(pos int) string {
pub fn (u ustring) left(pos int) string {
return u.substr(0, pos)
}
fn (u ustring) right(pos int) string {
pub fn (u ustring) right(pos int) string {
return u.substr(pos, u.len)
}

View File

@ -66,6 +66,7 @@ import const (
struct C.stat {
st_size int
st_mode int
st_mtime int
}
struct C.DIR {
@ -640,6 +641,16 @@ pub fn signal(signum int, handler voidptr) {
C.signal(signum, handler)
}
pub fn file_last_mod_unix(path string) int {
attr := C.stat{}
//# struct stat attr;
C.stat(path.str, &attr)
//# stat(path.str, &attr);
return attr.st_mtime
//# return attr.st_mtime ;
}
fn log(s string) {
}

View File

@ -25,6 +25,7 @@ struct C.PGResult { }
fn C.PQconnectdb(a byteptr) *C.PGconn
fn C.PQerrorMessage(voidptr) byteptr
fn C.PQgetvalue(voidptr, int, int) byteptr
pub fn connect(dbname, user string) DB {
conninfo := 'host=localhost user=$user dbname=$dbname'
@ -46,7 +47,7 @@ fn res_to_rows(res voidptr) []pg.Row {
mut row := Row{}
for j := 0; j < nr_cols; j++ {
val := C.PQgetvalue(res, i, j)
row.vals << string(val)
row.vals << string(val)
}
rows << row
}