Huge refactor, Adding more code to engine from the game
This commit is contained in:
+414
@@ -0,0 +1,414 @@
|
||||
package edit2d
|
||||
|
||||
import "core:strings"
|
||||
import "core:os"
|
||||
import "core:bytes"
|
||||
import "core:path/filepath"
|
||||
import "core:crypto/hash"
|
||||
import "core:time"
|
||||
import b2 "vendor:box2d"
|
||||
import "core:fmt"
|
||||
import "core:encoding/cbor"
|
||||
|
||||
/*
|
||||
The engine can provide helper functions so that it'll be easier to write
|
||||
the level code in the game
|
||||
|
||||
A Typical level code
|
||||
*/
|
||||
|
||||
engine_world :: struct {
|
||||
world_id : b2.WorldId,
|
||||
|
||||
//This in engine code?
|
||||
static_indexes : map[static_index]int `cbor:"-"`,
|
||||
relations : map[^static_index][dynamic]static_index_global `cbor:"-"`,
|
||||
relations_serializeable : map[ static_index][dynamic]static_index_global,
|
||||
|
||||
|
||||
/*
|
||||
Seems okay to put the joint defs in engine rather than in game because
|
||||
we don't add more attributes in joints in the game
|
||||
|
||||
Can be changed later without requireing refactor
|
||||
*/
|
||||
revolute_joint_defs : [dynamic]revolt_joint_def,
|
||||
distant_joint_defs : [dynamic]distance_joint_def,
|
||||
joints : [dynamic]b2.JointId `cbor:"-"`,
|
||||
cam : Camera,
|
||||
name : string,
|
||||
}
|
||||
|
||||
@(require_results)
|
||||
time_to_string_hms :: proc(t: time.Time, buf: []u8) -> (res: string) #no_bounds_check {
|
||||
assert(len(buf) >= time.MIN_HMS_LEN)
|
||||
h, m, s := time.clock(t)
|
||||
|
||||
buf[7] = '0' + u8(s % 10); s /= 10
|
||||
buf[6] = '0' + u8(s)
|
||||
buf[5] = '-'
|
||||
buf[4] = '0' + u8(m % 10); m /= 10
|
||||
buf[3] = '0' + u8(m)
|
||||
buf[2] = '-'
|
||||
buf[1] = '0' + u8(h % 10); h /= 10
|
||||
buf[0] = '0' + u8(h)
|
||||
|
||||
return string(buf[:time.MIN_HMS_LEN])
|
||||
}
|
||||
|
||||
cbor_flags : cbor.Encoder_Flags :
|
||||
{
|
||||
.Self_Described_CBOR,
|
||||
.Deterministic_Int_Size,
|
||||
.Deterministic_Float_Size,
|
||||
}
|
||||
|
||||
level_load_from_files :: proc(
|
||||
$G, $L, $E: typeid,
|
||||
game: ^G,
|
||||
state: ^engine_state
|
||||
)
|
||||
{
|
||||
files, err := os.read_all_directory_by_path("levels", context.allocator)
|
||||
|
||||
for file in files
|
||||
{
|
||||
if !os.exists(file.fullpath) do continue
|
||||
if os.is_dir(file.fullpath) do continue
|
||||
|
||||
level_name := strings.clone(filepath.stem(file.fullpath))
|
||||
game.levels[level_name] = {}
|
||||
game.curr_level = strings.clone(level_name)
|
||||
level_init_from_path(L, E, &game.levels[level_name], file.fullpath, state)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Returns engine_world by reading from the file
|
||||
|
||||
|
||||
/*
|
||||
In the user code
|
||||
|
||||
level_name = strings.clone(filepath.stem(path))
|
||||
game.levels[level_name] = {}
|
||||
|
||||
level_init_from_path(&game.levels[level_name], path)
|
||||
|
||||
*/
|
||||
//We don't want the user to configure and use cbor everytime so we want to handle it
|
||||
//
|
||||
level_init_from_path :: proc(
|
||||
$T, $E: typeid,
|
||||
level_data : ^T,
|
||||
path : string,
|
||||
state: ^engine_state,
|
||||
) -> (ret : bool)
|
||||
{
|
||||
|
||||
level := cast(^engine_world)level_data
|
||||
|
||||
if os.is_file(path)
|
||||
{
|
||||
|
||||
level_name := strings.clone(filepath.stem(path))
|
||||
data, err := os.read_entire_file_from_path(path, context.allocator)
|
||||
level.name = level_name
|
||||
|
||||
//?
|
||||
level.relations = {}
|
||||
level.relations = make(map[^static_index][dynamic]static_index_global)
|
||||
|
||||
if len(data) > 0
|
||||
{
|
||||
|
||||
unmarshall_err := cbor.unmarshal(data, level_data)
|
||||
delete(data)
|
||||
|
||||
if unmarshall_err == nil
|
||||
{
|
||||
|
||||
level_reload(T, E, level_data)
|
||||
state.draw.cam = level.cam
|
||||
|
||||
for key, val in level.relations_serializeable
|
||||
{
|
||||
index := level.static_indexes[key]
|
||||
entity := level_data.entities[index]
|
||||
|
||||
level_data.relations[entity.index] = {}
|
||||
for v in val do append(&level.relations[entity.index], v)
|
||||
}
|
||||
|
||||
ret = true
|
||||
}else
|
||||
{
|
||||
ret = false
|
||||
fmt.eprintf("Faield to unmarshall file %s \n", unmarshall_err)
|
||||
}
|
||||
|
||||
}else
|
||||
{
|
||||
level_reload(T, E, level_data)
|
||||
state.draw.cam = level.cam
|
||||
|
||||
for key, val in level.relations_serializeable
|
||||
{
|
||||
index := level.static_indexes[key]
|
||||
entity := level_data.entities[index]
|
||||
|
||||
level_data.relations[entity.index] = {}
|
||||
for v in val do append(&level.relations[entity.index], v)
|
||||
}
|
||||
|
||||
//Handle no data
|
||||
fmt.println("Empty level")
|
||||
ret = false
|
||||
}
|
||||
|
||||
}else
|
||||
{
|
||||
fmt.eprintln("File not exist or is a directory")
|
||||
ret = false
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
/*
|
||||
We can't create the entities
|
||||
That should be done by the user
|
||||
*/
|
||||
level_reload :: proc($T: typeid, $E: typeid, level: ^T)
|
||||
{
|
||||
clear(&level.entities)
|
||||
clear(&level.static_indexes)
|
||||
|
||||
if level.world_id != b2.nullWorldId do b2.DestroyWorld(level.world_id)
|
||||
|
||||
//initilize world
|
||||
{
|
||||
world_def := b2.DefaultWorldDef()
|
||||
b2.SetLengthUnitsPerMeter(100)
|
||||
|
||||
//Get the gravity from config
|
||||
world_def.gravity = {0, -9.8 * 100}
|
||||
level.world_id = b2.CreateWorld(world_def)
|
||||
}
|
||||
|
||||
for &def, i in &level.entity_defs
|
||||
{
|
||||
//new_entity := engine_create_entity(&def, level.world_id, i32(i))
|
||||
new_entity : E
|
||||
new_entity.type = def.type
|
||||
new_entity.engine = engine_create_entity(&def, level.world_id, i32(i))
|
||||
|
||||
//new_entity := entity_create_by_type(&def, level.world_id, i32(i))
|
||||
|
||||
append(&level.entities, new_entity)
|
||||
|
||||
if new_entity.index != nil do level.static_indexes[new_entity.index^] = i
|
||||
|
||||
//if new_entity.type == .PLAYER do level.player_index = i32(i)
|
||||
|
||||
}
|
||||
|
||||
{
|
||||
for &def in &level.distant_joint_defs
|
||||
{
|
||||
//Get entity_a and entity_b
|
||||
|
||||
if def.entity_a <= 0 do continue
|
||||
if def.entity_b <= 0 do continue
|
||||
|
||||
entity_a := &level.entities[level.static_indexes[def.entity_a]]
|
||||
def.bodyIdA = entity_a.body_id
|
||||
|
||||
entity_b := &level.entities[level.static_indexes[def.entity_b]]
|
||||
def.bodyIdB = entity_b.body_id
|
||||
|
||||
append(&level.joints, b2.CreateDistanceJoint(level.world_id, def.def))
|
||||
}
|
||||
|
||||
for &def in &level.revolute_joint_defs
|
||||
{
|
||||
//Get entity_a and entity_b
|
||||
|
||||
if def.entity_a <= 0 do continue
|
||||
if def.entity_b <= 0 do continue
|
||||
|
||||
entity_a := &level.entities[level.static_indexes[def.entity_a]]
|
||||
def.bodyIdA = entity_a.body_id
|
||||
|
||||
entity_b := &level.entities[level.static_indexes[def.entity_b]]
|
||||
def.bodyIdB = entity_b.body_id
|
||||
|
||||
append(&level.joints, b2.CreateRevoluteJoint(level.world_id, def.def))
|
||||
}
|
||||
}
|
||||
//level.player.e = &level.entities[level.player_index]
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
Saves the level to the levels directory using level's name as filename
|
||||
It uses core library's hash and cbor to searialize the level into bytes
|
||||
|
||||
It assumes that it uses engine_world struct as it's first member
|
||||
|
||||
Checks hash of current level state and file's hash
|
||||
If both hash is same the exits
|
||||
*/
|
||||
|
||||
level_save_to_file :: proc(level_data: ^$T, state: ^engine_state)
|
||||
{
|
||||
|
||||
level := cast(^engine_world)level_data
|
||||
level_path := fmt.tprintf("levels/%s.cbor", level.name)
|
||||
|
||||
level.cam = state.draw.cam
|
||||
fmt.println(level_path)
|
||||
|
||||
//Relation serializeable
|
||||
{
|
||||
clear(&level.relations_serializeable)
|
||||
for key, val in &level.relations
|
||||
{
|
||||
if key != nil
|
||||
{
|
||||
level.relations_serializeable[key^] = {}
|
||||
for v in val do append(&level.relations_serializeable[key^], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
binary, cbor_err := cbor.marshal_into_bytes(level_data^, cbor_flags)
|
||||
|
||||
if cbor_err == nil
|
||||
{
|
||||
}else
|
||||
{
|
||||
fmt.eprintf("Failed to marshall level %s \n", cbor_err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
//Get hash of current loaded game and current saved game
|
||||
binary_hash := hash.hash_bytes(.SHA3_512, binary)
|
||||
fmt.println(binary_hash)
|
||||
file_hash, hash_err := hash.hash_file_by_name(.SHA3_512, level_path)
|
||||
|
||||
//If the hash is same that means no changes has been made
|
||||
|
||||
|
||||
if bytes.compare(binary_hash, file_hash) != 0
|
||||
{
|
||||
|
||||
if !os.exists("levels")
|
||||
{
|
||||
err := os.mkdir("levels")
|
||||
if err != nil
|
||||
{
|
||||
fmt.println("Failed to create levels directory")
|
||||
fmt.println(err)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Create backeup before saving to file
|
||||
|
||||
We create backup by copying the current level file to the backup directory
|
||||
Backup directory will contain the date and time of the backup
|
||||
*/
|
||||
|
||||
|
||||
if os.exists(level_path)
|
||||
{
|
||||
//Get time and date to create file and folder name for backup
|
||||
curr_time := time.now()
|
||||
|
||||
buf : [time.MIN_YYYY_DATE_LEN]u8
|
||||
time_buf : [time.MIN_HMS_LEN]u8
|
||||
date_str := time.to_string_yyyy_mm_dd(curr_time, buf[:])
|
||||
time_str := time_to_string_hms(curr_time, time_buf[:])
|
||||
|
||||
backup_dir_path := fmt.tprintf("levels/backups/%s",date_str)
|
||||
backup_file_path := fmt.tprintf("levels/backups/%s/%s-%s", date_str, level.name, time_str)
|
||||
|
||||
bk_err := os.make_directory_all(backup_dir_path)
|
||||
|
||||
if bk_err != nil
|
||||
{
|
||||
fmt.eprintf("Failed to create backup directory %s \n", bk_err)
|
||||
}
|
||||
else{
|
||||
bk_err := os.copy_file(backup_file_path, level_path)
|
||||
|
||||
if bk_err != nil
|
||||
{
|
||||
fmt.eprintf("Failed to copy file %s\n", bk_err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Save the current binary to file
|
||||
save_err := os.write_entire_file_from_bytes(level_path, binary)
|
||||
|
||||
if save_err != nil
|
||||
{
|
||||
fmt.eprintf("Failed to save level %s\n", save_err)
|
||||
}
|
||||
|
||||
|
||||
}else{
|
||||
fmt.println("No changes detected")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
The engine assumes that it has attribute entities
|
||||
*/
|
||||
game_to_engine_entities :: proc($Game: typeid, game_data: ^Game, state: ^engine_state)
|
||||
{
|
||||
level := &game_data.levels[game_data.curr_level]
|
||||
|
||||
interface : ^interface_state = &game_data.interface
|
||||
|
||||
clear(&interface.entities)
|
||||
clear(&interface.entity_defs)
|
||||
|
||||
for &entity in &level.entities
|
||||
{
|
||||
append(&interface.entities, &entity.engine)
|
||||
}
|
||||
for &def in &level.entity_defs
|
||||
{
|
||||
append(&interface.entity_defs, &def.engine)
|
||||
}
|
||||
|
||||
interface.state = state
|
||||
interface.world = &level.engine
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user