hot code reloading examples
parent
af19aa5096
commit
de8dc4cddb
|
@ -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); ')
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
|
|
11
vlib/os/os.v
11
vlib/os/os.v
|
@ -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) {
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue