Add exact arithmetic implementation and tests
- Introduced exact arithmetic with support for large integers and fractions in `exact.gd`, `big_integer.gd`, and `exact_value.gd`. - Implemented a comprehensive test suite in `test_exact_math.gd` to validate arithmetic operations against expected results. - Added a README in `math` directory to document the exact arithmetic system and its usage. - Ensured that all arithmetic operations maintain precision without floating-point errors, using a custom representation for large numbers. - Included methods for encoding and decoding exact values for serialization.
This commit is contained in:
+44
-39
@@ -1,4 +1,5 @@
|
||||
extends SceneTree
|
||||
const E = preload("res://world/owniverse/math/exact.gd")
|
||||
|
||||
var failures: int = 0
|
||||
var checks: int = 0
|
||||
@@ -8,89 +9,93 @@ func _initialize() -> void:
|
||||
call_deferred("run")
|
||||
|
||||
func check(value: bool, message: String) -> void:
|
||||
checks += 1
|
||||
if not value:
|
||||
failures += 1
|
||||
checks = E.add(checks, 1)
|
||||
if (not value):
|
||||
failures = E.add(failures, 1)
|
||||
push_error(message)
|
||||
|
||||
func run() -> void:
|
||||
var manager = load("res://autoload/save_manager.gd").new()
|
||||
var directory: String = "/tmp/owniverse-async-%s-%s" % [OS.get_process_id(), Time.get_ticks_usec()]
|
||||
var directory: String = ("/tmp/owniverse-async-%s-%s" % [OS.get_process_id(), Time.get_ticks_usec()])
|
||||
DirAccess.make_dir_recursive_absolute(directory)
|
||||
manager.save_directory = directory
|
||||
root.add_child(manager)
|
||||
manager.save_completed.connect(func(filename, success, error): completions.append([filename, success, error]))
|
||||
manager.save_completed.connect(func ( filename , success , error ) : completions.append([filename, success, error]))
|
||||
var world = load("res://world/owniverse/owniverse.gd").new()
|
||||
world.state.energy = E.subtract(world.state.energy, 1)
|
||||
world.state.time = preload("res://world/owniverse/simulation_time.gd").advance({"datetime": E.of("1e27")}, 0.000000001, 1)
|
||||
var data: Dictionary = {"state": world.state, "ui": {"current_page": 1}}
|
||||
var initial_energy: float = world.state.energy
|
||||
var initial_energy: Variant = world.state.energy
|
||||
var global: Node = root.get_node("Global")
|
||||
var old_plaintext: bool = global.save_plaintext_copy
|
||||
global.save_plaintext_copy = true
|
||||
check(manager.request_save("snapshot", "test-password", data), "Snapshot queued")
|
||||
data.state.energy = 99.0
|
||||
data.state.energy = 99
|
||||
data.ui.current_page = 2
|
||||
var frames: int = 0
|
||||
var deadline: int = Time.get_ticks_msec() + 10000
|
||||
while manager.is_saving() and Time.get_ticks_msec() < deadline:
|
||||
frames += 1
|
||||
var deadline: int = E.add(Time.get_ticks_msec(), 10000)
|
||||
while (manager.is_saving() and E.lt(Time.get_ticks_msec(), deadline)):
|
||||
frames = E.add(frames, 1)
|
||||
await process_frame
|
||||
check(not manager.is_saving() and frames > 0, "Frames continue while save is pending")
|
||||
check(((not manager.is_saving()) and E.gt(frames, 0)), "Frames continue while save is pending")
|
||||
var saved: Dictionary = manager.load_data("snapshot", "test-password")
|
||||
check(saved.state.energy == initial_energy and saved.ui.current_page == 1, "Worker owns a deep snapshot")
|
||||
check((E.eq(saved.state.energy, initial_energy) and E.eq(saved.ui.current_page, 1)), "Worker owns a deep snapshot")
|
||||
check(E.eq(saved.state.time, world.state.time), "Encrypted save preserves exact ticks at huge age")
|
||||
var plain: Dictionary = JSON.parse_string(FileAccess.get_file_as_string(directory.path_join("snapshot.adventure.json")))
|
||||
check(plain.state.energy == initial_energy, "Diagnostic JSON uses same snapshot")
|
||||
check(completions.size() == 1 and completions[0][1], "Completion reports successful write")
|
||||
check(E.eq(E.decode(plain.state.energy), initial_energy), "Diagnostic JSON uses same snapshot")
|
||||
check(E.eq(E.decode(plain.state.time), saved.state.time), "Diagnostic JSON preserves fractional time")
|
||||
check((E.eq(completions.size(), 1) and completions[0][1]), "Completion reports successful write")
|
||||
|
||||
completions.clear()
|
||||
data.state.energy = 1.0
|
||||
data.state.energy = 1
|
||||
manager.request_save("latest", "test-password", data)
|
||||
data.state.energy = 2.0
|
||||
data.state.energy = 2
|
||||
manager.request_save("latest", "test-password", data)
|
||||
data.state.energy = 3.0
|
||||
data.state.energy = 3
|
||||
manager.request_save("latest", "test-password", data)
|
||||
data.state.energy = 4.0
|
||||
data.state.energy = 4
|
||||
manager.flush_saves()
|
||||
check(completions.size() == 2, "Repeated requests coalesce behind active write")
|
||||
check(manager.load_data("latest", "test-password").state.energy == 3.0, "Last queued snapshot wins")
|
||||
check(E.eq(completions.size(), 2), "Repeated requests coalesce behind active write")
|
||||
check(E.eq(manager.load_data("latest", "test-password").state.energy, 3), "Last queued snapshot wins")
|
||||
|
||||
var incompatible_path: String = directory.path_join("incompatible.adventure")
|
||||
var incompatible = FileAccess.open_encrypted_with_pass(incompatible_path, FileAccess.WRITE, "test-password")
|
||||
incompatible.store_var({"format_version": -1, "state": {"unknown": true}}, false)
|
||||
incompatible.store_var({"format_version": E.neg(1), "state": {"unknown": true}}, false)
|
||||
incompatible.close()
|
||||
var original: PackedByteArray = FileAccess.get_file_as_bytes(incompatible_path)
|
||||
var previous_archive = FileAccess.open(incompatible_path + ".incompatible.bak", FileAccess.WRITE)
|
||||
var previous_archive = FileAccess.open(E.add(incompatible_path, ".incompatible.bak"), FileAccess.WRITE)
|
||||
previous_archive.store_string("keep previous backup")
|
||||
previous_archive.close()
|
||||
manager.blocked_saves.incompatible = true
|
||||
check(manager.load_data("incompatible", "test-password").is_empty() and manager.last_error == "", "Unsupported format permits a fresh session")
|
||||
check(not manager.blocked_saves.has("incompatible") and not FileAccess.file_exists(incompatible_path), "Incompatible file no longer blocks saving")
|
||||
check(FileAccess.get_file_as_bytes(incompatible_path + ".incompatible.bak.1") == original, "Unsupported save archived without losing bytes")
|
||||
check(FileAccess.get_file_as_string(incompatible_path + ".incompatible.bak") == "keep previous backup", "Earlier archive preserved")
|
||||
check((manager.load_data("incompatible", "test-password").is_empty() and E.eq(manager.last_error, "")), "Unsupported format permits a fresh session")
|
||||
check(((not manager.blocked_saves.has("incompatible")) and (not FileAccess.file_exists(incompatible_path))), "Incompatible file no longer blocks saving")
|
||||
check(E.eq(FileAccess.get_file_as_bytes(E.add(incompatible_path, ".incompatible.bak.1")), original), "Unsupported save archived without losing bytes")
|
||||
check(E.eq(FileAccess.get_file_as_string(E.add(incompatible_path, ".incompatible.bak")), "keep previous backup"), "Earlier archive preserved")
|
||||
check(manager.request_save("incompatible", "test-password", data), "New session can queue a save")
|
||||
manager.flush_saves()
|
||||
check(manager.load_data("incompatible", "test-password").state == data.state, "New format saves and reloads after incompatible file")
|
||||
check(E.eq(manager.load_data("incompatible", "test-password").state, data.state), "New format saves and reloads after incompatible file")
|
||||
|
||||
# Expected failures below must leave the last good encrypted file intact.
|
||||
data.state.energy = -1.0
|
||||
data.state.energy = E.neg(1)
|
||||
manager.request_save("latest", "test-password", data)
|
||||
manager.flush_saves()
|
||||
check(not completions.back()[1] and manager.last_error.contains("Invalid save"), "Worker validation error reaches main thread")
|
||||
check(manager.load_data("latest", "test-password").state.energy == 3.0, "Invalid snapshot preserves previous save")
|
||||
check(((not completions.back()[1]) and manager.last_error.contains("Invalid save")), "Worker validation error reaches main thread")
|
||||
check(E.eq(manager.load_data("latest", "test-password").state.energy, 3), "Invalid snapshot preserves previous save")
|
||||
manager.blocked_saves.latest = true
|
||||
check(not manager.request_save("latest", "test-password", data), "Unreadable save protection also applies to queue")
|
||||
check((not manager.request_save("latest", "test-password", data)), "Unreadable save protection also applies to queue")
|
||||
# A valid snapshot exercises an actual I/O failure, not validation.
|
||||
data.state.energy = 5.0
|
||||
data.state.energy = 5
|
||||
manager.request_save("missing/failure", "test-password", data)
|
||||
manager.flush_saves()
|
||||
check(manager.last_error.contains("Cannot write"), "I/O error reported without hanging queue")
|
||||
|
||||
manager.request_save("shutdown", "test-password", data)
|
||||
data.state.energy = 6.0
|
||||
data.state.energy = 6
|
||||
manager.request_save("shutdown", "test-password", data)
|
||||
manager.free()
|
||||
var reader = load("res://autoload/save_manager.gd").new()
|
||||
reader.save_directory = directory
|
||||
check(reader.load_data("shutdown", "test-password").state.energy == 6.0, "Teardown drains final pending snapshot")
|
||||
check(E.eq(reader.load_data("shutdown", "test-password").state.energy, 6), "Teardown drains final pending snapshot")
|
||||
reader.free()
|
||||
world.free()
|
||||
# Exercise the stage entry point too: manager tests alone do not catch a
|
||||
@@ -100,12 +105,12 @@ func run() -> void:
|
||||
shared_manager.save_directory = directory
|
||||
var stage = load("res://stage/stage.gd").new()
|
||||
stage.pending_page = 2
|
||||
stage.owniverse.state.energy = 42.0
|
||||
stage.owniverse.state.energy = 42
|
||||
stage.save_data()
|
||||
check(shared_manager.is_saving(), "Stage queues a background save instead of waiting for disk")
|
||||
stage.owniverse.state.energy = 43.0
|
||||
stage.owniverse.state.energy = 43
|
||||
shared_manager.flush_saves()
|
||||
check(shared_manager.load_data("owniverse", "qwerty").state.energy == 42.0, "Stage save writes an independent snapshot")
|
||||
check(E.eq(shared_manager.load_data("owniverse", "qwerty").state.energy, 42), "Stage save writes an independent snapshot")
|
||||
stage.owniverse.free()
|
||||
stage.free()
|
||||
shared_manager.save_directory = original_directory
|
||||
@@ -113,5 +118,5 @@ func run() -> void:
|
||||
for filename in DirAccess.get_files_at(directory):
|
||||
DirAccess.remove_absolute(directory.path_join(filename))
|
||||
DirAccess.remove_absolute(directory)
|
||||
print("Async save: %d checks, %d failures (validation/block/I/O errors above are intentional)" % [checks, failures])
|
||||
quit(0 if failures == 0 else 1)
|
||||
print(("Async save: %d checks, %d failures (validation/block/I/O errors above are intentional)" % [checks, failures]))
|
||||
quit((0 if E.eq(failures, 0) else 1))
|
||||
|
||||
Reference in New Issue
Block a user