Add header computation function
This commit is contained in:
+21
-21
@@ -1,24 +1,24 @@
|
|||||||
# Design
|
# Design
|
||||||
|
|
||||||
The data layout design is very simple. It consists of data split into chunks
|
The data layout design is very simple. It consists of data split into chunks
|
||||||
with the first chunk holding the metadata for the structure and files.
|
with the first chunk holding the metadata for the structure and assets.
|
||||||
|
|
||||||
## Spec
|
## Spec
|
||||||
|
|
||||||
```
|
```
|
||||||
METADATA DATA
|
METADATA DATA
|
||||||
|---------------------------------------------------------------------------||------|
|
|----------------------------------------------------------------------------------------||------|
|
||||||
|
|
||||||
[ MAGIC ][ VERSION ][ COMPRESSION ][ SIZE_PER_CHUNK ][ TOTAL_SIZE ][ #FILES ][ DATA ] # chunk 1
|
[ MAGIC ][ VERSION ][ COMPRESSION ][ SIZE_PER_CHUNK ][ TOTAL_SIZE ][ #ASSETS ][ []ASSETS ][ DATA ] # chunk 1
|
||||||
[ DATA ] # chunk 2
|
[ DATA ] # chunk 2
|
||||||
[ DATA ] # etc..
|
[ DATA ] # etc..
|
||||||
[ DATA ] # last chunk
|
[ DATA ] # last chunk
|
||||||
```
|
```
|
||||||
|
|
||||||
### Magic string
|
### Magic string
|
||||||
|
|
||||||
Magic string to detect if the read file is what we expect it is.
|
Magic string to detect if the read file is what we expect it is.
|
||||||
Represented in 3 bytes.
|
Represented in 4 bytes.
|
||||||
|
|
||||||
Value is `0x514D21`, the closest approximation of "Sindri".
|
Value is `0x514D21`, the closest approximation of "Sindri".
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ Value is `1` currently.
|
|||||||
### Compression
|
### Compression
|
||||||
|
|
||||||
Wether LZ4 compression is enabled/disabled.
|
Wether LZ4 compression is enabled/disabled.
|
||||||
Represented in `1 byte`.
|
Represented in `2 byte`.
|
||||||
|
|
||||||
Value is configurable, defaulting to `0`.
|
Value is configurable, defaulting to `0`.
|
||||||
|
|
||||||
@@ -54,30 +54,30 @@ Value is configurable, defaulting to `2GiB`.
|
|||||||
The total size in bytes of all chunks combined.
|
The total size in bytes of all chunks combined.
|
||||||
Represented in `8 bytes`.
|
Represented in `8 bytes`.
|
||||||
|
|
||||||
### Number of files
|
### Number of assets
|
||||||
|
|
||||||
The total number of files in the pack.
|
The total number of assets in the pack.
|
||||||
Represented in `4 bytes`.
|
Represented in `4 bytes`.
|
||||||
|
|
||||||
### File
|
### []Asset
|
||||||
|
|
||||||
Piece of data stored, consisting of 3 parts:
|
Index to piece of stored data, consisting of 3 parts:
|
||||||
|
|
||||||
- Path
|
- Path
|
||||||
- Offset
|
|
||||||
- Size
|
- Size
|
||||||
|
- Offset
|
||||||
|
|
||||||
#### Path
|
#### Path
|
||||||
|
|
||||||
Path of the file as if it were on the filesystem.
|
Path of the asset as if it were on the filesystem.
|
||||||
Represented in `512 bytes`.
|
Represented in `512 bytes`.
|
||||||
|
|
||||||
#### Offset
|
|
||||||
|
|
||||||
The offset to the file, starting from the true 0th byte of the first chunk.
|
|
||||||
Represented in `8 bytes`.
|
|
||||||
|
|
||||||
#### Size
|
#### Size
|
||||||
|
|
||||||
Size of the file.
|
Size of the asset.
|
||||||
|
Represented in `8 bytes`.
|
||||||
|
|
||||||
|
#### Offset
|
||||||
|
|
||||||
|
The offset to the asset, starting from the true 0th byte of the first chunk.
|
||||||
Represented in `8 bytes`.
|
Represented in `8 bytes`.
|
||||||
|
|||||||
+2
-2
@@ -7,8 +7,8 @@ import "core:strings"
|
|||||||
|
|
||||||
Options :: struct {
|
Options :: struct {
|
||||||
verbose: bool `args:"name=verbose" usage:"Enable verbose output"`,
|
verbose: bool `args:"name=verbose" usage:"Enable verbose output"`,
|
||||||
compression: u8 `args:"name=compression" usage:"Compression level, 0-12"`,
|
compression: u16 `args:"name=compression" usage:"Compression level, 0-12"`,
|
||||||
size: i64 `args:"name=size" usage:"Chunk size in bytes"`,
|
size: u64 `args:"name=size" usage:"Chunk size in bytes"`,
|
||||||
input: ^os.File `args:"name=input,file=r,required" usage:"Path to input directory"`,
|
input: ^os.File `args:"name=input,file=r,required" usage:"Path to input directory"`,
|
||||||
output: ^os.File `args:"name=output,file=wc" usage:"Path to ouput directory"`,
|
output: ^os.File `args:"name=output,file=wc" usage:"Path to ouput directory"`,
|
||||||
// overflow: [dynamic]string,
|
// overflow: [dynamic]string,
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:mem"
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
MAGIC_STRING :: 0x514D21 // SINDRI
|
||||||
|
VERSION :: 1
|
||||||
|
COMPRESSION :: 0
|
||||||
|
SIZE_PER_CHUNK :: 2 * (1 << 30) // 2GiB
|
||||||
|
PATH_SIZE :: 512
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
Header :: struct #packed #all_or_none {
|
||||||
|
magic_string: u32,
|
||||||
|
version: u16,
|
||||||
|
compression: u16,
|
||||||
|
size_per_chunk: u64,
|
||||||
|
total_size: u64,
|
||||||
|
number_of_assets: u32,
|
||||||
|
// asset_index: []AssetIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetIndex :: struct #packed #all_or_none {
|
||||||
|
relpath: [PATH_SIZE]u8,
|
||||||
|
size: u64,
|
||||||
|
offset: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
// -----------------------------------------
|
||||||
|
|
||||||
|
// remove :: proc(name: string) -> Error {…}
|
||||||
|
// create :: proc(name: string) -> (^File, Error) {…}
|
||||||
|
// write_at :: proc(f: ^File, p: []u8, offset: i64) -> (n: int, err: Error) {…}
|
||||||
|
|
||||||
|
// Returning `[]u8` is owned by the caller.
|
||||||
|
compute_header :: proc(
|
||||||
|
entries: [dynamic]FileEntry,
|
||||||
|
compression: u16 = COMPRESSION,
|
||||||
|
size_per_chunk: u64 = SIZE_PER_CHUNK,
|
||||||
|
allocator := context.allocator,
|
||||||
|
) -> (
|
||||||
|
header: Header,
|
||||||
|
asset_index: []AssetIndex,
|
||||||
|
) {
|
||||||
|
number_of_assets := cast(u32)len(entries)
|
||||||
|
|
||||||
|
index_size: u64 = size_of(AssetIndex) * cast(u64)number_of_assets
|
||||||
|
data_offset: u64 = size_of(Header) + index_size
|
||||||
|
total_size: u64 = data_offset
|
||||||
|
|
||||||
|
bytes := make([]u8, index_size, allocator)
|
||||||
|
|
||||||
|
for f, i in entries {
|
||||||
|
|
||||||
|
path_length := len(f.relpath)
|
||||||
|
if path_length > 512 {
|
||||||
|
fmt.eprintln("error: path exeeds maximum of 512:", f.relpath)
|
||||||
|
os.exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
offset_in_bytes := size_of(AssetIndex) * cast(u64)i
|
||||||
|
offset_pointer := mem.ptr_offset(
|
||||||
|
&bytes[0],
|
||||||
|
offset_in_bytes,
|
||||||
|
)
|
||||||
|
|
||||||
|
ai := cast(^AssetIndex)offset_pointer
|
||||||
|
copy(ai.relpath[:PATH_SIZE], f.relpath)
|
||||||
|
ai.size = cast(u64)f.size
|
||||||
|
ai.offset = data_offset
|
||||||
|
data_offset += ai.size
|
||||||
|
|
||||||
|
total_size += ai.size
|
||||||
|
}
|
||||||
|
|
||||||
|
header = Header {
|
||||||
|
magic_string = MAGIC_STRING,
|
||||||
|
version = VERSION,
|
||||||
|
compression = compression,
|
||||||
|
size_per_chunk = size_per_chunk,
|
||||||
|
total_size = total_size,
|
||||||
|
number_of_assets = number_of_assets,
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(len(bytes) % size_of(AssetIndex) == 0) // clean multiple
|
||||||
|
return header, mem.slice_data_cast([]AssetIndex, bytes) // transmute slice with proper size
|
||||||
|
}
|
||||||
+7
-10
@@ -7,7 +7,7 @@ import "core:strings"
|
|||||||
|
|
||||||
// -----------------------------------------
|
// -----------------------------------------
|
||||||
|
|
||||||
DirectoryEntry :: struct #all_or_none {
|
FileEntry :: struct #all_or_none {
|
||||||
relpath: string, // NOTE: owning string
|
relpath: string, // NOTE: owning string
|
||||||
size: i64,
|
size: i64,
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ DirectoryEntry :: struct #all_or_none {
|
|||||||
list_dir_recursive :: proc(
|
list_dir_recursive :: proc(
|
||||||
f: ^os.File,
|
f: ^os.File,
|
||||||
allocator := context.allocator,
|
allocator := context.allocator,
|
||||||
) -> [dynamic]DirectoryEntry {
|
) -> [dynamic]FileEntry {
|
||||||
path := os.name(f)
|
path := os.name(f)
|
||||||
return list_dir_recursive_by_path_impl(path, allocator)
|
return list_dir_recursive_by_path_impl(path, allocator)
|
||||||
}
|
}
|
||||||
@@ -27,7 +27,7 @@ list_dir_recursive :: proc(
|
|||||||
list_dir_recursive_by_path :: proc(
|
list_dir_recursive_by_path :: proc(
|
||||||
path: string,
|
path: string,
|
||||||
allocator := context.allocator,
|
allocator := context.allocator,
|
||||||
) -> [dynamic]DirectoryEntry {
|
) -> [dynamic]FileEntry {
|
||||||
return list_dir_recursive_by_path_impl(path, allocator)
|
return list_dir_recursive_by_path_impl(path, allocator)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,13 +35,13 @@ list_dir_recursive_by_path :: proc(
|
|||||||
list_dir_recursive_by_path_impl :: proc(
|
list_dir_recursive_by_path_impl :: proc(
|
||||||
path: string,
|
path: string,
|
||||||
allocator: runtime.Allocator,
|
allocator: runtime.Allocator,
|
||||||
) -> [dynamic]DirectoryEntry {
|
) -> [dynamic]FileEntry {
|
||||||
if !os.is_dir(path) {
|
if !os.is_dir(path) {
|
||||||
fmt.eprintln("error: path is not a directory:", path)
|
fmt.eprintln("error: path is not a directory:", path)
|
||||||
os.exit(1)
|
os.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([dynamic]DirectoryEntry, allocator)
|
result := make([dynamic]FileEntry, allocator)
|
||||||
|
|
||||||
queue := make([dynamic]string, 0, 1, allocator) // reserve 1 slot
|
queue := make([dynamic]string, 0, 1, allocator) // reserve 1 slot
|
||||||
defer delete(queue) // dynamic array embeds allocator
|
defer delete(queue) // dynamic array embeds allocator
|
||||||
@@ -57,7 +57,7 @@ list_dir_recursive_by_path_impl :: proc(
|
|||||||
@(private)
|
@(private)
|
||||||
list_dir_queue :: proc(
|
list_dir_queue :: proc(
|
||||||
queue: ^[dynamic]string,
|
queue: ^[dynamic]string,
|
||||||
result: ^[dynamic]DirectoryEntry,
|
result: ^[dynamic]FileEntry,
|
||||||
working_dir: string,
|
working_dir: string,
|
||||||
allocator: runtime.Allocator,
|
allocator: runtime.Allocator,
|
||||||
) {
|
) {
|
||||||
@@ -99,10 +99,7 @@ list_dir_queue :: proc(
|
|||||||
if !was_allocation {
|
if !was_allocation {
|
||||||
relpath = strings.clone(relpath, result^.allocator)
|
relpath = strings.clone(relpath, result^.allocator)
|
||||||
}
|
}
|
||||||
append(
|
append(result, FileEntry{relpath = relpath, size = entry.size})
|
||||||
result,
|
|
||||||
DirectoryEntry{relpath = relpath, size = entry.size},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
case: // skip other types
|
case: // skip other types
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ main :: proc() {
|
|||||||
|
|
||||||
entries := file.list_dir_recursive_by_path("./src")
|
entries := file.list_dir_recursive_by_path("./src")
|
||||||
fmt.println("entries:", entries)
|
fmt.println("entries:", entries)
|
||||||
|
|
||||||
|
header, asset_index_array := file.compute_header(entries)
|
||||||
|
fmt.println("header:", header)
|
||||||
|
fmt.println("aia:", asset_index_array)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user