mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
live: use mostly pure V code for reloading, eases customization
This commit is contained in:
parent
b4e4e6bb21
commit
845ffb59a6
5
cmd/tools/preludes/live.v
Normal file
5
cmd/tools/preludes/live.v
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module main
|
||||||
|
|
||||||
|
// This prelude is loaded in every v program compiled with -live,
|
||||||
|
// in both the main executable, and in the shared library.
|
||||||
|
import live
|
@ -1,11 +1,5 @@
|
|||||||
module main
|
module main
|
||||||
|
|
||||||
import os
|
// This prelude is loaded in every v program compiled with -live,
|
||||||
import time
|
// but only for the main executable.
|
||||||
import dl
|
import live.executable
|
||||||
|
|
||||||
const (
|
|
||||||
os_used = os.MAX_PATH
|
|
||||||
time_used = time.now()
|
|
||||||
dl_used = dl.version
|
|
||||||
)
|
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
module main
|
module main
|
||||||
|
|
||||||
import os
|
// This prelude is loaded in every v program compiled with -live,
|
||||||
import time
|
// but only for the shared library.
|
||||||
|
import live.shared
|
||||||
const (
|
|
||||||
os_used = os.MAX_PATH
|
|
||||||
time_used = time.now()
|
|
||||||
)
|
|
||||||
|
@ -79,8 +79,8 @@ const (
|
|||||||
[live]
|
[live]
|
||||||
fn (game &Game) draw() {
|
fn (game &Game) draw() {
|
||||||
game.gg.draw_rect(game.x, game.y, width, width, blue)
|
game.gg.draw_rect(game.x, game.y, width, width, blue)
|
||||||
game.gg.draw_rect(550 - game.x + 10, 200 - game.y + 50, width, width, gx.rgb(128, 10, 255))
|
game.gg.draw_rect(550 - game.x + 10, 200 - game.y + 50, width, width, gx.rgb(228, 10, 55))
|
||||||
game.gg.draw_rect(game.x - 20, 250 - game.y, width, width, gx.rgb(128, 240, 155))
|
game.gg.draw_rect(game.x - 25, 250 - game.y, width, width, gx.rgb(28, 240, 55))
|
||||||
}
|
}
|
||||||
|
|
||||||
[live]
|
[live]
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
module main
|
module main
|
||||||
|
|
||||||
// Build this example with
|
// Build this example with `v -live message.v`
|
||||||
// v -live message.v
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
import live
|
||||||
|
|
||||||
[live]
|
[live]
|
||||||
fn print_message() {
|
fn print_message() {
|
||||||
println('Hello! Modify this message while the program is running.')
|
info := live.info()
|
||||||
|
println('OK reloads: ${info.reloads_ok:4d} | Total reloads: ${info.reloads:4d} | Hello! Modify this message while the program is running.')
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -4,6 +4,7 @@ module dl
|
|||||||
|
|
||||||
pub const (
|
pub const (
|
||||||
RTLD_NOW = C.RTLD_NOW
|
RTLD_NOW = C.RTLD_NOW
|
||||||
|
RTLD_LAZY = C.RTLD_LAZY
|
||||||
DL_EXT = '.so'
|
DL_EXT = '.so'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ module dl
|
|||||||
|
|
||||||
pub const (
|
pub const (
|
||||||
RTLD_NOW = 0
|
RTLD_NOW = 0
|
||||||
|
RTLD_LAZY = 0
|
||||||
DL_EXT = '.dll'
|
DL_EXT = '.dll'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
60
vlib/live/common.v
Normal file
60
vlib/live/common.v
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
module live
|
||||||
|
|
||||||
|
pub type FNLinkLiveSymbols = fn (linkcb voidptr)
|
||||||
|
|
||||||
|
pub type FNLiveReloadCB = fn (info &LiveReloadInfo)
|
||||||
|
|
||||||
|
pub struct LiveReloadInfo {
|
||||||
|
pub:
|
||||||
|
vexe string // full path to the v compiler
|
||||||
|
vopts string // v compiler options for a live shared library
|
||||||
|
original string // full path to the original source file, compiled with -live
|
||||||
|
live_fn_mutex voidptr // the address of the C mutex, that locks the [live] fns during reloads.
|
||||||
|
live_linkfn FNLinkLiveSymbols // generated C callback; receives a dlopen handle
|
||||||
|
so_extension string // .so or .dll
|
||||||
|
so_name_template string // a sprintf template for the shared libraries location
|
||||||
|
mut:
|
||||||
|
live_lib voidptr // the result of dl.open
|
||||||
|
reloads int // how many times a reloading was tried
|
||||||
|
reloads_ok int // how many times the reloads succeeded
|
||||||
|
reload_time_ms int // how much time the last reload took (compilation + loading)
|
||||||
|
last_mod_ts int // a timestamp for when the original was last changed
|
||||||
|
recheck_period_ms int = 100 // how often do you want to check for changes
|
||||||
|
cb_recheck FNLiveReloadCB = 0 // executed periodically
|
||||||
|
cb_compile_failed FNLiveReloadCB = 0 // executed when a reload compilation failed
|
||||||
|
cb_before FNLiveReloadCB = 0 // executed before a reload try happens
|
||||||
|
cb_after FNLiveReloadCB = 0 // executed after a reload try happened, even if failed
|
||||||
|
cb_locked_before FNLiveReloadCB = 0 // executed before lib reload, in the mutex section
|
||||||
|
cb_locked_after FNLiveReloadCB = 0 // executed after lib reload, in the mutex section
|
||||||
|
}
|
||||||
|
|
||||||
|
// LiveReloadInfo.live_linkfn should be called by the reloader
|
||||||
|
// to dlsym all live functions. TODO: research a way to implement
|
||||||
|
// live_linkfn in pure V, without complicating live code generation
|
||||||
|
// too much.
|
||||||
|
//
|
||||||
|
// The callbacks: cb_compile_fail, cb_before, cb_after will be
|
||||||
|
// executed outside the mutex protected section, so be careful,
|
||||||
|
// if you modify your data inside them. They can race with your
|
||||||
|
// [live] functions.
|
||||||
|
//
|
||||||
|
// cb_locked_before and cb_locked_after will be executed *inside*
|
||||||
|
// the mutex protected section. They can NOT race with your [live]
|
||||||
|
// functions. They should be very quick in what they do though,
|
||||||
|
// otherwise your live functions can be delayed.
|
||||||
|
//
|
||||||
|
// live.info - give user access to program's LiveReloadInfo struct,
|
||||||
|
// so that the user can set callbacks, read meta information, etc.
|
||||||
|
pub fn info() &LiveReloadInfo {
|
||||||
|
if C.g_live_info != 0 {
|
||||||
|
return C.g_live_info
|
||||||
|
}
|
||||||
|
// When the current program is not compiled with -live, simply
|
||||||
|
// return a new empty struct LiveReloadInfo in order to prevent
|
||||||
|
// crashes. In this case, the background reloader thread is not
|
||||||
|
// started, and the structure LiveReloadInfo will not get updated.
|
||||||
|
// All its fields will be 0, but still safe to access.
|
||||||
|
mut x := &LiveReloadInfo{}
|
||||||
|
C.g_live_info = voidptr(x)
|
||||||
|
return x
|
||||||
|
}
|
164
vlib/live/executable/reloader.v
Normal file
164
vlib/live/executable/reloader.v
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
module executable
|
||||||
|
|
||||||
|
import os
|
||||||
|
import time
|
||||||
|
import dl
|
||||||
|
import strconv
|
||||||
|
import live
|
||||||
|
|
||||||
|
// The live reloader code is implemented here.
|
||||||
|
|
||||||
|
fn C.pthread_mutex_unlock(mtx voidptr)
|
||||||
|
fn C.pthread_mutex_lock(mtx voidptr)
|
||||||
|
|
||||||
|
// NB: new_live_reload_info will be called by generated C code inside main()
|
||||||
|
pub fn new_live_reload_info(original string, vexe string, vopts string, live_fn_mutex voidptr, live_linkfn live.FNLinkLiveSymbols) &live.LiveReloadInfo {
|
||||||
|
file_base := os.file_name(original).replace('.v', '')
|
||||||
|
so_dir := os.cache_dir()
|
||||||
|
so_extension := dl.DL_EXT
|
||||||
|
/* $if msvc { so_extension = '.dll' } $else { so_extension = '.so' } */
|
||||||
|
return &live.LiveReloadInfo{
|
||||||
|
original: original
|
||||||
|
vexe: vexe
|
||||||
|
vopts: vopts
|
||||||
|
live_fn_mutex: live_fn_mutex
|
||||||
|
live_linkfn: live_linkfn
|
||||||
|
so_extension: so_extension
|
||||||
|
so_name_template: '${so_dir}/tmp.%d.${file_base}'
|
||||||
|
live_lib: 0
|
||||||
|
reloads: 0
|
||||||
|
reload_time_ms: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: start_reloader will be called by generated code inside main(), to start
|
||||||
|
// the hot code reloader thread. start_reloader is executed in the context of
|
||||||
|
// the original main thread.
|
||||||
|
pub fn start_reloader(r mut live.LiveReloadInfo) {
|
||||||
|
// The shared library should be loaded once in the main thread
|
||||||
|
// If that fails, the program would crash anyway, just provide
|
||||||
|
// an error message to the user and exit:
|
||||||
|
r.reloads++
|
||||||
|
_ := compile_and_reload_shared_lib(r) or {
|
||||||
|
eprintln( err )
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
go reloader(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
[if debuglive]
|
||||||
|
fn elog(r mut live.LiveReloadInfo, s string){
|
||||||
|
eprintln(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_and_reload_shared_lib(r mut live.LiveReloadInfo) ?bool {
|
||||||
|
sw := time.new_stopwatch()
|
||||||
|
new_lib_path := compile_lib(r) or {
|
||||||
|
return error('errors while compiling $r.original')
|
||||||
|
}
|
||||||
|
elog(r,'> compile_and_reload_shared_lib compiled: ${new_lib_path}')
|
||||||
|
load_lib(r, new_lib_path )
|
||||||
|
r.reload_time_ms = sw.elapsed().milliseconds()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_lib(r mut live.LiveReloadInfo) ?string {
|
||||||
|
new_lib_path, new_lib_path_with_extension := current_shared_library_path(r)
|
||||||
|
cmd := '$r.vexe $r.vopts -o $new_lib_path $r.original'
|
||||||
|
elog(r,'> compilation cmd: $cmd')
|
||||||
|
cwatch := time.new_stopwatch()
|
||||||
|
recompilation_result := os.exec( cmd ) or {
|
||||||
|
eprintln('recompilation failed')
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
elog(r,'compilation took: ${cwatch.elapsed().milliseconds()}ms')
|
||||||
|
if recompilation_result.exit_code != 0 {
|
||||||
|
eprintln('recompilation error:')
|
||||||
|
eprintln( recompilation_result.output )
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
if !os.exists( new_lib_path_with_extension ) {
|
||||||
|
eprintln('new_lib_path: $new_lib_path_with_extension does not exist')
|
||||||
|
return none
|
||||||
|
}
|
||||||
|
return new_lib_path_with_extension
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_shared_library_path(r mut live.LiveReloadInfo) (string, string) {
|
||||||
|
lib_path := strconv.v_sprintf(r.so_name_template.replace('\\', '\\\\'), r.reloads)
|
||||||
|
lib_path_with_extension := lib_path + r.so_extension
|
||||||
|
return lib_path, lib_path_with_extension
|
||||||
|
}
|
||||||
|
|
||||||
|
fn load_lib(r mut live.LiveReloadInfo, new_lib_path string) {
|
||||||
|
elog(r,'live mutex locking...')
|
||||||
|
C.pthread_mutex_lock(r.live_fn_mutex)
|
||||||
|
elog(r,'live mutex locked')
|
||||||
|
//
|
||||||
|
if r.cb_locked_before != 0 {
|
||||||
|
r.cb_locked_before( r )
|
||||||
|
}
|
||||||
|
//
|
||||||
|
protected_load_lib(r, new_lib_path)
|
||||||
|
//
|
||||||
|
r.reloads_ok++
|
||||||
|
if r.cb_locked_after != 0 {
|
||||||
|
r.cb_locked_after( r )
|
||||||
|
}
|
||||||
|
//
|
||||||
|
elog(r,'live mutex unlocking...')
|
||||||
|
C.pthread_mutex_unlock(r.live_fn_mutex)
|
||||||
|
elog(r,'live mutex unlocked')
|
||||||
|
}
|
||||||
|
|
||||||
|
fn protected_load_lib(r mut live.LiveReloadInfo, new_lib_path string) {
|
||||||
|
if r.live_lib != 0 {
|
||||||
|
dl.close( r.live_lib )
|
||||||
|
r.live_lib = 0
|
||||||
|
}
|
||||||
|
r.live_lib = dl.open(new_lib_path, dl.RTLD_LAZY)
|
||||||
|
if r.live_lib == 0 {
|
||||||
|
eprintln('opening $new_lib_path failed')
|
||||||
|
exit(1)
|
||||||
|
}
|
||||||
|
r.live_linkfn( r.live_lib )
|
||||||
|
elog(r,'> load_lib OK, new live_lib: $r.live_lib')
|
||||||
|
// removing the .so file from the filesystem after dlopen-ing
|
||||||
|
// it is safe, since it will still be mapped in memory
|
||||||
|
os.rm( new_lib_path )
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: r.reloader() is executed in a new, independent thread
|
||||||
|
fn reloader(r mut live.LiveReloadInfo) {
|
||||||
|
elog(r,'reloader, r: $r')
|
||||||
|
mut last_ts := os.file_last_mod_unix( r.original )
|
||||||
|
for {
|
||||||
|
if r.cb_recheck != 0 {
|
||||||
|
r.cb_recheck( r )
|
||||||
|
}
|
||||||
|
now_ts := os.file_last_mod_unix( r.original )
|
||||||
|
if last_ts != now_ts {
|
||||||
|
r.reloads++
|
||||||
|
last_ts = now_ts
|
||||||
|
r.last_mod_ts = last_ts
|
||||||
|
if r.cb_before != 0 {
|
||||||
|
r.cb_before( r )
|
||||||
|
}
|
||||||
|
compile_and_reload_shared_lib(r) or {
|
||||||
|
if r.cb_compile_failed != 0 {
|
||||||
|
r.cb_compile_failed( r )
|
||||||
|
}
|
||||||
|
if r.cb_after != 0 {
|
||||||
|
r.cb_after( r )
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if r.cb_after != 0 {
|
||||||
|
r.cb_after( r )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.recheck_period_ms > 0 {
|
||||||
|
time.sleep_ms(r.recheck_period_ms)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
vlib/live/shared/live_sharedlib.v
Normal file
3
vlib/live/shared/live_sharedlib.v
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module shared
|
||||||
|
|
||||||
|
import live
|
@ -151,6 +151,9 @@ pub fn (v Builder) get_user_files() []string {
|
|||||||
// See cmd/tools/preludes/README.md for more info about what preludes are
|
// See cmd/tools/preludes/README.md for more info about what preludes are
|
||||||
vroot := os.dir(pref.vexe_path())
|
vroot := os.dir(pref.vexe_path())
|
||||||
preludes_path := os.join_path(vroot, 'cmd', 'tools', 'preludes')
|
preludes_path := os.join_path(vroot, 'cmd', 'tools', 'preludes')
|
||||||
|
if v.pref.is_livemain || v.pref.is_liveshared {
|
||||||
|
user_files << os.join_path(preludes_path, 'live.v')
|
||||||
|
}
|
||||||
if v.pref.is_livemain {
|
if v.pref.is_livemain {
|
||||||
user_files << os.join_path(preludes_path, 'live_main.v')
|
user_files << os.join_path(preludes_path, 'live_main.v')
|
||||||
}
|
}
|
||||||
|
@ -171,6 +171,8 @@ extern wchar_t **_wenviron;
|
|||||||
#include <pthread.h>
|
#include <pthread.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// g_live_info is used by live.info()
|
||||||
|
void* g_live_info = NULL;
|
||||||
|
|
||||||
//============================== HELPER C MACROS =============================*/
|
//============================== HELPER C MACROS =============================*/
|
||||||
//#define tos4(s, slen) ((string){.str=(s), .len=(slen)})
|
//#define tos4(s, slen) ((string){.str=(s), .len=(slen)})
|
||||||
|
@ -6,69 +6,38 @@ import v.pref
|
|||||||
import v.util
|
import v.util
|
||||||
|
|
||||||
fn (g &Gen) generate_hotcode_reloading_declarations() {
|
fn (g &Gen) generate_hotcode_reloading_declarations() {
|
||||||
if g.pref.os != .windows {
|
if g.pref.os == .windows {
|
||||||
if g.pref.is_livemain {
|
|
||||||
g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;')
|
|
||||||
}
|
|
||||||
if g.pref.is_liveshared {
|
|
||||||
g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex;')
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if g.pref.is_livemain {
|
if g.pref.is_livemain {
|
||||||
g.hotcode_definitions.writeln('HANDLE live_fn_mutex = 0;')
|
g.hotcode_definitions.writeln('HANDLE live_fn_mutex = 0;')
|
||||||
}
|
}
|
||||||
if g.pref.is_liveshared {
|
if g.pref.is_liveshared {
|
||||||
g.hotcode_definitions.writeln('HANDLE live_fn_mutex;')
|
g.hotcode_definitions.writeln('HANDLE live_fn_mutex;')
|
||||||
|
}
|
||||||
g.hotcode_definitions.writeln('
|
g.hotcode_definitions.writeln('
|
||||||
void pthread_mutex_lock(HANDLE *m) {
|
void pthread_mutex_lock(HANDLE *m) {
|
||||||
WaitForSingleObject(*m, INFINITE);
|
WaitForSingleObject(*m, INFINITE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pthread_mutex_unlock(HANDLE *m) {
|
void pthread_mutex_unlock(HANDLE *m) {
|
||||||
ReleaseMutex(*m);
|
ReleaseMutex(*m);
|
||||||
}')
|
}
|
||||||
|
')
|
||||||
|
} else {
|
||||||
|
if g.pref.is_livemain {
|
||||||
|
g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex = PTHREAD_MUTEX_INITIALIZER;')
|
||||||
|
}
|
||||||
|
if g.pref.is_liveshared {
|
||||||
|
g.hotcode_definitions.writeln('pthread_mutex_t live_fn_mutex;')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (g &Gen) generate_hotcode_reloader_code() {
|
fn (g &Gen) generate_hotcode_reloader_code() {
|
||||||
if g.pref.is_liveshared {
|
if g.pref.is_liveshared {
|
||||||
g.hotcode_definitions.writeln('')
|
|
||||||
g.hotcode_definitions.writeln('int load_so(byteptr path) { return 0; }')
|
|
||||||
g.hotcode_definitions.writeln('')
|
g.hotcode_definitions.writeln('')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// Hot code reloading
|
// Hot code reloading
|
||||||
if g.pref.is_livemain {
|
if g.pref.is_livemain {
|
||||||
mut file := os.real_path(g.pref.path)
|
|
||||||
file_base := os.file_name(file).replace('.v', '')
|
|
||||||
// Need to build .so file before building the live application
|
|
||||||
// The live app needs to load this .so file on initialization.
|
|
||||||
mut vexe := pref.vexe_path()
|
|
||||||
mut so_dir := os.cache_dir()
|
|
||||||
if os.user_os() == 'windows' {
|
|
||||||
vexe = util.cescaped_path(vexe)
|
|
||||||
file = util.cescaped_path(file)
|
|
||||||
so_dir = util.cescaped_path(so_dir)
|
|
||||||
}
|
|
||||||
mut msvc := ''
|
|
||||||
if g.pref.ccompiler == 'msvc' {
|
|
||||||
msvc = '-cc msvc'
|
|
||||||
}
|
|
||||||
so_debug_flag := if g.pref.is_debug { '-cg' } else { '' }
|
|
||||||
cmd_compile_shared_library := '$vexe $msvc -cg -keepc $so_debug_flag -o ${so_dir}/${file_base} -sharedlive -shared $file'
|
|
||||||
if g.pref.is_verbose {
|
|
||||||
println(cmd_compile_shared_library)
|
|
||||||
}
|
|
||||||
ticks := time.ticks()
|
|
||||||
so_compilation_result := os.system(cmd_compile_shared_library)
|
|
||||||
if g.pref.is_verbose {
|
|
||||||
diff := time.ticks() - ticks
|
|
||||||
println('compiling shared library took $diff ms')
|
|
||||||
}
|
|
||||||
if so_compilation_result != 0 {
|
|
||||||
exit(1)
|
|
||||||
}
|
|
||||||
mut phd := ''
|
mut phd := ''
|
||||||
mut load_code := []string{}
|
mut load_code := []string{}
|
||||||
if g.pref.os != .windows {
|
if g.pref.os != .windows {
|
||||||
@ -83,181 +52,55 @@ fn (g &Gen) generate_hotcode_reloader_code() {
|
|||||||
phd = windows_hotcode_definitions_1
|
phd = windows_hotcode_definitions_1
|
||||||
}
|
}
|
||||||
g.hotcode_definitions.writeln(phd.replace('@LOAD_FNS@', load_code.join('\n')))
|
g.hotcode_definitions.writeln(phd.replace('@LOAD_FNS@', load_code.join('\n')))
|
||||||
g.hotcode_definitions.writeln('
|
|
||||||
|
|
||||||
void lfnmutex_print(char *s){
|
|
||||||
#if 0
|
|
||||||
fflush(stderr);
|
|
||||||
fprintf(stderr,">> live_fn_mutex: %p | %s\\n", &live_fn_mutex, s);
|
|
||||||
fflush(stderr);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void remove_so_file(char *s){
|
|
||||||
// removing the .so file from the filesystem after dlopen-ing it is safe, since it will still be mapped in memory.
|
|
||||||
#ifndef _WIN32
|
|
||||||
unlink(s);
|
|
||||||
#else
|
|
||||||
_unlink(s);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
int _live_reloads = 0;
|
|
||||||
void reload_so() {
|
|
||||||
char new_so_base[PATH_MAX] = {0};
|
|
||||||
char new_so_name[PATH_MAX] = {0};
|
|
||||||
char compile_cmd[PATH_MAX] = {0};
|
|
||||||
int last = os__file_last_mod_unix(tos2("$file"));
|
|
||||||
while (1) {
|
|
||||||
// TODO use inotify
|
|
||||||
int now = os__file_last_mod_unix(tos2("$file"));
|
|
||||||
if (now != last) {
|
|
||||||
last = now;
|
|
||||||
_live_reloads++;
|
|
||||||
|
|
||||||
//v -o bounce -sharedlive -shared bounce.v
|
|
||||||
snprintf(new_so_base, sizeof (new_so_base), "${so_dir}/tmp.%d.${file_base}", _live_reloads);
|
|
||||||
#ifdef _MSC_VER
|
|
||||||
snprintf(new_so_name, sizeof (new_so_name), "%s.dll", new_so_base);
|
|
||||||
#else
|
|
||||||
snprintf(new_so_name, sizeof (new_so_name), "%s.so", new_so_base);
|
|
||||||
#endif
|
|
||||||
snprintf(compile_cmd, sizeof (compile_cmd), "$vexe $msvc $so_debug_flag -cg -keepc -o %s -sharedlive -shared $file", new_so_base);
|
|
||||||
os__system(tos2(compile_cmd));
|
|
||||||
|
|
||||||
if( !os__exists(tos2(new_so_name)) ) {
|
|
||||||
puts("Errors while compiling $file\\n");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
lfnmutex_print("reload_so locking...");
|
|
||||||
pthread_mutex_lock(&live_fn_mutex);
|
|
||||||
lfnmutex_print("reload_so locked");
|
|
||||||
|
|
||||||
load_so(new_so_name);
|
|
||||||
remove_so_file( new_so_name );
|
|
||||||
|
|
||||||
lfnmutex_print("reload_so unlocking...");
|
|
||||||
pthread_mutex_unlock(&live_fn_mutex);
|
|
||||||
lfnmutex_print("reload_so unlocked");
|
|
||||||
}
|
|
||||||
time__sleep_ms(100);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
posix_hotcode_definitions_1 = '
|
posix_hotcode_definitions_1 = '
|
||||||
#include <limits.h>
|
void v_bind_live_symbols(void* live_lib){
|
||||||
#ifndef PATH_MAX
|
|
||||||
#define PATH_MAX 1024
|
|
||||||
#endif
|
|
||||||
void* live_lib = 0;
|
|
||||||
int load_so(byteptr path) {
|
|
||||||
char cpath[PATH_MAX] = {0};
|
|
||||||
int res = snprintf(cpath, sizeof(cpath), "%s", path);
|
|
||||||
if (res >= sizeof (cpath)) {
|
|
||||||
fprintf (stderr, "path is too long");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (live_lib) {
|
|
||||||
int closing_status = dlclose(live_lib);
|
|
||||||
//fprintf(stderr, "Closing status: %d\\n", closing_status);
|
|
||||||
live_lib = 0;
|
|
||||||
}
|
|
||||||
//fprintf (stderr, "Opening shared library at: %s\\n", cpath);
|
|
||||||
live_lib = dlopen(cpath, RTLD_LAZY);
|
|
||||||
if (!live_lib) {
|
|
||||||
fprintf(stderr, "open failed, reason: %s\\n", dlerror());
|
|
||||||
exit(1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
dlerror(); // clear errors
|
|
||||||
//fprintf(stderr, "live_lib: %p\\n", live_lib);
|
|
||||||
|
|
||||||
@LOAD_FNS@
|
@LOAD_FNS@
|
||||||
|
|
||||||
char *dlsym_error = dlerror();
|
|
||||||
if (dlsym_error != NULL) {
|
|
||||||
fprintf(stderr, "dlsym failed, reason: %s\\n", dlsym_error);
|
|
||||||
}
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
'
|
'
|
||||||
windows_hotcode_definitions_1 = '
|
windows_hotcode_definitions_1 = '
|
||||||
#ifndef PATH_MAX
|
void v_bind_live_symbols(void* live_lib){
|
||||||
#define PATH_MAX 1024
|
|
||||||
#endif
|
|
||||||
void pthread_mutex_lock(HANDLE *m) {
|
|
||||||
WaitForSingleObject(*m, INFINITE);
|
|
||||||
}
|
|
||||||
void pthread_mutex_unlock(HANDLE *m) {
|
|
||||||
ReleaseMutex(*m);
|
|
||||||
}
|
|
||||||
void* live_lib = NULL;
|
|
||||||
int load_so(byteptr path) {
|
|
||||||
char cpath[PATH_MAX];
|
|
||||||
int res = snprintf(cpath, sizeof(cpath), "%s", path);
|
|
||||||
if (res >= sizeof(cpath)) {
|
|
||||||
puts("path is too long\\n");
|
|
||||||
exit(1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (live_lib) FreeLibrary(live_lib);
|
|
||||||
live_lib = LoadLibraryA(cpath);
|
|
||||||
if (!live_lib) {
|
|
||||||
puts("open failed\\n");
|
|
||||||
exit(1);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@LOAD_FNS@
|
@LOAD_FNS@
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
'
|
'
|
||||||
)
|
)
|
||||||
|
|
||||||
//
|
|
||||||
|
|
||||||
fn (g &Gen) generate_hotcode_reloading_main_caller() {
|
fn (g &Gen) generate_hotcode_reloading_main_caller() {
|
||||||
if !g.pref.is_livemain {
|
if !g.pref.is_livemain {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
// We are in live code reload mode, so start the .so loader in the background
|
// We are in live code reload mode, so start the .so loader in the background
|
||||||
file_base := os.file_name(g.pref.path).replace('.v', '')
|
|
||||||
mut so_dir := os.cache_dir()
|
|
||||||
if os.user_os() == 'windows' {
|
|
||||||
so_dir = util.cescaped_path(so_dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
g.writeln('\t// live code initialization section:')
|
g.writeln('\t// live code initialization section:')
|
||||||
g.writeln('\t{')
|
g.writeln('\t{')
|
||||||
g.writeln('\t\t// initialization of live function pointers')
|
g.writeln('\t\t// initialization of live function pointers')
|
||||||
for fname in g.hotcode_fn_names {
|
for fname in g.hotcode_fn_names {
|
||||||
g.writeln('\t\timpl_live_${fname} = 0;')
|
g.writeln('\t\timpl_live_${fname} = 0;')
|
||||||
}
|
}
|
||||||
|
vexe := util.cescaped_path( pref.vexe_path() )
|
||||||
|
file := util.cescaped_path( g.pref.path )
|
||||||
|
msvc := if g.pref.ccompiler == 'msvc' { '-cc msvc' } else { '' }
|
||||||
|
so_debug_flag := if g.pref.is_debug { '-cg' } else { '' }
|
||||||
|
vopts := '$msvc $so_debug_flag -keepc -sharedlive -shared'
|
||||||
|
//
|
||||||
g.writeln('\t\t// start background reloading thread')
|
g.writeln('\t\t// start background reloading thread')
|
||||||
if g.pref.os != .windows {
|
if g.pref.os == .windows {
|
||||||
// unix:
|
|
||||||
so_name := file_base + '.so'
|
|
||||||
g.writeln('\t\tchar *live_library_name = "${so_dir}/$so_name";')
|
|
||||||
g.writeln('\t\tload_so(live_library_name);')
|
|
||||||
g.writeln('\t\tpthread_t _thread_so;')
|
|
||||||
g.writeln('\t\tpthread_create(&_thread_so , NULL, (void *)&reload_so, live_library_name);')
|
|
||||||
} else {
|
|
||||||
// windows:
|
|
||||||
so_extension := if g.pref.ccompiler == 'msvc' { '.dll' } else { '.so' }
|
|
||||||
so_name := file_base + so_extension
|
|
||||||
g.writeln('\t\tchar *live_library_name = "${so_dir}/$so_name";')
|
|
||||||
g.writeln('\t\tlive_fn_mutex = CreateMutexA(0, 0, 0);')
|
g.writeln('\t\tlive_fn_mutex = CreateMutexA(0, 0, 0);')
|
||||||
g.writeln('\t\tload_so(live_library_name);')
|
|
||||||
g.writeln('\t\tunsigned long _thread_so;')
|
|
||||||
g.writeln('\t\t_thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0);')
|
|
||||||
}
|
}
|
||||||
|
g.writeln('\t\tlive__LiveReloadInfo* live_info = live__executable__new_live_reload_info(')
|
||||||
|
g.writeln('\t\t\t\t\t tos2("$file"),')
|
||||||
|
g.writeln('\t\t\t\t\t tos2("$vexe"),')
|
||||||
|
g.writeln('\t\t\t\t\t tos2("$vopts"),')
|
||||||
|
g.writeln('\t\t\t\t\t &live_fn_mutex,')
|
||||||
|
g.writeln('\t\t\t\t\t v_bind_live_symbols')
|
||||||
|
g.writeln('\t\t);')
|
||||||
|
// g_live_info gives access to the LiveReloadInfo methods,
|
||||||
|
// to the custom user code, through calling v_live_info()
|
||||||
|
g.writeln('\t\t g_live_info = (void*)live_info;')
|
||||||
|
g.writeln('\t\tlive__executable__start_reloader(live_info);')
|
||||||
g.writeln('\t}\t// end of live code initialization section')
|
g.writeln('\t}\t// end of live code initialization section')
|
||||||
g.writeln('')
|
g.writeln('')
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user