Setup spall profiling, did first optimizations!

This commit is contained in:
Edward R. Gonzalez 2024-03-11 02:05:18 -04:00
parent 304e710c16
commit 1656dffb67
23 changed files with 357 additions and 120 deletions

View File

@ -9,6 +9,7 @@ import "core:os"
import "core:slice"
import "core:strings"
import "core:time"
import "core:prof/spall"
import rl "vendor:raylib"
Path_Assets :: "../assets/"
@ -27,8 +28,11 @@ ModuleAPI :: struct {
}
@export
startup :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger )
startup :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger )
{
spall.SCOPED_EVENT( & prof.ctx, & prof.buffer, #procedure )
Memory_App.profiler = prof
startup_tick := time.tick_now()
logger_init( & Memory_App.logger, "Sectr", host_logger.file_path, host_logger.file )
@ -221,8 +225,11 @@ sectr_shutdown :: proc()
}
@export
reload :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger )
reload :: proc( prof : ^SpallProfiler, persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VArena, host_logger : ^ Logger )
{
spall.SCOPED_EVENT( & prof.ctx, & prof.buffer, #procedure )
Memory_App.profiler = prof
context.logger = to_odin_logger( & Memory_App.logger )
using Memory_App;
@ -234,13 +241,14 @@ reload :: proc( persistent_mem, frame_mem, transient_mem, files_buffer_mem : ^VA
context.allocator = persistent_allocator()
context.temp_allocator = transient_allocator()
state := get_state()
// Procedure Addresses are not preserved on hot-reload. They must be restored for persistent data.
// The only way to alleviate this is to either do custom handles to allocators
// Or as done below, correct containers using allocators on reload.
// Thankfully persistent dynamic allocations are rare, and thus we know exactly which ones they are.
font_provider_data := & get_state().font_provider_data
font_provider_data := & state.font_provider_data
font_provider_data.font_cache.hashes.allocator = persistent_slab_allocator()
font_provider_data.font_cache.entries.allocator = persistent_slab_allocator()
@ -257,73 +265,83 @@ swap :: proc( a, b : ^ $Type ) -> ( ^ Type, ^ Type ) {
@export
tick :: proc( host_delta_time : f64, host_delta_ns : Duration ) -> b32
{
profile( "Client Tick" )
context.logger = to_odin_logger( & Memory_App.logger )
state := get_state(); using state
client_tick := time.tick_now()
should_close : b32
// Setup Frame Slab
client_tick := time.tick_now()
{
alloc_error : AllocatorError
frame_slab, alloc_error = slab_init( & default_slab_policy, bucket_reserve_num = 0, allocator = frame_allocator() )
verify( alloc_error == .None, "Failed to allocate frame slab" )
profile("Work frame")
// Setup Frame Slab
{
alloc_error : AllocatorError
frame_slab, alloc_error = slab_init( & default_slab_policy, bucket_reserve_num = 0, allocator = frame_allocator() )
verify( alloc_error == .None, "Failed to allocate frame slab" )
}
context.allocator = frame_allocator()
context.temp_allocator = transient_allocator()
rl.PollInputEvents()
should_close = update( host_delta_time )
render()
rl.SwapScreenBuffer()
}
context.allocator = frame_allocator()
context.temp_allocator = transient_allocator()
rl.PollInputEvents()
result := update( host_delta_time )
render()
rl.SwapScreenBuffer()
config.engine_refresh_hz = uint(monitor_refresh_hz)
frametime_target_ms = 1.0 / f64(config.engine_refresh_hz) * S_To_MS
sub_ms_granularity_required := frametime_target_ms <= Frametime_High_Perf_Threshold_MS
frametime_delta_ns = time.tick_lap_time( & client_tick )
frametime_delta_ms = duration_ms( frametime_delta_ns )
frametime_delta_seconds = duration_seconds( frametime_delta_ns )
frametime_elapsed_ms = frametime_delta_ms + host_delta_time
if frametime_elapsed_ms < frametime_target_ms
// Timing
{
sleep_ms := frametime_target_ms - frametime_elapsed_ms
pre_sleep_tick := time.tick_now()
// profile("Client tick timing processing")
config.engine_refresh_hz = uint(monitor_refresh_hz)
frametime_target_ms = 1.0 / f64(config.engine_refresh_hz) * S_To_MS
sub_ms_granularity_required := frametime_target_ms <= Frametime_High_Perf_Threshold_MS
if sleep_ms > 0 {
thread_sleep( cast(Duration) sleep_ms * MS_To_NS )
// thread__highres_wait( sleep_ms )
}
frametime_delta_ns = time.tick_lap_time( & client_tick )
frametime_delta_ms = duration_ms( frametime_delta_ns )
frametime_delta_seconds = duration_seconds( frametime_delta_ns )
frametime_elapsed_ms = frametime_delta_ms + host_delta_time
sleep_delta_ns := time.tick_lap_time( & pre_sleep_tick)
sleep_delta_ms := duration_ms( sleep_delta_ns )
if frametime_elapsed_ms < frametime_target_ms
{
sleep_ms := frametime_target_ms - frametime_elapsed_ms
pre_sleep_tick := time.tick_now()
if sleep_delta_ms < sleep_ms {
// log( str_fmt_tmp("frametime sleep was off by: %v ms", sleep_delta_ms - sleep_ms ))
}
if sleep_ms > 0 {
thread_sleep( cast(Duration) sleep_ms * MS_To_NS )
// thread__highres_wait( sleep_ms )
}
frametime_elapsed_ms += sleep_delta_ms
for ; frametime_elapsed_ms < frametime_target_ms; {
sleep_delta_ns = time.tick_lap_time( & pre_sleep_tick)
sleep_delta_ms = duration_ms( sleep_delta_ns )
sleep_delta_ns := time.tick_lap_time( & pre_sleep_tick)
sleep_delta_ms := duration_ms( sleep_delta_ns )
if sleep_delta_ms < sleep_ms {
// log( str_fmt_tmp("frametime sleep was off by: %v ms", sleep_delta_ms - sleep_ms ))
}
frametime_elapsed_ms += sleep_delta_ms
for ; frametime_elapsed_ms < frametime_target_ms; {
sleep_delta_ns = time.tick_lap_time( & pre_sleep_tick)
sleep_delta_ms = duration_ms( sleep_delta_ns )
frametime_elapsed_ms += sleep_delta_ms
}
}
if frametime_elapsed_ms > 60.0 {
log( str_fmt_tmp("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning )
}
}
if frametime_elapsed_ms > 60.0 {
log( str_fmt_tmp("Big tick! %v ms", frametime_elapsed_ms), LogLevel.Warning )
}
return result
return should_close
}
@export
clean_frame :: proc()
{
// profile( #procedure)
state := get_state(); using state
context.logger = to_odin_logger( & Memory_App.logger )

View File

@ -44,8 +44,9 @@ Memory :: struct {
// Not for large memory env states
snapshot : MemorySnapshot,
replay : ReplayState,
logger : Logger,
replay : ReplayState,
logger : Logger,
profiler : ^SpallProfiler
}
persistent_allocator :: proc() -> Allocator {

View File

@ -8,7 +8,9 @@ import "core:os"
import rl "vendor:raylib"
Font_Largest_Px_Size :: 96
Font_Largest_Px_Size :: 32
Font_Size_Interval :: 2
// Font_Default :: ""
Font_Default :: FontID { 0, "" }
@ -52,7 +54,7 @@ FontDef :: struct {
// data : []u8,
default_size : i32,
size_table : [Font_Largest_Px_Size / 2] FontGlyphsRender,
size_table : [Font_Largest_Px_Size / Font_Size_Interval] FontGlyphsRender,
}
FontProviderData :: struct {
@ -61,6 +63,7 @@ FontProviderData :: struct {
font_provider_startup :: proc()
{
profile(#procedure)
state := get_state()
font_provider_data := & get_state().font_provider_data; using font_provider_data
@ -94,6 +97,7 @@ font_load :: proc( path_file : string,
desired_id : string = Font_Load_Gen_ID
) -> FontID
{
profile(#procedure)
font_provider_data := & get_state().font_provider_data; using font_provider_data
font_data, read_succeded : = os.read_entire_file( path_file, context.temp_allocator )
@ -114,7 +118,7 @@ font_load :: proc( path_file : string,
default_size = Font_Default_Point_Size
}
key := cast(u64) xxh32( transmute([]byte) desired_id )
key := cast(u64) crc32( transmute([]byte) desired_id )
def, set_error := zpl_hmap_set( & font_cache, key,FontDef {} )
verify( set_error == AllocatorError.None, "Failed to add new font entry to cache" )
@ -127,9 +131,10 @@ font_load :: proc( path_file : string,
// Render all sizes at once
// Note(Ed) : We only generate textures for even multiples of the font.
for font_size : i32 = 2; font_size <= Font_Largest_Px_Size; font_size += 2
for font_size : i32 = Font_Size_Interval; font_size <= Font_Largest_Px_Size; font_size += Font_Size_Interval
{
id := (font_size / 2) + (font_size % 2)
profile("font size render")
id := (font_size / Font_Size_Interval) + (font_size % Font_Size_Interval)
px_render := & def.size_table[id - 1]
using px_render
@ -170,16 +175,27 @@ to_rl_Font :: proc( id : FontID, size := Font_Use_Default_Size ) -> rl.Font
{
font_provider_data := & get_state().font_provider_data; using font_provider_data
even_size := math.round(size * 0.5) * 2.0
size := clamp( i32( even_size), 8, Font_Largest_Px_Size )
even_size := math.round(size * (1.0/f32(Font_Size_Interval))) * f32(Font_Size_Interval)
size := clamp( i32( even_size), 4, Font_Largest_Px_Size )
def := zpl_hmap_get( & font_cache, id.key )
size = size if size != i32(Font_Use_Default_Size) else def.default_size
id := (size / 2) + (size % 2)
id := (size / Font_Size_Interval) + (size % Font_Size_Interval)
px_render := & def.size_table[ id - 1 ]
// This is free for now perf wise... may have to move this out to on a setting change later.
rl.SetTextureFilter( px_render.texture, rl.TextureFilter.TRILINEAR )
if id <= 8 {
rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT )
}
else if id <= 14 {
rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT )
}
else if id <= 48 {
rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT )
}
else if id > 48 {
rl.SetTextureFilter( px_render.texture, rl.TextureFilter.POINT )
}
rl_font : rl.Font
rl_font.baseSize = px_render.size

View File

@ -18,7 +18,7 @@ import "base:runtime"
import c "core:c/libc"
import "core:dynlib"
import "core:hash"
// crc32 :: hash.crc32
crc32 :: hash.crc32
import "core:hash/xxhash"
xxh32 :: xxhash.XXH32
import fmt_io "core:fmt"

View File

@ -70,6 +70,7 @@ array_init_reserve :: proc
array_append :: proc( self : ^Array( $ Type), value : Type ) -> AllocatorError
{
// profile(#procedure)
if self.header.num == self.header.capacity
{
grow_result := array_grow( self, self.header.capacity )
@ -206,6 +207,7 @@ array_free :: proc( using self : Array( $ Type ) ) {
array_grow :: proc( using self : ^Array( $ Type ), min_capacity : u64 ) -> AllocatorError
{
profile(#procedure)
new_capacity := array_grow_formula( capacity )
if new_capacity < min_capacity {

View File

@ -84,6 +84,7 @@ zpl_hmap_destroy :: proc( using self : ^ HMapZPL( $ Type ) ) {
zpl_hmap_get :: proc( using self : ^ HMapZPL( $ Type ), key : u64 ) -> ^ Type
{
// profile(#procedure)
id := zpl_hmap_find( self, key ).entry_index
if id >= 0 {
return & entries.data[id].value
@ -113,6 +114,7 @@ zpl_hmap_grow :: proc( using self : ^ HMapZPL( $ Type ) ) -> AllocatorError {
zpl_hmap_rehash :: proc( ht : ^ HMapZPL( $ Type ), new_num : u64 ) -> AllocatorError
{
profile(#procedure)
// For now the prototype should never allow this to happen.
ensure( false, "ZPL HMAP IS REHASHING" )
last_added_index : i64
@ -183,6 +185,7 @@ zpl_hmap_remove_entry :: proc( using self : ^ HMapZPL( $ Type ), id : i64 ) {
zpl_hmap_set :: proc( using self : ^ HMapZPL( $ Type), key : u64, value : Type ) -> (^ Type, AllocatorError)
{
// profile(#procedure)
id : i64 = 0
find_result : HMapZPL_FindResult
@ -237,6 +240,7 @@ zpl_hmap_add_entry :: proc( using self : ^ HMapZPL( $ Type), key : u64 ) -> i64
zpl_hmap_find :: proc( using self : ^ HMapZPL( $ Type), key : u64 ) -> HMapZPL_FindResult
{
// profile(#procedure)
result : HMapZPL_FindResult = { -1, -1, -1 }
if hashes.num > 0 {

View File

@ -91,6 +91,7 @@ pool_destroy :: proc ( using self : Pool )
pool_allocate_buckets :: proc( using self : Pool, num_buckets : uint ) -> AllocatorError
{
profile(#procedure)
if num_buckets == 0 {
return .Invalid_Argument
}
@ -124,6 +125,7 @@ pool_allocate_buckets :: proc( using self : Pool, num_buckets : uint ) -> Alloca
pool_grab :: proc( using pool : Pool ) -> ( block : []byte, alloc_error : AllocatorError )
{
// profile(#procedure)
alloc_error = .None
// Check the free-list first for a block
@ -189,6 +191,7 @@ pool_grab :: proc( using pool : Pool ) -> ( block : []byte, alloc_error : Alloca
pool_release :: proc( self : Pool, block : []byte, loc := #caller_location )
{
// profile(#procedure)
if Pool_Check_Release_Object_Validity {
within_bucket := pool_validate_ownership( self, block )
verify( within_bucket, "Attempted to release data that is not within a bucket of this pool", location = loc )
@ -221,6 +224,7 @@ pool_reset :: proc( using pool : Pool )
pool_validate_ownership :: proc( using self : Pool, block : [] byte ) -> b32
{
profile(#procedure)
within_bucket := b32(false)
// Compiler Bug : Same as pool_reset

23
code/grime_profiler.odin Normal file
View File

@ -0,0 +1,23 @@
package sectr
import "base:runtime"
import "core:prof/spall"
SpallProfiler :: struct {
ctx : spall.Context,
buffer : spall.Buffer,
}
@(deferred_none=profile_end)
profile :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
spall._buffer_begin( & Memory_App.profiler.ctx, & Memory_App.profiler.buffer, name, "", loc )
}
profile_begin :: #force_inline proc "contextless" ( name : string, loc := #caller_location ) {
spall._buffer_begin( & Memory_App.profiler.ctx, & Memory_App.profiler.buffer, name, "", loc )
}
profile_end :: #force_inline proc "contextless" ()
{
spall._buffer_end( & Memory_App.profiler.ctx, & Memory_App.profiler.buffer)
}

View File

@ -103,6 +103,7 @@ slab_alloc :: proc( using self : Slab,
loc := #caller_location
) -> ( data : []byte, alloc_error : AllocatorError )
{
// profile(#procedure)
pool : Pool
for id in 0 ..< pools.idx {
pool = pools.items[id]
@ -130,6 +131,7 @@ slab_alloc :: proc( using self : Slab,
slab_free :: proc( using self : Slab, data : []byte, loc := #caller_location )
{
// profile(#procedure)
pool : Pool
for id in 0 ..< pools.idx
{
@ -150,6 +152,7 @@ slab_resize :: proc( using self : Slab,
loc := #caller_location
) -> ( new_data : []byte, alloc_error : AllocatorError )
{
// profile(#procedure)
old_size := uint( len(data))
pool_resize, pool_old : Pool

View File

@ -62,30 +62,35 @@ str_intern :: proc(
content : string
) -> StringCached
{
// profile(#procedure)
cache := get_state().string_cache
key := u64( xxh32( transmute([]byte) content ))
key := u64( crc32( transmute([]byte) content ))
result := zpl_hmap_get( & cache.table, key )
if result != nil {
return (result ^)
}
length := len(content)
// str_mem, alloc_error := alloc( length, mem.DEFAULT_ALIGNMENT )
str_mem, alloc_error := slab_alloc( cache.slab, uint(length), uint(mem.DEFAULT_ALIGNMENT) )
verify( alloc_error == .None, "String cache had a backing allocator error" )
// profile_begin("new entry")
{
length := len(content)
// str_mem, alloc_error := alloc( length, mem.DEFAULT_ALIGNMENT )
str_mem, alloc_error := slab_alloc( cache.slab, uint(length), uint(mem.DEFAULT_ALIGNMENT) )
verify( alloc_error == .None, "String cache had a backing allocator error" )
// copy_non_overlapping( str_mem, raw_data(content), length )
copy_non_overlapping( raw_data(str_mem), raw_data(content), length )
// copy_non_overlapping( str_mem, raw_data(content), length )
copy_non_overlapping( raw_data(str_mem), raw_data(content), length )
runes : []rune
// runes, alloc_error = to_runes( content, persistent_allocator() )
runes, alloc_error = to_runes( content, slab_allocator(cache.slab) )
verify( alloc_error == .None, "String cache had a backing allocator error" )
runes : []rune
// runes, alloc_error = to_runes( content, persistent_allocator() )
runes, alloc_error = to_runes( content, slab_allocator(cache.slab) )
verify( alloc_error == .None, "String cache had a backing allocator error" )
// result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) byte_slice(str_mem, length), runes } )
result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) str_mem, runes } )
verify( alloc_error == .None, "String cache had a backing allocator error" )
// result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) byte_slice(str_mem, length), runes } )
result, alloc_error = zpl_hmap_set( & cache.table, key, StringCached { transmute(string) str_mem, runes } )
verify( alloc_error == .None, "String cache had a backing allocator error" )
}
// profile_end()
return (result ^)
}

View File

@ -132,6 +132,7 @@ varena_alloc :: proc( using self : ^VArena,
needs_more_committed := commit_left < size_to_allocate
if needs_more_committed
{
profile("VArena Growing")
next_commit_size := growth_policy( commit_used, committed, reserved, size_to_allocate )
alloc_error = virtual_commit( vmem, next_commit_size )
if alloc_error != .None {

View File

@ -59,6 +59,7 @@ import "core:time"
Duration :: time.Duration
duration_seconds :: time.duration_seconds
thread_sleep :: time.sleep
import "core:prof/spall"
import rl "vendor:raylib"
import sectr "../."
VArena :: sectr.VArena
@ -70,6 +71,7 @@ import sectr "../."
logger_init :: sectr.logger_init
LogLevel :: sectr.LogLevel
log :: sectr.log
SpallProfiler :: sectr.SpallProfiler
to_odin_logger :: sectr.to_odin_logger
TrackedAllocator :: sectr.TrackedAllocator
tracked_allocator :: sectr.tracked_allocator
@ -108,8 +110,9 @@ ClientMemory :: struct {
files_buffer : VArena,
}
setup_memory :: proc() -> ClientMemory
setup_memory :: proc( profiler : ^SpallProfiler ) -> ClientMemory
{
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure )
memory : ClientMemory; using memory
// Setup the static arena for the entire application
@ -211,8 +214,10 @@ unload_sectr_api :: proc( module : ^ sectr.ModuleAPI )
log("Unloaded sectr API")
}
sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, logger : ^Logger )
sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, logger : ^Logger, profiler : ^SpallProfiler )
{
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure )
if write_time, result := os.last_write_time_by_name( Path_Sectr_Module );
result == os.ERROR_NONE && sectr_api.write_time != write_time
{
@ -223,10 +228,11 @@ sync_sectr_api :: proc( sectr_api : ^sectr.ModuleAPI, memory : ^ClientMemory, lo
for ; file_is_locked( Path_Sectr_Debug_Symbols ) && file_is_locked( Path_Sectr_Live_Module ); {}
thread_sleep( Millisecond * 100 )
sectr_api ^ = load_sectr_api( version_id )
(sectr_api ^) = load_sectr_api( version_id )
verify( sectr_api.lib_version != 0, "Failed to hot-reload the sectr module" )
sectr_api.reload(
profiler,
& memory.persistent,
& memory.frame,
& memory.transient,
@ -240,6 +246,15 @@ main :: proc()
state : RuntimeState
using state
// Setup profiling
profiler : SpallProfiler
{
buffer_backing := make([]u8, spall.BUFFER_DEFAULT_SIZE)
profiler.ctx = spall.context_create("sectr.spall")
profiler.buffer = spall.buffer_create(buffer_backing)
}
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, #procedure )
// Generating the logger's name, it will be used when the app is shutting down.
path_logger_finalized : string
{
@ -268,7 +283,7 @@ main :: proc()
log( to_str(builder) )
}
memory := setup_memory()
memory := setup_memory( & profiler )
// TODO(Ed): Cannot use the manually created allocators for the host. Not sure why
// Something is wrong with the tracked_allocator init
@ -286,6 +301,7 @@ main :: proc()
running = true;
sectr_api = sectr_api
sectr_api.startup(
& profiler,
& memory.persistent,
& memory.frame,
& memory.transient,
@ -299,8 +315,10 @@ main :: proc()
// TODO(Ed) : This should have an end status so that we know the reason the engine stopped.
for ; running ;
{
spall.SCOPED_EVENT( & profiler.ctx, & profiler.buffer, "Host Tick" )
// Hot-Reload
sync_sectr_api( & sectr_api, & memory, & logger )
sync_sectr_api( & sectr_api, & memory, & logger, & profiler )
running = sectr_api.tick( duration_seconds( delta_ns ), delta_ns )
sectr_api.clean_frame()
@ -318,6 +336,9 @@ main :: proc()
sectr_api.shutdown()
unload_sectr_api( & sectr_api )
spall.buffer_destroy( & profiler.ctx, & profiler.buffer )
spall.context_destroy( & profiler.ctx )
log("Succesfuly closed")
file_close( logger.file )
// TODO(Ed) : Add string interning!!!!!!!!!

View File

@ -290,6 +290,7 @@ import rl "vendor:raylib"
poll_input :: proc( old, new : ^ InputState )
{
profile(#procedure)
input_process_digital_btn :: proc( old_state, new_state : ^ DigitalBtn, is_down : b32 )
{
new_state.ended_down = is_down
@ -304,6 +305,7 @@ poll_input :: proc( old, new : ^ InputState )
// Keyboard
{
// profile("Keyboard")
check_range :: proc( old, new : ^ InputState, start, end : i32 )
{
for id := start; id < end; id += 1
@ -332,6 +334,7 @@ poll_input :: proc( old, new : ^ InputState )
// Mouse
{
// profile("Mouse")
// Process Buttons
for id : i32 = 0; id < i32(MouseBtn.count); id += 1
{

View File

@ -49,3 +49,12 @@ add_range2 :: #force_inline proc "contextless" ( a, b : Range2 ) -> Range2 {
}}
return result
}
equal_range2 :: #force_inline proc "contextless" ( a, b : Range2 ) -> b32 {
result := a.p0 == b.p0 && a.p1 == b.p1
return b32(result)
}
size_range2 :: #force_inline proc "contextless" ( value : Range2 ) -> Vec2 {
return { value.p1.x - value.p0.x, value.p0.y - value.p1.y }
}

View File

@ -118,6 +118,7 @@ PWS_LexerData :: struct {
pws_parser_lex :: proc ( text : string, allocator : Allocator ) -> ( PWS_LexResult, AllocatorError )
{
profile(#procedure)
using lexer : PWS_LexerData
context.user_ptr = & lexer
content = text
@ -234,6 +235,7 @@ PWS_ParseData :: struct {
pws_parser_parse :: proc( text : string, allocator : Allocator ) -> ( PWS_ParseResult, AllocatorError )
{
profile(#procedure)
using parser : PWS_ParseData
context.user_ptr = & result

View File

@ -6,6 +6,7 @@ import rl "vendor:raylib"
debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
{
// profile(#procedure)
state := get_state(); using state
if len( content ) == 0 {
@ -35,6 +36,7 @@ debug_draw_text :: proc( content : string, pos : Vec2, size : f32, color : rl.Co
draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
{
// profile(#procedure)
state := get_state(); using state
if len( content ) == 0 {
@ -62,7 +64,9 @@ draw_text_string :: proc( content : string, pos : Vec2, size : f32, color : rl.C
tint = color );
}
draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default ) {
draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32, color : rl.Color = rl.WHITE, font : FontID = Font_Default )
{
// profile(#procedure)
state := get_state(); using state
if len( content.str ) == 0 {
@ -91,6 +95,7 @@ draw_text_string_cached :: proc( content : StringCached, pos : Vec2, size : f32,
// So this is a 1:1 copy except it takes Odin strings
measure_text_size :: proc( text : string, font : FontID, font_size := Font_Use_Default_Size, spacing : f32 ) -> Vec2
{
// profile(#procedure)
px_size := math.round( points_to_pixels( font_size ) )
rl_font := to_rl_Font( font, font_size )

View File

@ -6,6 +6,7 @@ import rl "vendor:raylib"
render :: proc()
{
profile(#procedure)
state := get_state(); using state
replay := & Memory_App.replay
cam := & project.workspace.cam
@ -21,6 +22,7 @@ render :: proc()
render_mode_2d()
//region Render Screenspace
{
profile("Render Screenspace")
fps_msg := str_fmt_tmp( "FPS: %f", 1 / (frametime_elapsed_ms * MS_To_S) )
fps_msg_width := measure_text_size( fps_msg, default_font, 16.0, 0.0 ).x
fps_msg_pos := screen_get_corners().top_right - { fps_msg_width, 0 }
@ -95,6 +97,7 @@ render :: proc()
render_mode_2d :: proc()
{
profile(#procedure)
state := get_state(); using state
cam := & project.workspace.cam
@ -108,6 +111,7 @@ render_mode_2d :: proc()
ImguiRender:
{
profile("Imgui Render")
ui := & state.project.workspace.ui
root := ui.root
if root.num_children == 0 {
@ -115,7 +119,9 @@ render_mode_2d :: proc()
}
current := root.first
for ; current != nil; {
for ; current != nil;
{
profile("Box")
parent := current.parent
style := current.style
@ -125,6 +131,7 @@ render_mode_2d :: proc()
// TODO(Ed) : Render Borders
// profile_begin("Calculating Raylib rectangles")
render_bounds := Range2 { pts = {
world_to_screen_pos(computed.bounds.min),
world_to_screen_pos(computed.bounds.max),
@ -157,15 +164,47 @@ render_mode_2d :: proc()
render_content.max.x - render_content.min.x,
render_content.max.y - render_content.min.y,
}
// profile_end()
rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color )
draw_rectangle :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRounded( rect, style.layout.corner_radii[0], 9, style.bg_color )
}
else {
rl.DrawRectangleRec( rect, style.bg_color )
}
}
draw_rectangle_lines :: #force_inline proc "contextless" ( rect : rl.Rectangle, style : UI_Style, color : Color, thickness : f32 ) {
if style.layout.corner_radii[0] > 0 {
rl.DrawRectangleRoundedLines( rect, style.layout.corner_radii[0], 9, thickness, color )
}
else {
rl.DrawRectangleLinesEx( rect, thickness, color )
}
}
// profile_begin("rl.DrawRectangleRounded( rect_bounds, style.layout.corner_radii[0], 9, style.bg_color )")
if style.bg_color.a != 0
{
draw_rectangle( rect_bounds, style )
}
// profile_end()
line_thickness := 1 * cam_zoom_ratio
rl.DrawRectangleRoundedLines( rect_padding, style.layout.corner_radii[0], 9, line_thickness, Color_Debug_UI_Padding_Bounds )
rl.DrawRectangleRoundedLines( rect_content, style.layout.corner_radii[0], 9, line_thickness, Color_Debug_UI_Content_Bounds )
if .Mouse_Resizable in current.flags
// profile_begin("rl.DrawRectangleRoundedLines: padding & content")
if equal_range2(computed.content, computed.padding) {
// draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Padding_Bounds, line_thickness )
}
else {
// draw_rectangle_lines( rect_padding, style, Color_Debug_UI_Content_Bounds, line_thickness )
}
// profile_end()
if .Mouse_Resizable in current.flags && false
{
// profile("Resize Bounds")
resize_border_width := cast(f32) get_state().config.ui_resize_border_width
resize_percent_width := style.size * (resize_border_width * 1.0/ 200.0)
resize_border_non_range := add(current.computed.bounds, range2(
@ -182,12 +221,16 @@ render_mode_2d :: proc()
render_resize.max.x - render_resize.min.x,
render_resize.max.y - render_resize.min.y,
}
rl.DrawRectangleRoundedLines( rect_resize, style.layout.corner_radii[0], 9, line_thickness, Color_Red )
// rl.DrawRectangleRoundedLines( rect_resize, style.layout.corner_radii[0], 9, line_thickness, Color_Red )
draw_rectangle_lines( rect_padding, style, Color_Red, line_thickness )
}
point_radius := 3 * cam_zoom_ratio
rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red )
rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue )
point_radius := 2 * cam_zoom_ratio
// profile_begin("circles")
// rl.DrawCircleV( render_bounds.p0, point_radius, Color_Red )
// rl.DrawCircleV( render_bounds.p1, point_radius, Color_Blue )
// profile_end()
if len(current.text.str) > 0 {
draw_text_string_cached( current.text, world_to_screen_pos(computed.text_pos), style.font_size, style.text_color )

View File

@ -29,6 +29,7 @@ DebugActions :: struct {
poll_debug_actions :: proc( actions : ^ DebugActions, input : ^ InputState )
{
// profile(#procedure)
using actions
using input
@ -64,6 +65,7 @@ frametime_delta32 :: #force_inline proc "contextless" () -> f32 {
update :: proc( delta_time : f64 ) -> b32
{
profile(#procedure)
state := get_state(); using state
replay := & Memory_App.replay
workspace := & project.workspace
@ -146,6 +148,7 @@ update :: proc( delta_time : f64 ) -> b32
//region Camera Manual Nav
{
// profile("Camera Manual Nav")
digital_move_speed : f32 = 200.0
if workspace.zoom_target == 0.0 {
@ -190,6 +193,8 @@ update :: proc( delta_time : f64 ) -> b32
//region Imgui Tick
{
profile("Imgui Tick")
// Creates the root box node, set its as the first parent.
ui_graph_build( & state.project.workspace.ui )
ui := ui_context
@ -236,17 +241,27 @@ update :: proc( delta_time : f64 ) -> b32
// Whitespace AST test
when true
{
profile("Whitespace AST test")
text_style := frame_style_default
text_style.flags = {
.Fixed_Position_X, .Fixed_Position_Y,
// .Fixed_Width, .Fixed_Height,
}
text_theme := UI_StyleTheme { styles = {
frame_style_default,
frame_style_default,
frame_style_default,
frame_style_default,
text_style,
text_style,
text_style,
text_style,
}}
text_theme.default.bg_color = Color_Transparent
text_theme.disabled.bg_color = Color_Frame_Disabled
text_theme.hovered.bg_color = Color_Frame_Hover
text_theme.focused.bg_color = Color_Frame_Select
layout_text := default_layout
ui_style_theme( text_theme )
alloc_error : AllocatorError; success : bool
@ -267,6 +282,8 @@ update :: proc( delta_time : f64 ) -> b32
for line in array_to_slice_num( debug.lorem_parse.lines )
{
profile("WS AST Line")
head := line.first
for ; head != nil;
{
@ -282,32 +299,30 @@ update :: proc( delta_time : f64 ) -> b32
widget = ui_text( label.str, head.content )
label_id += 1
layout_text.pos.x += widget.style.layout.size.x
layout_text.pos.x += size_range2( widget.computed.bounds ).x
case .Spaces:
label := str_intern( str_fmt_alloc( "%v %v", "space", label_id ))
// widget = ui_text( label.str, text_space, {} )
// widget.style.layout.size = Vec2 { 1, 16 }
widget = ui_space( label.str )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
widget.style.layout.size.x += widget.style.layout.size.x
// TODO(Ed): VIRTUAL WHITESPACE
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
layout_text.pos.x += widget.style.layout.size.x
layout_text.pos.x += size_range2( widget.computed.bounds ).x
case .Tabs:
label := str_intern( str_fmt_alloc( "%v %v", "tab", label_id ))
// widget = ui_text( label.str, text_tab, {} )
widget = ui_tab( label.str )
label_id += 1
for idx in 1 ..< len( head.content.runes )
{
widget.style.layout.size.x += widget.style.layout.size.x
// widget.style.layout.size.x += range2_size( widget.computed.bounds )
}
layout_text.pos.x += widget.style.layout.size.x
layout_text.pos.x += size_range2( widget.computed.bounds ).x
}
array_append( widgets_ptr, widget )

View File

@ -144,12 +144,14 @@ UI_Layout :: struct {
pos : Vec2,
// TODO(Ed) : Should everything no matter what its parent is use a WS_Pos instead of a raw vector pos?
size : Vec2,
// If the box is a child of the root parent, its automatically in world space and thus will use the tile_pos.
tile_pos : WS_Pos,
// TODO(Ed) : Add support for size_to_content?
// size_to_content : b32,
size : Vec2,
size_to_text : b8,
// size_to_content : b8,
}
UI_Signal :: struct {
@ -341,6 +343,8 @@ ui_box_from_key :: proc( cache : ^HMapZPL(UI_Box), key : UI_Key ) -> (^UI_Box) {
ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
{
// profile(#procedure)
using ui := get_state().ui_context
key := ui_key_from_string( label )
@ -348,6 +352,8 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
curr_box : (^ UI_Box)
prev_box := zpl_hmap_get( prev_cache, cast(u64) key )
{
// profile("Assigning current box")
set_result : ^ UI_Box
set_error : AllocatorError
if prev_box != nil {
@ -388,7 +394,8 @@ ui_box_make :: proc( flags : UI_BoxFlags, label : string ) -> (^ UI_Box)
return curr_box
}
ui_box_tranverse_next :: proc( box : ^ UI_Box ) -> (^ UI_Box) {
ui_box_tranverse_next :: #force_inline proc "contextless" ( box : ^ UI_Box ) -> (^ UI_Box)
{
// If current has children, do them first
if box.first != nil {
return box.first
@ -423,6 +430,8 @@ ui_drag_delta :: #force_inline proc "contextless" () -> Vec2 {
ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
{
profile(#procedure)
get_state().ui_context = ui
using get_state().ui_context
@ -444,6 +453,8 @@ ui_graph_build_begin :: proc( ui : ^ UI_State, bounds : Vec2 = {} )
// TODO(Ed) :: Is this even needed?
ui_graph_build_end :: proc()
{
profile(#procedure)
ui_parent_pop() // Should be ui_context.root
// Regenerate the computed layout if dirty
@ -457,8 +468,25 @@ ui_graph_build :: proc( ui : ^ UI_State ) {
ui_graph_build_begin( ui )
}
ui_key_from_string :: proc( value : string ) -> UI_Key {
key := cast(UI_Key) xxh32( transmute([]byte) value )
ui_key_from_string :: proc( value : string ) -> UI_Key
{
// profile(#procedure)
USE_RAD_DEBUGGERS_METHOD :: true
key : UI_Key
when USE_RAD_DEBUGGERS_METHOD {
hash : u64
for str_byte in transmute([]byte) value {
hash = ((hash << 5) + hash) + u64(str_byte)
}
key = cast(UI_Key) hash
}
when ! USE_RAD_DEBUGGERS_METHOD {
key = cast(UI_Key) crc32( transmute([]byte) value )
}
return key
}

View File

@ -2,6 +2,7 @@ package sectr
ui_compute_layout :: proc()
{
profile(#procedure)
state := get_state()
root := state.project.workspace.ui.root
@ -21,6 +22,7 @@ ui_compute_layout :: proc()
current := root.first
for ; current != nil;
{
profile("Layout Box")
parent := current.parent
parent_content := parent.computed.content
computed := & current.computed
@ -46,6 +48,15 @@ ui_compute_layout :: proc()
pos.y += margins.p1.y * anchor.y1
}
text_size : Vec2
// If the computed matches, we alreayd have the size, don't bother.
// if computed.text_size.y == style.font_size {
if current.first_frame || ! style.size_to_text || computed.text_size.y != size_range2(computed.bounds).y {
text_size = cast(Vec2) measure_text_size( current.text.str, style.font, style.font_size, 0 )
} else {
text_size = computed.text_size
}
size : Vec2
if UI_StyleFlag.Fixed_Width in style.flags {
size.x = layout.size.x
@ -53,6 +64,7 @@ ui_compute_layout :: proc()
else {
// TODO(Ed) : Not sure what todo here...
}
if UI_StyleFlag.Fixed_Height in style.flags {
size.y = layout.size.y
}
@ -60,6 +72,10 @@ ui_compute_layout :: proc()
// TODO(Ed) : Not sure what todo here...
}
if style.size_to_text {
size = text_size
}
half_size := size * 0.5
size_bounds := range2(
Vec2 {},
@ -94,12 +110,11 @@ ui_compute_layout :: proc()
// Text
if len(current.text.str) > 0
{
// profile("Text")
top_left := content.p0
bottom_right := content.p1
content_size := Vec2 { top_left.x - bottom_right.x, top_left.y - bottom_right.y }
text_size := cast(Vec2) measure_text_size( current.text.str, style.font, style.font_size, 0 )
text_pos : Vec2
text_pos = top_left
text_pos.x += (-content_size.x - text_size.x) * layout.text_alignment.x

View File

@ -2,6 +2,7 @@ package sectr
ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
{
// profile(#procedure)
ui := get_state().ui_context
input := get_state().input
@ -10,6 +11,7 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
signal := UI_Signal {}
// Cursor Collision
// profile_begin( "Cursor collision")
signal.cursor_pos = ui_cursor_pos()
signal.cursor_over = cast(b8) pos_within_range2( signal.cursor_pos, box.computed.bounds )
@ -22,7 +24,9 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
within_resize_range := cast(b8) ! pos_within_range2( signal.cursor_pos, resize_border_non_range )
within_resize_range &= signal.cursor_over
within_resize_range &= .Mouse_Resizable in box.flags
// profile_end()
// profile_begin("misc")
left_pressed := pressed( input.mouse.left )
left_released := released( input.mouse.left )
@ -118,11 +122,13 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
ui.hot = UI_Key(0)
}
}
// profile_end()
signal.resizing = cast(b8) is_active && (within_resize_range || ui.active_start_signal.resizing)
ui.hot_resizable = cast(b32) (is_hot && within_resize_range) || signal.resizing
// State Deltas update
// profile_begin( "state deltas upate")
if is_hot
{
box.hot_delta += frame_delta
@ -153,11 +159,14 @@ ui_signal_from_box :: proc ( box : ^ UI_Box ) -> UI_Signal
else {
box.disabled_delta = 0
}
// profile_end()
signal.dragging = cast(b8) is_active && ( ! within_resize_range && ! ui.active_start_signal.resizing)
// Update style if not in default state
{
// profile("Update style")
if is_hot
{
if ! was_hot {

View File

@ -7,6 +7,8 @@ UI_Widget :: struct {
ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
{
// profile(#procedure)
widget.box = ui_box_make( flags, label )
widget.signal = ui_signal_from_box( widget.box )
return
@ -14,6 +16,8 @@ ui_widget :: proc( label : string, flags : UI_BoxFlags ) -> (widget : UI_Widget)
ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widget)
{
// profile(#procedure)
btn_flags := UI_BoxFlags { .Mouse_Clickable, .Focusable, .Click_To_Focus }
btn.box = ui_box_make( btn_flags | flags, label )
btn.signal = ui_signal_from_box( btn.box )
@ -22,20 +26,22 @@ ui_button :: proc( label : string, flags : UI_BoxFlags = {} ) -> (btn : UI_Widge
ui_text :: proc( label : string, content : StringCached, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
state := get_state(); using state
box := ui_box_make( flags, label )
signal := ui_signal_from_box( box )
text_size := measure_text_size( content.str, box.style.font, box.style.font_size, 0 )
box.text = content
box.style.layout.size = text_size
box.text = content
box.style.layout.size_to_text = true
return { box, signal }
}
ui_space :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
space_str := str_intern( " " )
state := get_state(); using state
@ -43,14 +49,15 @@ ui_space :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
box := ui_box_make( flags, label )
signal := ui_signal_from_box( box )
text_size := measure_text_size( space_str.str, box.style.font, box.style.font_size, 0 )
box.text = space_str
box.style.layout.size = text_size
box.text = space_str
box.style.layout.size_to_text = true
return { box, signal }
}
ui_tab :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
{
// profile(#procedure)
tab_str := str_intern( "\t" )
state := get_state(); using state
@ -58,8 +65,8 @@ ui_tab :: proc( label : string, flags : UI_BoxFlags = {} ) -> UI_Widget
box := ui_box_make( flags, label )
signal := ui_signal_from_box( box )
text_size := measure_text_size( tab_str.str, box.style.font, box.style.font_size, 0 )
box.text = tab_str
box.style.layout.size = text_size
box.text = tab_str
box.style.layout.size_to_text = true
return { box, signal }
}

View File

@ -158,6 +158,7 @@ push-location $path_root
$build_args += $flag_thread_count + $CoreCount_Physical
$build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
# $build_args += $flag_optimize_speed
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'
@ -225,7 +226,9 @@ push-location $path_root
# $build_args += ($flag_collection + $pkg_collection_thirdparty)
$build_args += $flag_use_separate_modules
$build_args += $flag_thread_count + $CoreCount_Physical
$build_args += $flag_optimize_none
# $build_args += $flag_optimize_none
# $build_args += $flag_optimize_minimal
$build_args += $flag_optimize_speed
$build_args += $flag_debug
$build_args += $flag_pdb_name + $pdb
$build_args += $flag_subsystem + 'windows'