Better error handling for writer functions

This commit is contained in:
Riyyi
2026-08-09 17:11:30 +02:00
parent 4035ff48f3
commit 5fcdba2c22
8 changed files with 141 additions and 92 deletions
+1 -58
View File
@@ -1,11 +1,8 @@
package chunks package chunks
import "base:runtime" import "base:runtime"
import "core:fmt"
import "core:io" import "core:io"
import "core:os" import "core:os"
import "core:strconv"
import "core:strings"
MAGIC_STRING :: 0x514D21 // SINDRI MAGIC_STRING :: 0x514D21 // SINDRI
VERSION :: 1 VERSION :: 1
@@ -38,64 +35,10 @@ Asset_Table :: struct {
asset_sizes: [dynamic]u64, asset_sizes: [dynamic]u64,
} }
Writer :: struct {
chunk_file: ^os.File,
asset_file: ^os.File,
asset_table: Asset_Table,
}
Error :: union #shared_nil { Error :: union #shared_nil {
runtime.Allocator_Error, runtime.Allocator_Error,
io.Error, io.Error,
os.Error, os.Error,
Read_Error, Read_Error,
} Write_Error,
// -----------------------------------------
writer_init :: proc(
number_of_assets: u64,
allocator := context.allocator,
) -> (
w: Writer,
err: runtime.Allocator_Error,
) #optional_allocator_error {
// [ Header ][ []Asset Index ][ Data ]
w.asset_table.asset_index_offset = size_of(Header)
w.asset_table.data_offset =
size_of(Header) + (size_of(Asset_Index) * number_of_assets)
w.asset_table.asset_offsets = make([dynamic]u64, allocator) or_return
w.asset_table.asset_sizes = make([dynamic]u64, allocator) or_return
resize(&w.asset_table.asset_offsets, number_of_assets)
resize(&w.asset_table.asset_sizes, number_of_assets)
return w, nil
}
writer_destroy :: proc(w: ^Writer) {
if w.chunk_file != nil do os.close(w.chunk_file)
if w.asset_file != nil do os.close(w.asset_file)
delete(w.asset_table.asset_offsets)
delete(w.asset_table.asset_sizes)
}
@(private)
compute_chunk_path :: proc(
output_path: string,
chunk_index: int,
allocator: runtime.Allocator,
) -> string {
@(static) buf: [20]u8
chunk_index_str := strconv.write_int(buf[:], cast(i64)chunk_index, 10)
chunk_name := strings.concatenate({CHUNK, chunk_index_str}, allocator)
defer delete(chunk_name, allocator)
chunk_path, c_err := os.join_path({output_path, chunk_name}, allocator)
if c_err != nil {
fmt.eprintln("error: chunk join path failed:", c_err)
os.exit(1)
}
return chunk_path // owning string
} }
+1 -1
View File
@@ -63,7 +63,7 @@ read_file :: proc(
output_path, output_path,
cast(int)chunk_index, cast(int)chunk_index,
allocator, allocator,
) ) or_return
defer delete(chunk_path, allocator) defer delete(chunk_path, allocator)
// Open chunk // Open chunk
+1 -1
View File
@@ -25,7 +25,7 @@ read_header :: proc(
} }
if r.header.compression != 0 { if r.header.compression != 0 {
return .Unimplemented return Read_Error.Unimplemented
} }
return nil return nil
+1 -1
View File
@@ -31,7 +31,7 @@ reader_init :: proc(
output_path := os.name(output) output_path := os.name(output)
// Chunk path // Chunk path
chunk_path := compute_chunk_path(output_path, 0, allocator) chunk_path := compute_chunk_path(output_path, 0, allocator) or_return
defer delete(chunk_path, allocator) defer delete(chunk_path, allocator)
// Chunk open // Chunk open
+45 -21
View File
@@ -16,7 +16,7 @@ write_assets :: proc(
size_per_chunk: u64 = SIZE_PER_CHUNK, size_per_chunk: u64 = SIZE_PER_CHUNK,
compression: u16 = COMPRESSION, compression: u16 = COMPRESSION,
allocator := context.allocator, allocator := context.allocator,
) { ) -> Error {
output_path := os.name(output) output_path := os.name(output)
chunk_index := 0 chunk_index := 0
asset_index := 0 asset_index := 0
@@ -30,18 +30,28 @@ write_assets :: proc(
"error: chunk size too small to hold asset index:", "error: chunk size too small to hold asset index:",
w.asset_table.data_offset, w.asset_table.data_offset,
) )
os.exit(1) return .Chunk_Size_Too_Small
} }
// ---------------------------------------- // ----------------------------------------
// Create new chunk // Create new chunk
chunk_stream := create_new_chunk(w, output_path, chunk_index, allocator) chunk_stream := create_new_chunk(
w,
output_path,
chunk_index,
allocator,
) or_return
// Edge case of the chunk size being exactly the size of the data offset // Edge case of the chunk size being exactly the size of the data offset
if size_per_chunk == w.asset_table.data_offset { if size_per_chunk == w.asset_table.data_offset {
chunk_index += 1 chunk_index += 1
chunk_stream = create_new_chunk(w, output_path, chunk_index, allocator) chunk_stream = create_new_chunk(
w,
output_path,
chunk_index,
allocator,
) or_return
} }
// Chunk 0 starts after metadata // Chunk 0 starts after metadata
@@ -52,7 +62,10 @@ write_assets :: proc(
// ---------------------------------------- // ----------------------------------------
// Open asset // Open asset
asset_stream, asset_size := open_asset(w, entries[asset_index].relpath) asset_stream, asset_size := open_asset(
w,
entries[asset_index].relpath,
) or_return
w.asset_table.asset_offsets[asset_index] = total_offset w.asset_table.asset_offsets[asset_index] = total_offset
w.asset_table.asset_sizes[asset_index] = asset_size w.asset_table.asset_sizes[asset_index] = asset_size
@@ -79,7 +92,7 @@ write_assets :: proc(
asset_stream, asset_size = open_asset( asset_stream, asset_size = open_asset(
w, w,
entries[asset_index].relpath, entries[asset_index].relpath,
) ) or_return
w.asset_table.asset_offsets[asset_index] = total_offset w.asset_table.asset_offsets[asset_index] = total_offset
w.asset_table.asset_sizes[asset_index] = asset_size w.asset_table.asset_sizes[asset_index] = asset_size
@@ -96,7 +109,7 @@ write_assets :: proc(
asset_stream, asset_size = open_asset( asset_stream, asset_size = open_asset(
w, w,
entries[asset_index].relpath, entries[asset_index].relpath,
) ) or_return
w.asset_table.asset_offsets[asset_index] = total_offset w.asset_table.asset_offsets[asset_index] = total_offset
w.asset_table.asset_sizes[asset_index] = asset_size w.asset_table.asset_sizes[asset_index] = asset_size
@@ -108,7 +121,7 @@ write_assets :: proc(
output_path, output_path,
chunk_index, chunk_index,
allocator, allocator,
) ) or_return
} else { // spillover to next chunk } else { // spillover to next chunk
write_chunk(chunk_stream, asset_stream, chunk_remaining) write_chunk(chunk_stream, asset_stream, chunk_remaining)
asset_offset += chunk_remaining asset_offset += chunk_remaining
@@ -122,9 +135,11 @@ write_assets :: proc(
output_path, output_path,
chunk_index, chunk_index,
allocator, allocator,
) ) or_return
} }
} }
return nil
} }
@(private) @(private)
@@ -133,15 +148,22 @@ create_new_chunk :: proc(
output_path: string, output_path: string,
chunk_index: int, chunk_index: int,
allocator: runtime.Allocator, allocator: runtime.Allocator,
) -> io.Stream { ) -> (
chunk_stream: io.Stream,
err: Error,
) {
// Remove old chunk // Remove old chunk
chunk_path := compute_chunk_path(output_path, chunk_index, allocator) chunk_path := compute_chunk_path(
output_path,
chunk_index,
allocator,
) or_return
defer delete(chunk_path, allocator) defer delete(chunk_path, allocator)
if os.exists(chunk_path) { if os.exists(chunk_path) {
rm_err := os.remove(chunk_path) rm_err := os.remove(chunk_path)
if rm_err != nil { if rm_err != nil {
fmt.eprintln("error: remove old chunk failed:", rm_err) fmt.eprintln("error: remove old chunk failed:", rm_err)
os.exit(1) return {}, rm_err
} }
} }
@@ -152,23 +174,23 @@ create_new_chunk :: proc(
w.chunk_file, cf_err = os.create(chunk_path) w.chunk_file, cf_err = os.create(chunk_path)
if cf_err != nil { if cf_err != nil {
fmt.eprintln("error: create new chunk failed:", cf_err) fmt.eprintln("error: create new chunk failed:", cf_err)
os.exit(1) return {}, cf_err
} }
chunk_stream := os.to_stream(w.chunk_file) chunk_stream = os.to_stream(w.chunk_file)
return chunk_stream return chunk_stream, nil
} }
@(private) @(private)
open_asset :: proc(w: ^Writer, path: string) -> (io.Stream, u64) { open_asset :: proc(w: ^Writer, path: string) -> (io.Stream, u64, Error) {
if w.asset_file != nil do os.close(w.asset_file) // close previous if w.asset_file != nil do os.close(w.asset_file) // close previous
// Open asset // Open asset
asset, open_err := os.open(path, {.Read}) asset, open_err := os.open(path, {.Read})
if open_err != nil { if open_err != nil {
fmt.eprintln("error: asset open failed:", open_err) fmt.eprintln("error: asset open failed:", open_err)
os.exit(1) return {}, {}, open_err
} }
// Asset size // Asset size
@@ -176,17 +198,19 @@ open_asset :: proc(w: ^Writer, path: string) -> (io.Stream, u64) {
asset_size, size_err := io.size(asset_stream) asset_size, size_err := io.size(asset_stream)
if size_err != nil { if size_err != nil {
fmt.eprintln("error: asset size failed:", size_err) fmt.eprintln("error: asset size failed:", size_err)
os.exit(1) return {}, {}, size_err
} }
return asset_stream, cast(u64)asset_size return asset_stream, cast(u64)asset_size, nil
} }
@(private) @(private)
write_chunk :: proc(dst: io.Stream, src: io.Stream, n: u64) { write_chunk :: proc(dst: io.Stream, src: io.Stream, n: u64) -> Error {
w, err := io.copy_n(dst, src, cast(i64)n) w, err := io.copy_n(dst, src, cast(i64)n)
if err != nil { if err != nil {
fmt.eprintln("error: chunk write error:", err) fmt.eprintln("error: chunk write error:", err)
os.exit(1) return err
} }
return nil
} }
+14 -8
View File
@@ -16,24 +16,26 @@ write_metadata :: proc(
size_per_chunk: u64 = SIZE_PER_CHUNK, size_per_chunk: u64 = SIZE_PER_CHUNK,
compression: u16 = COMPRESSION, compression: u16 = COMPRESSION,
allocator := context.allocator, allocator := context.allocator,
) { ) -> Error {
output_path := os.name(output) output_path := os.name(output)
number_of_assets := cast(u32)len(entries) number_of_assets := cast(u32)len(entries)
// ---------------------------------------- // ----------------------------------------
chunk_path := compute_chunk_path(output_path, 0, allocator) chunk_path := compute_chunk_path(output_path, 0, allocator) or_return
defer delete(chunk_path, allocator) defer delete(chunk_path, allocator)
chunk_file, err := os.open(chunk_path, {.Read, .Write}) chunk_file, err := os.open(chunk_path, {.Read, .Write})
if err != nil { if err != nil {
fmt.eprintln("error: open chunk failed: ", err) fmt.eprintln("error: open chunk failed: ", err)
os.exit(1) return err
} }
defer os.close(chunk_file) defer os.close(chunk_file)
write_header(w, chunk_file, number_of_assets, size_per_chunk, compression) write_header(w, chunk_file, number_of_assets, size_per_chunk, compression)
write_asset_index(w, chunk_file, entries, number_of_assets, allocator) write_asset_index(w, chunk_file, entries, number_of_assets, allocator)
return nil
} }
@(private) @(private)
@@ -43,7 +45,7 @@ write_header :: proc(
number_of_assets: u32, number_of_assets: u32,
size_per_chunk: u64 = SIZE_PER_CHUNK, size_per_chunk: u64 = SIZE_PER_CHUNK,
compression: u16 = COMPRESSION, compression: u16 = COMPRESSION,
) { ) -> Error {
last_offset := last_offset :=
w.asset_table.asset_offsets[len(w.asset_table.asset_offsets) - 1] w.asset_table.asset_offsets[len(w.asset_table.asset_offsets) - 1]
last_size := w.asset_table.asset_sizes[len(w.asset_table.asset_sizes) - 1] last_size := w.asset_table.asset_sizes[len(w.asset_table.asset_sizes) - 1]
@@ -63,8 +65,10 @@ write_header :: proc(
n, err := os.write_at(chunk_file, header_bytes, 0) n, err := os.write_at(chunk_file, header_bytes, 0)
if err != nil { if err != nil {
fmt.eprintln("error: chunk write error:", err) fmt.eprintln("error: chunk write error:", err)
os.exit(1) return err
} }
return nil
} }
@(private) @(private)
@@ -74,7 +78,7 @@ write_asset_index :: proc(
entries: []file.File_Entry, entries: []file.File_Entry,
number_of_assets: u32, number_of_assets: u32,
allocator: runtime.Allocator, allocator: runtime.Allocator,
) { ) -> Error {
index_size: u64 = size_of(Asset_Index) * cast(u64)number_of_assets index_size: u64 = size_of(Asset_Index) * cast(u64)number_of_assets
// ---------------------------------------- // ----------------------------------------
@@ -88,7 +92,7 @@ write_asset_index :: proc(
path_length := len(f.relpath) path_length := len(f.relpath)
if path_length > 512 { if path_length > 512 {
fmt.eprintln("error: path exeeds maximum of 512:", f.relpath) fmt.eprintln("error: path exeeds maximum of 512:", f.relpath)
os.exit(1) return .Path_Too_Large
} }
offset_in_bytes := size_of(Asset_Index) * cast(u64)i offset_in_bytes := size_of(Asset_Index) * cast(u64)i
@@ -110,6 +114,8 @@ write_asset_index :: proc(
) )
if err != nil { if err != nil {
fmt.eprintln("error: chunk write error:", err) fmt.eprintln("error: chunk write error:", err)
os.exit(1) return err
} }
return nil
} }
+75
View File
@@ -0,0 +1,75 @@
package chunks
import "base:runtime"
import "core:fmt"
import "core:os"
import "core:strconv"
import "core:strings"
// -----------------------------------------
Writer :: struct {
chunk_file: ^os.File,
asset_file: ^os.File,
asset_table: Asset_Table,
}
Write_Error :: enum u8 {
None = 0,
Chunk_Size_Too_Small = 1,
Path_Too_Large = 2,
Unimplemented = 127,
Okay = None,
}
// -----------------------------------------
writer_init :: proc(
number_of_assets: u64,
allocator := context.allocator,
) -> (
w: Writer,
err: runtime.Allocator_Error,
) #optional_allocator_error {
// [ Header ][ []Asset Index ][ Data ]
w.asset_table.asset_index_offset = size_of(Header)
w.asset_table.data_offset =
size_of(Header) + (size_of(Asset_Index) * number_of_assets)
w.asset_table.asset_offsets = make([dynamic]u64, allocator) or_return
w.asset_table.asset_sizes = make([dynamic]u64, allocator) or_return
resize(&w.asset_table.asset_offsets, number_of_assets)
resize(&w.asset_table.asset_sizes, number_of_assets)
return w, nil
}
writer_destroy :: proc(w: ^Writer) {
if w.chunk_file != nil do os.close(w.chunk_file)
if w.asset_file != nil do os.close(w.asset_file)
delete(w.asset_table.asset_offsets)
delete(w.asset_table.asset_sizes)
}
@(private)
compute_chunk_path :: proc(
output_path: string,
chunk_index: int,
allocator: runtime.Allocator,
) -> (
string,
Error,
) {
@(static) buf: [20]u8
chunk_index_str := strconv.write_int(buf[:], cast(i64)chunk_index, 10)
chunk_name := strings.concatenate({CHUNK, chunk_index_str}, allocator)
defer delete(chunk_name, allocator)
chunk_path, c_err := os.join_path({output_path, chunk_name}, allocator)
if c_err != nil {
fmt.eprintln("error: chunk join path failed:", c_err)
return {}, c_err
}
return chunk_path, nil // owning string
}
+3 -2
View File
@@ -1,6 +1,5 @@
package main package main
import "core:fmt"
import "core:os" import "core:os"
import "src:chunks" import "src:chunks"
@@ -25,13 +24,14 @@ main :: proc() {
w := chunks.writer_init(cast(u64)len(entries)) w := chunks.writer_init(cast(u64)len(entries))
defer chunks.writer_destroy(&w) defer chunks.writer_destroy(&w)
chunks.write_assets( err := chunks.write_assets(
&w, &w,
opts.output, opts.output,
entries[:], entries[:],
opts.size, opts.size,
opts.compression, opts.compression,
) )
if err != nil do os.exit(1)
chunks.write_metadata( chunks.write_metadata(
&w, &w,
@@ -40,6 +40,7 @@ main :: proc() {
opts.size, opts.size,
opts.compression, opts.compression,
) )
if err != nil do os.exit(2)
} }
// TODO: .chunkignore // TODO: .chunkignore