From f002857edce5ea53561608fa555bbbd94b7fa42a Mon Sep 17 00:00:00 2001 From: gingerBill Date: Thu, 12 May 2022 15:47:24 +0100 Subject: [PATCH] Clean up `core:time` to be consistent across all platforms --- core/os/os2/errors.odin | 2 + core/runtime/procs.odin | 2 +- core/sys/unix/pthread_unix.odin | 5 +- core/sys/unix/time_unix.odin | 83 ++++++++++++++++++++++ core/time/time.odin | 42 +++++++---- core/time/time_essence.odin | 13 ++-- core/time/time_freestanding.odin | 9 ++- core/time/time_js.odin | 7 +- core/time/time_unix.odin | 118 +++++-------------------------- core/time/time_wasi.odin | 7 +- core/time/time_windows.odin | 9 ++- 11 files changed, 159 insertions(+), 138 deletions(-) create mode 100644 core/sys/unix/time_unix.odin diff --git a/core/os/os2/errors.odin b/core/os/os2/errors.odin index f42f92eb9..2cff73ebd 100644 --- a/core/os/os2/errors.odin +++ b/core/os/os2/errors.odin @@ -14,6 +14,7 @@ General_Error :: enum u32 { Timeout, Invalid_File, + Invalid_Dir, Invalid_Path, Unsupported, @@ -51,6 +52,7 @@ error_string :: proc(ferr: Error) -> string { case .Closed: return "file already closed" case .Timeout: return "i/o timeout" case .Invalid_File: return "invalid file" + case .Invalid_Dir: return "invalid directory" case .Invalid_Path: return "invalid path" case .Unsupported: return "unsupported" } diff --git a/core/runtime/procs.odin b/core/runtime/procs.odin index 5a1d11fe0..782efa773 100644 --- a/core/runtime/procs.odin +++ b/core/runtime/procs.odin @@ -4,7 +4,7 @@ when ODIN_NO_CRT && ODIN_OS == .Windows { foreign import lib "system:NtDll.lib" @(private="file") - @(default_calling_convention="std") + @(default_calling_convention="stdcall") foreign lib { RtlMoveMemory :: proc(dst, src: rawptr, length: int) --- RtlFillMemory :: proc(dst: rawptr, length: int, fill: i32) --- diff --git a/core/sys/unix/pthread_unix.odin b/core/sys/unix/pthread_unix.odin index 62e3701ab..8bf397647 100644 --- a/core/sys/unix/pthread_unix.odin +++ b/core/sys/unix/pthread_unix.odin @@ -4,7 +4,6 @@ package unix foreign import "system:pthread" import "core:c" -import "core:time" // // On success, these functions return 0. @@ -72,7 +71,7 @@ foreign pthread { // assumes the mutex is pre-locked pthread_cond_wait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t) -> c.int --- - pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int --- + pthread_cond_timedwait :: proc(cond: ^pthread_cond_t, mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- pthread_condattr_init :: proc(attrs: ^pthread_condattr_t) -> c.int --- pthread_condattr_destroy :: proc(attrs: ^pthread_condattr_t) -> c.int --- @@ -95,7 +94,7 @@ foreign pthread { pthread_mutex_lock :: proc(mutex: ^pthread_mutex_t) -> c.int --- - pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^time.TimeSpec) -> c.int --- + pthread_mutex_timedlock :: proc(mutex: ^pthread_mutex_t, timeout: ^timespec) -> c.int --- pthread_mutex_unlock :: proc(mutex: ^pthread_mutex_t) -> c.int --- diff --git a/core/sys/unix/time_unix.odin b/core/sys/unix/time_unix.odin new file mode 100644 index 000000000..d9452c216 --- /dev/null +++ b/core/sys/unix/time_unix.odin @@ -0,0 +1,83 @@ +//+build linux, darwin, freebsd, openbsd +package unix + +when ODIN_OS == .Darwin { + foreign import libc "System.framework" +} else { + foreign import libc "system:c" +} + +@(default_calling_convention="c") +foreign libc { + clock_gettime :: proc(clock_id: u64, timespec: ^timespec) -> i32 --- + sleep :: proc(seconds: u32) -> i32 --- + nanosleep :: proc(requested, remaining: ^timespec) -> i32 --- +} + +foreign import "system:pthread" + +import "core:c" + +@(private="file") +@(default_calling_convention="c") +foreign pthread { + sched_yield :: proc() -> c.int --- +} + +timespec :: struct { + tv_sec: i64, // seconds + tv_nsec: i64, // nanoseconds +} + +when ODIN_OS == .OpenBSD { + CLOCK_REALTIME :: 0 + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_MONOTONIC :: 3 + CLOCK_THREAD_CPUTIME_ID :: 4 + CLOCK_UPTIME :: 5 + CLOCK_BOOTTIME :: 6 + + // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC + CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC +} else { + CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. + CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. + CLOCK_PROCESS_CPUTIME_ID :: 2 + CLOCK_THREAD_CPUTIME_ID :: 3 + CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. + CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." + CLOCK_MONOTONIC_COARSE :: 6 + CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. + CLOCK_REALTIME_ALARM :: 8 + CLOCK_BOOTTIME_ALARM :: 9 +} + +// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. +// I do not know if Darwin programmers are used to the existance of these constants or not, so +// I'm leaving aliases to them for now. +CLOCK_SYSTEM :: CLOCK_REALTIME +CLOCK_CALENDAR :: CLOCK_MONOTONIC + +boot_time_in_nanoseconds :: proc "c" () -> i64 { + ts_now, ts_boottime: timespec + clock_gettime(CLOCK_REALTIME, &ts_now) + clock_gettime(CLOCK_BOOTTIME, &ts_boottime) + + ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec + return i64(ns) +} + +seconds_since_boot :: proc "c" () -> f64 { + ts_boottime: timespec + clock_gettime(CLOCK_BOOTTIME, &ts_boottime) + return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 +} + + +inline_nanosleep :: proc "c" (nanoseconds: i64) -> (remaining: timespec, res: i32) { + s, ns := nanoseconds / 1e9, nanoseconds % 1e9 + requested := timespec{tv_sec=s, tv_nsec=ns} + res = nanosleep(&requested, &remaining) + return +} + diff --git a/core/time/time.odin b/core/time/time.odin index 1f778a8de..6c6e47dc0 100644 --- a/core/time/time.odin +++ b/core/time/time.odin @@ -14,6 +14,8 @@ Hour :: 60 * Minute MIN_DURATION :: Duration(-1 << 63) MAX_DURATION :: Duration(1<<63 - 1) +IS_SUPPORTED :: _IS_SUPPORTED + Time :: struct { _nsec: i64, // zero is 1970-01-01 00:00:00 } @@ -49,6 +51,14 @@ Stopwatch :: struct { _accumulation: Duration, } +now :: proc "contextless" () -> Time { + return _now() +} + +sleep :: proc "contextless" (d: Duration) { + _sleep(d) +} + stopwatch_start :: proc(using stopwatch: ^Stopwatch) { if !running { _start_time = tick_now() @@ -82,36 +92,36 @@ since :: proc(start: Time) -> Duration { return diff(start, now()) } -duration_nanoseconds :: proc(d: Duration) -> i64 { +duration_nanoseconds :: proc "contextless" (d: Duration) -> i64 { return i64(d) } -duration_microseconds :: proc(d: Duration) -> f64 { +duration_microseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e6 } -duration_milliseconds :: proc(d: Duration) -> f64 { +duration_milliseconds :: proc "contextless" (d: Duration) -> f64 { return duration_seconds(d) * 1e3 } -duration_seconds :: proc(d: Duration) -> f64 { +duration_seconds :: proc "contextless" (d: Duration) -> f64 { sec := d / Second nsec := d % Second return f64(sec) + f64(nsec)/1e9 } -duration_minutes :: proc(d: Duration) -> f64 { +duration_minutes :: proc "contextless" (d: Duration) -> f64 { min := d / Minute nsec := d % Minute return f64(min) + f64(nsec)/(60*1e9) } -duration_hours :: proc(d: Duration) -> f64 { +duration_hours :: proc "contextless" (d: Duration) -> f64 { hour := d / Hour nsec := d % Hour return f64(hour) + f64(nsec)/(60*60*1e9) } -_less_than_half :: #force_inline proc(x, y: Duration) -> bool { - return u64(x)+u64(x) < u64(y) -} - duration_round :: proc(d, m: Duration) -> Duration { + _less_than_half :: #force_inline proc(x, y: Duration) -> bool { + return u64(x)+u64(x) < u64(y) + } + if m <= 0 { return d } @@ -201,10 +211,12 @@ unix :: proc(sec: i64, nsec: i64) -> Time { return Time{(sec*1e9 + nsec) + UNIX_TO_INTERNAL} } +to_unix_seconds :: time_to_unix time_to_unix :: proc(t: Time) -> i64 { return t._nsec/1e9 } +to_unix_nanoseconds :: time_to_unix_nano time_to_unix_nano :: proc(t: Time) -> i64 { return t._nsec } @@ -265,20 +277,24 @@ INTERNAL_TO_WALL :: -WALL_TO_INTERNAL UNIX_TO_ABSOLUTE :: UNIX_TO_INTERNAL + INTERNAL_TO_ABSOLUTE ABSOLUTE_TO_UNIX :: -UNIX_TO_ABSOLUTE -_is_leap_year :: proc(year: int) -> bool { - return year%4 == 0 && (year%100 != 0 || year%400 == 0) -} +@(private) _date :: proc(t: Time, full: bool) -> (year: int, month: Month, day: int, yday: int) { year, month, day, yday = _abs_date(_time_abs(t), full) return } +@(private) _time_abs :: proc(t: Time) -> u64 { return u64(t._nsec/1e9 + UNIX_TO_ABSOLUTE) } +@(private) _abs_date :: proc(abs: u64, full: bool) -> (year: int, month: Month, day: int, yday: int) { + _is_leap_year :: proc(year: int) -> bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) + } + d := abs / SECONDS_PER_DAY // 400 year cycles diff --git a/core/time/time_essence.odin b/core/time/time_essence.odin index 72efe9f8f..b7bc616d8 100644 --- a/core/time/time_essence.odin +++ b/core/time/time_essence.odin @@ -1,18 +1,19 @@ +//+private package time import "core:sys/es" -IS_SUPPORTED :: true; +_IS_SUPPORTED :: true -now :: proc "contextless" () -> Time { +_now :: proc "contextless" () -> Time { // TODO Replace once there's a proper time API. - return Time{_nsec = i64(es.TimeStampMs() * 1e6)}; + return Time{_nsec = i64(es.TimeStampMs() * 1e6)} } -sleep :: proc "contextless" (d: Duration) { - es.Sleep(u64(d/Millisecond)); +_sleep :: proc "contextless" (d: Duration) { + es.Sleep(u64(d/Millisecond)) } _tick_now :: proc "contextless" () -> Tick { - return Tick{_nsec = i64(es.TimeStampMs() * 1e6)}; + return Tick{_nsec = i64(es.TimeStampMs() * 1e6)} } diff --git a/core/time/time_freestanding.odin b/core/time/time_freestanding.odin index 17a21d79c..7c67cc5e8 100644 --- a/core/time/time_freestanding.odin +++ b/core/time/time_freestanding.odin @@ -1,16 +1,19 @@ +//+private //+build freestanding package time -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { return {} } +_yield :: proc "contextless" () { +} diff --git a/core/time/time_js.odin b/core/time/time_js.odin index cfe54b86b..9a7163f38 100644 --- a/core/time/time_js.odin +++ b/core/time/time_js.odin @@ -1,13 +1,14 @@ +//+private //+build js package time -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { diff --git a/core/time/time_unix.odin b/core/time/time_unix.odin index 0cfa196a2..ba0d91527 100644 --- a/core/time/time_unix.odin +++ b/core/time/time_unix.odin @@ -1,118 +1,34 @@ +//+private //+build linux, darwin, freebsd, openbsd package time -IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. +import "core:sys/unix" -when ODIN_OS == .Darwin { - foreign import libc "System.framework" -} else { - foreign import libc "system:c" -} +_IS_SUPPORTED :: true // NOTE: Times on Darwin are UTC. - -@(default_calling_convention="c") -foreign libc { - @(link_name="clock_gettime") _unix_clock_gettime :: proc(clock_id: u64, timespec: ^TimeSpec) -> i32 --- - @(link_name="sleep") _unix_sleep :: proc(seconds: u32) -> i32 --- - @(link_name="nanosleep") _unix_nanosleep :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 --- -} - -foreign import "system:pthread" - -import "core:c" - -@(private="file") -@(default_calling_convention="c") -foreign pthread { - sched_yield :: proc() -> c.int --- -} - -_yield :: proc "contextless" () { - sched_yield() -} - -TimeSpec :: struct { - tv_sec : i64, /* seconds */ - tv_nsec : i64, /* nanoseconds */ -} - -when ODIN_OS == .OpenBSD { - CLOCK_REALTIME :: 0 - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_MONOTONIC :: 3 - CLOCK_THREAD_CPUTIME_ID :: 4 - CLOCK_UPTIME :: 5 - CLOCK_BOOTTIME :: 6 - - // CLOCK_MONOTONIC_RAW doesn't exist, use CLOCK_MONOTONIC - CLOCK_MONOTONIC_RAW :: CLOCK_MONOTONIC -} else { - CLOCK_REALTIME :: 0 // NOTE(tetra): May jump in time, when user changes the system time. - CLOCK_MONOTONIC :: 1 // NOTE(tetra): May stand still while system is asleep. - CLOCK_PROCESS_CPUTIME_ID :: 2 - CLOCK_THREAD_CPUTIME_ID :: 3 - CLOCK_MONOTONIC_RAW :: 4 // NOTE(tetra): "RAW" means: Not adjusted by NTP. - CLOCK_REALTIME_COARSE :: 5 // NOTE(tetra): "COARSE" clocks are apparently much faster, but not "fine-grained." - CLOCK_MONOTONIC_COARSE :: 6 - CLOCK_BOOTTIME :: 7 // NOTE(tetra): Same as MONOTONIC, except also including time system was asleep. - CLOCK_REALTIME_ALARM :: 8 - CLOCK_BOOTTIME_ALARM :: 9 -} - -// TODO(tetra, 2019-11-05): The original implementation of this package for Darwin used this constants. -// I do not know if Darwin programmers are used to the existance of these constants or not, so -// I'm leaving aliases to them for now. -CLOCK_SYSTEM :: CLOCK_REALTIME -CLOCK_CALENDAR :: CLOCK_MONOTONIC - - -clock_gettime :: proc "contextless" (clock_id: u64) -> TimeSpec { - ts : TimeSpec // NOTE(tetra): Do we need to initialize this? - _unix_clock_gettime(clock_id, &ts) - return ts -} - -now :: proc() -> Time { - time_spec_now := clock_gettime(CLOCK_REALTIME) +_now :: proc "contextless" () -> Time { + time_spec_now: unix.timespec + unix.clock_gettime(unix.CLOCK_REALTIME, &time_spec_now) ns := time_spec_now.tv_sec * 1e9 + time_spec_now.tv_nsec return Time{_nsec=ns} } -boot_time :: proc() -> Time { - ts_now := clock_gettime(CLOCK_REALTIME) - ts_boottime := clock_gettime(CLOCK_BOOTTIME) - - ns := (ts_now.tv_sec - ts_boottime.tv_sec) * 1e9 + ts_now.tv_nsec - ts_boottime.tv_nsec - return Time{_nsec=ns} -} - -seconds_since_boot :: proc() -> f64 { - ts_boottime := clock_gettime(CLOCK_BOOTTIME) - return f64(ts_boottime.tv_sec) + f64(ts_boottime.tv_nsec) / 1e9 -} - - -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { ds := duration_seconds(d) seconds := u32(ds) nanoseconds := i64((ds - f64(seconds)) * 1e9) - if seconds > 0 { _unix_sleep(seconds) } - if nanoseconds > 0 { nanosleep(nanoseconds) } + if seconds > 0 { unix.sleep(seconds) } + if nanoseconds > 0 { unix.inline_nanosleep(nanoseconds) } } -nanosleep :: proc(nanoseconds: i64) -> int { - // NOTE(tetra): Should we remove this assert? We are measuring nanoseconds after all... - assert(nanoseconds <= 999999999) - - requested := TimeSpec{tv_nsec = nanoseconds} - remaining: TimeSpec // NOTE(tetra): Do we need to initialize this? - return int(_unix_nanosleep(&requested, &remaining)) -} - - _tick_now :: proc "contextless" () -> Tick { - t := clock_gettime(CLOCK_MONOTONIC_RAW) - _nsec := t.tv_sec*1e9 + t.tv_nsec - return Tick{_nsec = _nsec} + t: unix.timespec + unix.clock_gettime(unix.CLOCK_MONOTONIC_RAW, &t) + return Tick{_nsec = t.tv_sec*1e9 + t.tv_nsec} } + +_yield :: proc "contextless" () { + unix.sched_yield() +} + diff --git a/core/time/time_wasi.odin b/core/time/time_wasi.odin index 4a6c8afc0..9360e3591 100644 --- a/core/time/time_wasi.odin +++ b/core/time/time_wasi.odin @@ -1,15 +1,16 @@ +//+private //+build wasi package time import wasi "core:sys/wasm/wasi" -IS_SUPPORTED :: false +_IS_SUPPORTED :: false -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { return {} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { } _tick_now :: proc "contextless" () -> Tick { diff --git a/core/time/time_windows.odin b/core/time/time_windows.odin index 397741126..20863c323 100644 --- a/core/time/time_windows.odin +++ b/core/time/time_windows.odin @@ -1,22 +1,21 @@ +//+private package time import win32 "core:sys/windows" -IS_SUPPORTED :: true +_IS_SUPPORTED :: true -now :: proc() -> Time { +_now :: proc "contextless" () -> Time { file_time: win32.FILETIME win32.GetSystemTimeAsFileTime(&file_time) ns := win32.FILETIME_as_unix_nanoseconds(file_time) return Time{_nsec=ns} } -sleep :: proc(d: Duration) { +_sleep :: proc "contextless" (d: Duration) { win32.Sleep(win32.DWORD(d/Millisecond)) } - - _tick_now :: proc "contextless" () -> Tick { mul_div_u64 :: proc "contextless" (val, num, den: i64) -> i64 { q := val / den