Skip to content

Instantly share code, notes, and snippets.

@twist84
Last active July 13, 2025 02:06
Show Gist options
  • Save twist84/8fd52529210279fd296247a96aab17e5 to your computer and use it in GitHub Desktop.
Save twist84/8fd52529210279fd296247a96aab17e5 to your computer and use it in GitHub Desktop.
c_saved_film::c_saved_film() :
m_film_header(),
m_async_double_buffer(2)
{
c_saved_film::reset_internal_state();
}
bool c_saved_film::initialize(c_allocation_base* allocator)
{
c_saved_film::reset_internal_state();
bool storage_allocated = m_async_double_buffer.allocate_storage(allocator, k_saved_film_async_io_buffer_size);
if (!storage_allocated)
{
event(_event_warning, "networking:saved_film: failed to allocate storage for film");
}
return storage_allocated;
}
void c_saved_film::dispose(c_allocation_base* allocator)
{
c_saved_film::close();
m_async_double_buffer.release_storage(allocator);
}
// $TODO: check my work
bool c_saved_film::open_for_read(const char* filename, e_controller_index controller_index, bool disable_version_check)
{
ASSERT(m_film_state == k_saved_film_state_none);
ASSERT(m_content_controller_index == k_no_controller);
if (controller_index < k_number_of_controllers)
{
c_content_catalogue* content_catalogue = content_catalogue_get_interface(controller_index);
file_reference_create_from_path(&m_file_reference, filename, false);
int32 content_item_index = content_catalogue->content_item_try_and_get_from_file_reference(&m_file_reference);
if (content_item_index != NONE)
{
if (!content_catalogue->content_item_mount(content_item_index, true))
{
event(_event_warning, "networking:saved_film: failed to open content item for saved film read (file= '%s')",
filename);
event(_event_warning, "networking:saved_film: failed to open saved film '%s' for reading",
filename);
c_saved_film::close();
return false;
}
m_content_controller_index = controller_index;
}
}
bool read_success = m_async_double_buffer.open_file(filename, _async_buffer_file_access_read, _async_buffer_disposition_open_existing);
if (!read_success)
{
event(_event_warning, "networking:saved_film: async_double_buffer.open_file() failed for '%s'",
filename);
event(_event_warning, "networking:saved_film: failed to open saved film '%s' for reading",
filename);
c_saved_film::close();
return false;
}
m_film_state = _saved_film_open_for_read;
read_success = c_saved_film::read_data(&m_film_header, k_saved_film_async_io_buffer_size);
if (!read_success)
{
event(_event_warning, "networking:saved_film: failed to get saved film header for '%s'",
filename);
event(_event_warning, "networking:saved_film: failed to open saved film '%s' for reading",
filename);
c_saved_film::close();
return false;
}
read_success = c_saved_film::header_valid(&m_film_header, disable_version_check);
if (!read_success)
{
event(_event_warning, "networking:saved_film: film header not valid for '%s'",
filename);
event(_event_warning, "networking:saved_film: failed to open saved film '%s' for reading",
filename);
c_saved_film::close();
return false;
}
m_current_film_offset = k_saved_film_async_io_buffer_size;
return read_success;
}
bool c_saved_film::read_update(s_saved_film_update* update_out)
{
ASSERT(update_out);
ASSERT(m_film_state == _saved_film_open_for_read);
uns32 header = 0;
if (!c_saved_film::read_data(&header, sizeof(header)))
{
event(_event_warning, "networking:saved_film: failed to read header");
return false;
}
if (!c_saved_film::parse_update_header(header, update_out))
{
event(_event_warning, "networking:saved_film: failed to parse header %d",
header);
return false;
}
return true;
}
bool c_saved_film::read_simulation_update(cnst s_saved_film_update* update, struct simulation_update* simulation_update_out)
{
ASSERT(simulation_update_out);
ASSERT(update->update_type == _saved_film_update_type_simulation_update);
ASSERT(m_film_state==_saved_film_open_for_read);
if (update->update_size <= 0 || update->update_size > sizeof(s_blf_saved_film))
{
event(_event_error, "networking:saved_film: read bad update size %d",
update->update_size);
return false;
}
if (!c_saved_film::read_data(data, update_size))
{
event(_event_warning, "networking:saved_film: failed to read update [size %d]",
update->update_size);
return false;
}
if (!simulation_update_read_from_buffer(simulation_update_out, update->update_size, data))
{
event(_event_warning, "networking:saved_film: read failed to decode update from buffer [size %d]",
update->update_size);
return false;
}
m_current_tick++;
return true;
}
bool c_saved_film::read_gamestate(const s_saved_film_update* update, void* compressed_game_state_buffer_out, int32 buffer_size, int32* compressed_game_state_size_out, int32* update_number_out)
{
ASSERT(update);
ASSERT(compressed_game_state_buffer_out);
ASSERT(buffer_size >= k_game_state_maximum_compressed_size);
ASSERT(update->update_type == _saved_film_update_type_gamestate);
ASSERT(update->update_size == sizeof(s_saved_film_gamestate_header));
ASSERT(buffer_size >= k_game_state_maximum_compressed_size);
ASSERT(m_film_state==_saved_film_open_for_read);
s_saved_film_gamestate_header saved_film_gamestate_header{};
if (!c_saved_film::read_data(&saved_film_gamestate_header, sizeof(s_saved_film_gamestate_header)))
{
event(_event_error, "networking:saved_film: failed to read game state header [header size %d]",
sizeof(s_saved_film_gamestate_header));
return false;
}
if (saved_film_gamestate_header.compressed_size > buffer_size)
{
event(_event_error, "networking:saved_film: read compressed game state size larger than buffer [%d > %d]",
saved_film_gamestate_header.compressed_size,
buffer_size);
return false;
}
if (!c_saved_film::read_data(compressed_game_state_buffer_out, saved_film_gamestate_header.compressed_size))
{
event(_event_error, "networking:saved_film: read failed to read compressed game state [size %d]",
saved_film_gamestate_header.compressed_size);
return false;
}
uns32 checksum = fast_checksum_buffer(NONE, compressed_game_state_buffer_out, saved_film_gamestate_header.compressed_size);
if (checksum != saved_film_gamestate_header.checksum)
{
event(_event_error, "networking:saved_film: read compressed game state checksum mismatch [0x%08X != 0x%08X]",
checksum,
saved_film_gamestate_header.checksum);
return false;
}
*compressed_game_state_size_out = saved_film_gamestate_header.compressed_size;
*update_number_out = saved_film_gamestate_header.update_number;
return true;
}
int32 c_saved_film::get_position()
{
ASSERT(m_film_state==_saved_film_open_for_read || m_film_state==_saved_film_open_for_write);
ASSERT(m_async_double_buffer.ready_to_read() || m_async_double_buffer.ready_to_write());
return m_async_double_buffer.get_position();
}
bool c_saved_film::set_position(int32 position)
{
ASSERT(m_film_state==_saved_film_open_for_read || m_film_state==_saved_film_open_for_write);
ASSERT(m_async_double_buffer.ready_to_read() || m_async_double_buffer.ready_to_write());
if (!m_async_double_buffer.set_position(position))
{
event(_event_warning, "networking:saved_film: failed to set film position [%d]",
position);
return false;
}
return true;
}
//bool c_saved_film::open_for_write(const char* filename, const game_options* options, e_controller_index controller_index)
// $TODO: check my work
bool c_saved_film::write_simulation_update(const struct simulation_update* update)
{
ASSERT(update);
ASSERT(m_film_state == _saved_film_open_for_write);
if (!simulation_update_write_to_buffer(update, sizeof(s_blf_saved_film), buffer, &out_update_length))
{
event(_event_error, "networking:saved_film: write failed to encode simulation update");
event(_event_warning, "networking:saved_film: marking fatal error in saved film");
m_fatal_error_encountered_during_write = true;
return false;
}
if (update->update_size <= 0 || update->update_size > k_saved_film_update_size_max)
{
event(_event_error, "networking:saved_film: write bad encoded size %d",
out_update_length);
event(_event_warning, "networking:saved_film: marking fatal error in saved film");
m_fatal_error_encountered_during_write = true;
return false;
}
int32 current_film_offset = m_current_film_offset;
int32 update_length = FLAG(k_saved_film_update_size_bits);
if (out_update_length < FLAG(k_saved_film_update_size_bits))
{
update_length = out_update_length;
}
if (out_update_length + (k_saved_film_async_io_buffer_size + sizeof(update_length)) + current_film_offset >= k_saved_film_maximum_size)
{
event(_event_warning, "networking:saved_film: write would exceed max saved film size, closing",
out_update_length);
return false;
}
if (!c_saved_film::write_data(&update_length, sizeof(update_length)))
{
event(_event_warning, "networking:saved_film: write failed to write header for update [size %d]",
out_update_length);
event(_event_warning, "networking:saved_film: marking fatal error in saved film");
m_fatal_error_encountered_during_write = true;
return false;
}
if (!c_saved_film::write_data(buffer, out_update_length))
{
event(_event_warning, "networking:saved_film: write failed to write encoded update [size %d]",
out_update_length);
event(_event_warning, "networking:saved_film: marking fatal error in saved film");
m_fatal_error_encountered_during_write = true;
return false;
}
m_current_tick++;
return true;
}
// $TODO: check my work
bool c_saved_film::write_gamestate()
{
ASSERT(m_film_state == _saved_film_open_for_write);
c_game_state_compressor* compressor = game_state_get_compressor();
byte* compressed_game_state = NULL;
int32 compressed_size = 0;
if (!compressor->lock())
{
event(_event_error, "networking:saved_film: failed to lock game state compressor for write_gamestate");
event(_event_warning, "networking:saved_film: failed to write gamestate, marking as fatal error");
m_fatal_error_encountered_during_write = true;
return false;
}
if (!c_saved_film::write_game_state_internal(0, compressed_game_state, compressed_size))
{
event(_event_warning, "networking:saved_film: failed to write compressed gamestate [%d/%d]",
(uns32)compressed_game_state,
compressed_size);
compressor->unlock();
event(_event_warning, "networking:saved_film: failed to write gamestate, marking as fatal error");
m_fatal_error_encountered_during_write = true;
return false;
}
compressor->unlock();
return true;
}
// $TODO: check my work
bool c_saved_film::write_gamestate_from_buffer(int32 update_number, const void* gamestate, int32 gamestate_size)
{
ASSERT(m_film_state == _saved_film_open_for_write);
c_game_state_compressor* compressor = game_state_get_compressor();
byte* compressed_game_state = NULL;
int32 compressed_size = 0;
if (!compressor->lock())
{
event(_event_error, "networking:saved_film: write lock game state compressor for write_gamestate_from_buffer");
event(_event_warning, "networking:saved_film: failed to write gamestate from buffer, marking as fatal error");
m_fatal_error_encountered_during_write = true;
return false;
}
if (!compressor->game_state_get_compressed_from_buffer(_game_state_compression_level_fast, gamestate, gamestate_size, &compressed_game_state, &compressed_size))
{
event(_event_error, "networking:saved_film: write failed to compress game state");
compressor->unlock();
event(_event_warning, "networking:saved_film: failed to write gamestate from buffer, marking as fatal error");
m_fatal_error_encountered_during_write = true;
return false;
}
if (!c_saved_film::write_game_state_internal(update_number, compressed_game_state, compressed_size))
{
event(_event_warning, "networking:saved_film: failed to write compressed gamestate from buffer [%d/%d]",
(uns32)compressed_game_state,
compressed_size);
compressor->unlock();
event(_event_warning, "networking:saved_film: failed to write gamestate from buffer, marking as fatal error");
m_fatal_error_encountered_during_write = true;
return false;
}
compressor->unlock();
return true;
}
int32 c_saved_film::get_ticks_remaining() const
{
ASSERT(m_film_state == _saved_film_open_for_read);
return m_film_header.film_header.length_in_ticks - m_current_tick;
}
int32 c_saved_film::get_current_tick() const
{
ASSERT(m_film_state == _saved_film_open_for_read || m_film_state == _saved_film_open_for_write);
return m_current_tick;
}
int32 c_saved_film::get_length_in_ticks() const
{
ASSERT(m_film_state==_saved_film_open_for_read);
return m_film_header.film_header.length_in_ticks;
}
e_saved_film_state c_saved_film::get_film_state() const
{
return m_film_state;
}
game_options* c_saved_film::get_game_options()
{
ASSERT(m_film_state!=k_saved_film_state_none);
return &m_film_header.film_header.options;
}
const s_blf_chunk_content_header* c_saved_film::get_film_content_header() const
{
return &m_film_header.content_header;
}
bool c_saved_film::contains_gamestate() const
{
return m_film_header.film_header.contains_gamestate;
}
bool c_saved_film::is_snippet() const
{
return m_film_header.film_header.is_snippet;
}
int32 c_saved_film::get_snippet_start_tick() const
{
if (!m_film_header.film_header.is_snippet)
{
return NONE;
}
return m_film_header.film_header.snippet_start_tick;
}
bool c_saved_film::handle_revert(int32 file_position, int32 film_tick)
{
ASSERT(m_film_state == _saved_film_open_for_read);
if (!m_async_double_buffer.set_position(file_position))
{
event(_event_warning, "networking:saved_film: failed to set file position for revert %d [tick %d]",
file_position,
film_tick);
return false;
}
m_current_tick = film_tick;
return true;
}
bool c_saved_film::close()
{
bool result = false;
bool open_for_write = false;
switch (m_film_state)
{
case _saved_film_open_for_read:
{
event(_event_message, "networking:saved_film: closing film");
result = true;
}
break;
case _saved_film_open_for_write:
{
if (!c_saved_film::write_finish())
{
event(_event_warning, "networking:saved_film: failed to finish writing the film, film is invalid");
break;
}
if (m_fatal_error_encountered_during_write)
{
break;
}
result = true;
open_for_write = true;
}
break;
}
m_async_double_buffer.close_file();
if (open_for_write && result)
{
char filename_buffer[256]{};
file_reference_get_name(&m_file_reference, FLAG(_name_filename_bit), filename_buffer, sizeof(filename_buffer));
c_static_string<256> destination_filename{};
destination_filename.set(filename_buffer);
destination_filename.append(".film");
file_rename(&m_file_reference, destination_filename.get_string());
}
if (VALID_INDEX(m_content_controller_index, k_number_of_controllers))
{
c_content_catalogue* content_catalogue = content_catalogue_get_interface(m_content_controller_index);
if (content_catalogue->content_item_try_and_get_from_file_reference(&m_file_reference) == NONE)
{
event(_event_warning, "networking:saved_film: on close failed to get content item for controller %d",
m_content_controller_index);
}
}
c_saved_film::reset_internal_state();
return result;
}
// $TODO: check my work
bool c_saved_film::header_valid(const s_blf_saved_film* header, bool disable_version_check) const
{
ASSERT(header);
if (header->film_data.header.chunk_size < 0 || header->film_data.header.chunk_size + k_maximum_game_state_header_size > k_saved_film_maximum_size)
{
event(_event_warning, "networking:saved_film: film payload %d bytes is outside valid range (max %d)",
header->film_data.header.chunk_size,
k_saved_film_maximum_size);
return false;
}
if (!disable_version_check)
{
int32 local_executable_type = 0;
int32 local_executable_version = 0;
int32 local_compatible_version = 0;
network_get_build_identifiers(&local_executable_type, &local_executable_version, &local_compatible_version);
int32 local_determinism_version = 0;
int32 local_determinism_compatible_version = 0;
game_get_determinism_versions(&local_determinism_version, &local_determinism_compatible_version);
if (header->film_header.build_compatibility.executable_type != local_executable_type)
{
event(_event_warning, "networking:saved_film: executable type mismatch [%d!=%d], film not valid",
header->film_header.build_compatibility.executable_type,
local_executable_type);
return false;
}
if (!game_determinism_version_compatible(header->film_header.options.determinism_version))
{
event(_event_warning, "networking:saved_film: determinism version mismatch [film %d local %d/%d], film not valid",
header->film_header.options.determinism_version,
local_determinism_version,
local_determinism_compatible_version);
return false;
}
if (header->film_header.build_compatibility.network_executable_version != local_executable_version)
{
event(_event_warning, "networking:saved_film: film executable version is different than local [%d != %d], relying on deterministic version",
header->film_header.build_compatibility.network_executable_version,
local_executable_version);
}
}
char error_string[512]{};
if (!game_options_verify(&header->film_header.options, error_string, sizeof(error_string)))
{
event(_event_warning, "networking:saved_film: film header has invalid game options (%s)",
error_string);
return false;
}
if (header->film_header.length_in_ticks <= 0)
{
event(_event_warning, "networking:saved_film: film header has invalid length in ticks [%d]",
header->film_header.length_in_ticks);
return false;
}
if (header->film_header.is_snippet)
{
if (!header->film_header.contains_gamestate)
{
event(_event_warning, "networking:saved_film: film is marked as snippet but does not contain gamestate?");
return false;
}
if (header->film_header.snippet_start_tick < 0)
{
event(_event_warning, "networking:saved_film: film is marked as snippet but has bad snippet start tick %d",
header->film_header.snippet_start_tick);
return false;
}
}
return true;
}
bool c_saved_film::parse_update_header(uns32 header, s_saved_film_update* update_out) const
{
e_saved_film_update_type update_type = (e_saved_film_update_type)(header >> k_saved_film_update_size_bits);
int32 update_size = (int32)(header & k_saved_film_update_size_max);
if (update_type > _saved_film_update_type_gamestate)
{
event(_event_error, "networking:saved_film: header has bad update type %d",
update_type);
return false;
}
if (update->update_size <= 0 || update->update_size > k_saved_film_update_size_max)
{
event(_event_error, "networking:saved_film: header has bad update size %d",
update_size);
return false;
}
update_out->update_type = update_type;
update_out->update_size = update_size;
return true;
}
uns32 c_saved_film::build_update_header(const s_saved_film_update* update) const
{
ASSERT(update->update_size > 0 && update->update_size <= k_saved_film_update_size_max);
if (update->update_size > k_saved_film_update_size_max)
{
return (update->update_type << k_saved_film_update_size_bits) | FLAG(k_saved_film_update_size_bits);
}
int32 update_size = 0;
if (update->update_size > 0)
{
update_size = update->update_size;
}
return update_size | (update->update_type << k_saved_film_update_size_bits)
}
e_controller_index c_saved_film::get_cached_controller_index() const
{
return m_content_controller_index;
}
s_file_reference* c_saved_film::get_file_reference()
{
return &m_file_reference;
}
void c_saved_film::mark_film_as_snippet(int32 start_tick)
{
ASSERT(m_film_state == _saved_film_open_for_write);
ASSERT(start_tick != NONE);
ASSERT(start_tick >= 0);
m_film_header.film_header.snippet_start_tick = start_tick;
m_film_header.film_header.is_snippet = true;
}
int32 c_saved_film::get_map_signature_size() const
{
return m_film_header.film_header.build_compatibility.map_signature_size;
}
const byte* c_saved_film::get_map_signature_bytes() const
{
return m_film_header.film_header.build_compatibility.map_signature_bytes;
}
//void c_saved_film::copy_film(const char* source_file, const char* destination_file)
void c_saved_film::reset_internal_state()
{
m_film_state = k_saved_film_state_none;
csmemset(&m_film_header, 0, sizeof(s_blf_saved_film));
m_start_of_film_data_offset = 0;
m_current_film_offset = 0;
m_current_tick = 0;
m_async_double_buffer.initialize();
csmemset(&m_file_reference, 0, sizeof(s_file_reference));
m_content_controller_index = k_no_controller;
m_fatal_error_encountered_during_write = false;
m_finalizing_film = false;
}
bool c_saved_film::write_game_state_internal(int32 update_number, const void* compressed_game_state, int32 compressed_game_state_size)
{
ASSERT(m_film_state == _saved_film_open_for_write);
uns32 header = 0x4000000C;
if (!c_saved_film::write_data(&header, sizeof(header)))
{
event(_event_warning, "networking:saved_film: write failed to write header for game state [compressed size %d]",
compressed_game_state_size);
m_fatal_error_encountered_during_write = true;
return false;
}
s_saved_film_gamestate_header saved_film_gamestate_header{};
saved_film_gamestate_header.compressed_size = compressed_game_state_size;
saved_film_gamestate_header.checksum = fast_checksum_buffer(NONE, compressed_game_state, compressed_game_state_size);
saved_film_gamestate_header.update_number = update_number;
if (!c_saved_film::write_data(&saved_film_gamestate_header, sizeof(s_saved_film_gamestate_header)))
{
event(_event_warning, "networking:saved_film: write failed to game state header [header size %d]",
sizeof(s_saved_film_gamestate_header));
m_fatal_error_encountered_during_write = true;
return false;
}
if (!c_saved_film::write_data(compressed_game_state, compressed_game_state_size))
{
event(_event_warning, "networking:saved_film: write failed to write compressed game state [size %d]",
compressed_game_state_size);
m_fatal_error_encountered_during_write = true;
return false;
}
m_film_header.film_header.contains_gamestate = true;
return true;
}
bool c_saved_film::write_finish()
{
ASSERT(m_film_state==_saved_film_open_for_write);
m_finalizing_film = true;
if (!async_usable())
{
event(_event_warning, "networking:saved_film: async not usable,won't be able to finish the write!");
m_finalizing_film = false;
return false;
}
if (m_fatal_error_encountered_during_write)
{
event(_event_warning, "networking:saved_film: finishing saved film write with an encountered fatal error");
}
else
{
ASSERT(m_current_film_offset == m_async_double_buffer.get_position());
ASSERT(m_current_film_offset>=sizeof(s_blf_saved_film) && m_current_film_offset<=k_saved_film_maximum_size);
}
s_blf_chunk_end_of_file end_of_file{};
{
int32 current_film_offset = this->m_current_film_offset;
end_of_file.total_file_size = m_async_double_buffer.get_position();
bool write_success = c_saved_film::write_data(&end_of_file, sizeof(end_of_file));
if (!write_success)
{
event(_event_error, "networking:saved_film: saved_film_write_finish failed to write footer");
}
m_current_film_offset = current_film_offset;
}
int32 position = m_async_double_buffer.get_position();
int32 current_film_offset = this->m_current_film_offset;
if (write_success)
{
// $TODO: implement this
}
m_finalizing_film = false;
return write_success;
}
bool c_saved_film::read_data(void* data, int32 data_size)
{
ASSERT(data);
ASSERT(data_size>0);
ASSERT(m_film_state==_saved_film_open_for_read);
ASSERT(m_async_double_buffer.ready_to_read());
int32 bytes_read = 0;
m_async_double_buffer.read(data, data_size, &bytes_read);
if (bytes_read != data_size)
{
event(_event_warning, "networking:saved_film: film async operation returned unexpected results. Wanted to read %d bytes, but async buffer read only %d.",
data_size,
bytes_read);
return false;
}
return true;
}
bool c_saved_film::write_data(const void* data, int32 data_size)
{
ASSERT(m_film_state == _saved_film_open_for_write);
if (!m_finalizing_film && data_size + m_current_film_offset > k_saved_film_maximum_size)
{
event(_event_warning, "networking:saved_film: the film exceeds the maximum size allowable. Trying to write %d bytes at offset %d (maximum film size %d)",
data_size,
m_current_film_offset,
k_saved_film_maximum_size);
return false;
}
int32 bytes_written = 0;
m_async_double_buffer.write(data, data_size, &bytes_written);
m_current_film_offset += bytes_written;
if (bytes_written != data_size)
{
event(_event_warning, "networking:saved_film: film async operation returned unexpected results. Wanted to write %d bytes, but async buffer wrote only %d.",
data_size,
bytes_written);
return false;
}
return true;
}
c_saved_film::~c_saved_film()
{
}
#pragma once
enum
{
k_saved_film_update_size_bits = 30,
k_saved_film_update_size_max = FLAG(k_saved_film_update_size_bits) - 1
}
enum
{
k_saved_film_maximum_size = 0x6400000,
k_saved_film_async_io_buffer_size = 0x10000,
k_saved_film_maximum_map_signature_bytes = 0x3C,
};
class c_saved_film
{
public:
c_saved_film();
bool initialize(c_allocation_base* allocator);
void dispose(c_allocation_base* allocator);
bool open_for_read(const char* filename, e_controller_index controller_index, bool disable_version_check);
bool read_update(s_saved_film_update* update_out);
bool read_simulation_update(const s_saved_film_update* update, struct simulation_update* simulation_update_out);
bool read_gamestate(const s_saved_film_update* update, void* compressed_game_state_buffer_out, int32 buffer_size, int32* compressed_game_state_size_out, int32* update_number_out);
int32 get_position();
bool set_position(int32 position);
bool open_for_write(const char* filename, const game_options* options, e_controller_index controller_index);
bool write_simulation_update(const struct simulation_update* update);
bool write_gamestate();
bool write_gamestate_from_buffer(int32 update_number, const void* gamestate, int32 gamestate_size);
int32 get_ticks_remaining() const;
int32 get_current_tick() const;
int32 get_length_in_ticks() const;
e_saved_film_state get_film_state() const;
game_options* get_game_options();
const s_blf_chunk_content_header* get_film_content_header() const;
bool contains_gamestate() const;
bool is_snippet() const;
int32 get_snippet_start_tick() const;
bool handle_revert(int32 file_position, int32 film_tick);
bool close();
bool header_valid(const s_blf_saved_film* header, bool disable_version_check) const;
bool parse_update_header(uns32 header, s_saved_film_update* update_out) const;
uns32 build_update_header(const s_saved_film_update* update) const;
e_controller_index get_cached_controller_index() const;
s_file_reference* get_file_reference();
void mark_film_as_snippet(int32 start_tick);
int32 get_map_signature_size() const;
const byte* get_map_signature_bytes() const;
void copy_film(const char* source_file, const char* destination_file);
private:
void reset_internal_state();
bool write_game_state_internal(int32 update_number, const void* compressed_game_state, int32 compressed_game_state_size);
bool write_finish();
bool read_data(void* data, int32 data_size);
bool write_data(const void* data, int32 data_size);
public:
~c_saved_film();
c_saved_film& operator=(const c_saved_film&) = default;
private:
e_saved_film_state m_film_state;
s_blf_saved_film m_film_header;
int32 m_start_of_film_data_offset;
int32 m_current_film_offset;
int32 m_current_tick;
c_async_stored_buffer_set<2> m_async_double_buffer;
s_file_reference m_file_reference;
e_controller_index m_content_controller_index;
bool m_fatal_error_encountered_during_write;
bool m_finalizing_film;
};
namespace
{
bool saved_film_manager_should_record_film_default = false;
}
bool saved_film_manager_should_record_film_default = false;
c_interlocked_long g_universal_saved_film_tick;
s_saved_film_manager_globals saved_film_manager_globals{};
static int32 saved_film_pending_seek_film_tick = NONE;
c_physical_memory_allocation g_physical_memory_allocation;
static real32 saved_film_set_pending_playback_game_speed = -1.0f;
void saved_film_manager_abort_playback(e_saved_film_playback_abort_reason abort_reason)
{
if (saved_film_manager_globals.playback_aborted)
{
return;
}
ASSERT(game_is_playback());
event(_event_warning, "networking:saved_film:manager: playback aborted due to error '%s'",
k_playback_abort_reason_names[abort_reason]);
saved_film_manager_globals.playback_aborted = true;
saved_film_manager_globals.playback_abort_reason = abort_reason;
}
bool saved_film_manager_authored_camera_locked_for_snippet()
{
return game_in_progress()
&& game_playback_get() == _game_playback_film
&& saved_film_manager_globals.saved_film.m_film_state == _saved_film_open_for_read
&& !saved_film_manager_globals.film_ended
&& saved_film_snippet_recording_or_previewing();
}
bool saved_film_manager_automatic_debug_saving_enabled()
{
return saved_film_manager_globals.automatic_debug_saving_enabled;
}
void saved_film_manager_build_file_path_from_name(const char* film_name, e_saved_film_file_path_creation_purpose purpose, c_static_string<128>* film_path_out)
{
ASSERT(film_name);
ASSERT(film_path_out);
switch (purpose)
{
case _file_path_for_creation:
{
const char* filename_prefix = "";
const char* directory_path = autosave_queue_get_directory_path();
if (autosave_queue_get_filename_prefix())
{
filename_prefix = autosave_queue_get_filename_prefix();
}
film_path_out.print("%s\\%s_%s.film", directory_path, filename_prefix, film_name);
}
break;
case _file_path_for_creation_final:
{
const char* filename_prefix = "";
const char* directory_path = autosave_queue_get_directory_path();
if (autosave_queue_get_filename_prefix())
{
filename_prefix = autosave_queue_get_filename_prefix();
}
film_path_out.print("%s\\%s_%s.film", directory_path, filename_prefix, film_name);
}
break;
case _file_path_for_reading:
{
film_path_out.print("%s.film", film_name);
}
break;
default:
{
throw "unreachable";
}
break;
}
}
bool saved_film_manager_can_revert(e_saved_film_revert_type desired_revert_type)
{
return saved_film_history_ready_for_revert_or_reset() && saved_film_history_can_revert_by_type(desired_revert_type);
}
bool saved_film_manager_can_set_playback_control()
{
if (saved_film_manager_globals.playback_locked)
{
return false;
}
if (!game_is_authoritative_playback())
{
return false;
}
if (saved_film_manager_globals.film_ended)
{
return false;
}
if (saved_film_manager_get_snippet_state() != _saved_film_snippet_state_none)
{
return false;
}
if (saved_film_manager_globals.snippet_start_tick != NONE &&
g_universal_saved_film_tick.peek() < saved_film_manager_globals.snippet_start_tick)
{
return false;
}
return true;
}
void saved_film_manager_clear_playback_state()
{
saved_film_manager_globals.authored_cam_set_for_user.clear();
saved_film_manager_globals.valid_camera_mask = 0;
saved_film_manager_globals.desired_revert_index = NONE;
saved_film_manager_globals.snippet_start_tick = NONE;
saved_film_manager_globals.playback_game_speed = 1.0f;
saved_film_manager_globals.camera_updates.clear();
saved_film_manager_globals.film_ended = false;
saved_film_manager_globals.film_ended_sound_disabled = false;
saved_film_manager_globals.playback_locked = false;
saved_film_manager_globals.desired_revert_type = _saved_film_revert_none;
saved_film_manager_globals.seek_film_tick = NONE;
saved_film_manager_globals.seek_and_stop = false;
g_universal_saved_film_tick.set(0);
saved_film_manager_globals.playback_aborted = false;
saved_film_manager_globals.playback_abort_reason = _saved_film_playback_abort_reason_none;
}
void saved_film_manager_close()
{
if (saved_film_manager_globals.film_close_in_progress)
{
event(_event_warning, "networking:saved_film:manager: film close in progress, not closing again");
return;
}
saved_film_manager_globals.film_close_in_progress = true;
saved_film_manager_globals.saved_film.close();
saved_film_manager_clear_playback_state();
saved_film_manager_globals.pending_gamestate_load = false;
saved_film_manager_globals.gamestate_file_position = NONE;
saved_film_manager_globals.film_close_in_progress = false;
}
void saved_film_manager_commit_snippet_autoname(e_controller_index controller_index)
{
e_saved_film_snippet_state snippet_state = saved_film_manager_get_snippet_state();
if (snippet_state == _saved_film_snippet_state_none)
{
event(_event_warning, "networking:saved_film:manager: snippts not available (can't commit by autoname)");
return;
}
if (snippet_state != _saved_film_snippet_state_recorded_and_ready)
{
event(_event_warning, "networking:saved_film:manager: can't commit snippet by autoname [current in state %d]",
snippet_state);
return;
}
if (!saved_film_snippet_commit_by_autoname(controller_index))
{
saved_film_manager_abort_playback(_saved_film_playback_abort_snippet_failed_to_commit);
return;
}
event(_event_message, "networking:saved_film:manager: committing snippet by autoname");
}
void saved_film_manager_commit_snippet_keyboard(e_controller_index controller_index)
{
e_saved_film_snippet_state snippet_state = saved_film_manager_get_snippet_state();
if (snippet_state == _saved_film_snippet_state_none)
{
event(_event_warning, "networking:saved_film:manager: snippts not available (can't commit by keyboard)");
return;
}
if (snippet_state != _saved_film_snippet_state_recorded_and_ready)
{
event(_event_warning, "networking:saved_film:manager: can't commit snippet by keyboard [current in state %d]",
snippet_state);
return;
}
if (!saved_film_snippet_commit_by_keyboard(controller_index))
{
saved_film_manager_abort_playback(_saved_film_playback_abort_snippet_failed_to_commit);
return;
}
event(_event_message, "networking:saved_film:manager: committing snippet by keyboard");
}
void saved_film_manager_copy_film_to_debug_path()
{
ASSERT(saved_film_manager_globals.initialized);
if (!saved_film_manager_globals.automatic_debug_saving_enabled || global_scenario->type > _scenario_type_multiplayer)
{
return;
}
void* blf_saved_film = overlapped_malloc(sizeof(s_blf_saved_film));
if (!blf_saved_film)
{
event(_event_warning, "networking:saved_film:manager: failed to allocate copy buffer to save last saved film to the xbox dev kit drive!");
return;
}
c_synchronized_long save_film_success = 0;
c_synchronized_long save_film_complete = 0;
if (saved_game_files_save_last_film_to_debugging_hard_drive(
_controller0,
blf_saved_film,
sizeof(s_blf_saved_film),
&save_film_success,
&save_film_complete) == NONE)
{
event(_event_warning, "networking:saved_film:manager: failed to allocate async copy task to save last saved film to the xbox dev kit drive!");
}
else
{
internal_async_yield_until_done(&save_film_complete, false, false, __FILE__, __LINE__);
if (save_film_success.peek() != 1)
{
event(_event_warning, "networking:saved_film:manager: failed to save last saved film to the xbox dev kit drive!");
}
}
overlapped_free(blf_saved_film);
}
void saved_film_manager_create_film_directory()
{
s_file_reference film_directory{};
const char* directory_path = autosave_queue_get_directory_path();
if (!file_reference_create_from_path(&film_directory, directory_path, true))
{
return;
}
if (file_exists(&film_directory))
{
return;
}
file_create_parent_directories_if_not_present(&film_directory);
}
void saved_film_manager_delete_current_snippet()
{
if (!saved_film_manager_snippets_available())
{
event(_event_warning, "networking:saved_film:manager: snippts not available (can't delete)");
return;
}
e_saved_film_snippet_state current_state = saved_film_snippet_get_current_state();
if (current_state != _saved_film_snippet_state_recorded_and_ready)
{
event(_event_warning, "networking:saved_film:manager: can't delete snippet [current in state %d]",
current_state);
return;
}
if (!saved_film_snippet_delete())
{
saved_film_manager_abort_playback(_saved_film_playback_abort_snippet_failed_to_delete);
return;
}
event(_event_message, "networking:saved_film:manager: deleted snippet");
}
void saved_film_manager_delete_on_level_load(bool delete_on_level_load)
{
event(_event_error, "networking:saved_film:manager: this no longer does anything");
}
void saved_film_manager_disable_version_checking(bool disable)
{
saved_film_manager_globals.disable_version_checking = disable;
}
void saved_film_manager_dispose_from_old_map()
{
if (game_is_ui_shell() || main_game_reset_in_progress())
{
return;
}
saved_film_manager_close();
saved_film_history_dispose_from_saved_film_playback();
saved_film_snippet_dispose_from_saved_film_playback();
saved_film_manager_globals.screensaver_enabled = true;
}
void saved_film_manager_dispose()
{
ASSERT(saved_film_manager_globals.initialized);
saved_film_manager_globals.saved_film.dispose(&g_physical_memory_allocation);
saved_film_manager_globals.saved_film_name.clear();
saved_film_manager_globals.initialized = false;
c_saved_film_scratch_memory::get()->dispose();
}
void saved_film_manager_end_film_internal()
{
if (saved_film_manager_globals.film_ended)
{
event(_event_warning, "networking:saved_film:manager: film ended already set, not ending the film again");
return;
}
saved_film_manager_globals.film_ended = true;
saved_film_manager_globals.film_ended_system_milliseconds = system_milliseconds();
if (game_is_authoritative_playback())
{
simulation_notify_saved_film_ended();
}
}
bool saved_film_manager_film_is_ended(real32* out_seconds_ago)
{
if (!game_in_progress() || !game_is_playback() || !saved_film_manager_globals.film_ended)
{
return false;
}
if (out_seconds_ago)
{
*out_seconds_ago = (float)system_milliseconds() * 0.001f;
}
return true;
}
bool saved_film_manager_film_valid(e_controller_index controller, const char* film_name)
{
ASSERT(saved_film_manager_globals.initialized);
if (saved_film_manager_globals.saved_film.m_film_state != k_saved_film_state_none)
{
event(_event_warning, "networking:saved_film:manager: can't validate film, as we have one open already [state %d]",
saved_film_manager_globals.saved_film.m_film_state);
return false;
}
bool result = saved_film_manager_open_film_for_reading(controller, film_name);
saved_film_manager_close();
return result;
}
bool saved_film_manager_get_current_film_name(c_static_string<64>* film_name_out)
{
ASSERT(film_name_out);
film_name_out->set(saved_film_manager_globals.saved_film_name.get_string());
return true;
}
const game_options* saved_film_manager_get_current_game_options()
{
if (saved_film_manager_globals.saved_film.m_film_state)
{
return NULL;
}
return saved_film_manager_globals.saved_film.get_game_options();
}
const s_saved_game_item_metadata* saved_film_manager_get_current_metadata()
{
if (saved_film_manager_globals.saved_film.m_film_state)
{
return NULL;
}
if (!saved_film_manager_globals.saved_film.m_film_header.content_header.metadata.is_valid())
{
return NULL;
}
return &saved_film_manager_globals.saved_film.m_film_header.content_header.metadata;
}
int32 saved_film_manager_get_current_tick_estimate()
{
reutrn g_universal_saved_film_tick.peek();
}
int32 saved_film_manager_get_current_tick()
{
ASSERT(saved_film_manager_globals.initialized);
return saved_film_manager_globals.saved_film.get_current_tick();
}
void saved_film_manager_get_director_state(s_saved_film_manager_director_state* director_state_out)
{
csmemset(director_state_out, 0, sizeof(s_saved_film_manager_director_state));
for (int32 user_index = 0; user_index < 4; user_index++)
{
if (!player_mapping_output_user_is_active(user_index))
{
continue;
}
c_director* director = director_get(user_index);
if (director->get_type() != _director_mode_saved_film)
{
continue;
}
s_saved_film_manager_user_director_state* state = &director_state_out->user_director_states[user_index];
c_saved_film_director* saved_film_director = (c_saved_film_director*)director;
user_director_state->observer_result = *observer_get_camera(user_index);
user_director_state->camera_mode = camera->get_type();
user_director_state->desired_camera_mode = saved_film_director->m_desired_camera_mode;
user_director_state->valid = true;
user_director_state->camera_target_player_absolute_index = saved_film_director->get_watched_player();
}
}
void saved_film_manager_get_hud_interface_state(s_saved_film_hud_interface_state* hud_state)
{
csmemset(hud_state, 0, sizeof(s_saved_film_hud_interface_state));
saved_film_history_get_hud_interface_state(hud_state);
saved_film_snippet_get_hud_interface_state(hud_state);
}
// $TODO: check my work
bool saved_film_manager_get_last_recorded_film(char* filepath, int32 maximum_characters, s_saved_game_item_metadata* out_optional_metadata)
{
s_file_reference directory{};
file_reference_create_from_path(&directory, autosave_queue_get_directory_path(), true);
s_find_file_data file_data{};
find_files_start_with_search_spec(&file_data, 0, &directory, "*.film");
s_file_reference current_file{};
s_file_last_modification_date mod_date{};
bool v7 = false;
bool v8 = true;
s_file_reference newest_file{};
while (find_files_next(&file_data, &current_file, &mod_date))
{
if (!v8 && file_compare_last_modification_dates(&mod_date, &newest_file_mod_date) <= 0);
{
continue;
}
newest_file = current_file;
v7 = true;
v8 = false;
}
find_files_end(&file_data);
if (!v7)
{
return false;
}
file_reference_get_fullpath(&newest_file, filepath, maximum_characters);
if (out_optional_metadata)
{
return saved_game_read_metadata_from_file(&newest_file, out_optional_metadata);
}
return true;
}
int32 saved_film_manager_get_length_in_ticks()
{
ASSERT(saved_film_manager_globals.initialized);
return saved_film_manager_globals.saved_film.get_length_in_ticks();
}
real32 saved_film_manager_get_pending_playback_game_speed()
{
return saved_film_set_pending_playback_game_speed;
}
real32 saved_film_manager_get_playback_game_speed()
{
return saved_film_manager_globals.playback_game_speed;
}
int32 saved_film_manager_get_position()
{
ASSERT(saved_film_manager_globals.initialized);
return saved_film_manager_globals.saved_film.get_position();
}
const char* saved_film_manager_get_recording_directory()
{
return autosave_queue_get_directory_path();
}
bool saved_film_manager_get_reproduction_enabled()
{
return saved_film_manager_globals.reproduction_mode_enabled;
}
uns32 saved_film_manager_get_simulation_camera_update_mask()
{
return saved_film_manager_globals.valid_camera_mask;
}
bool saved_film_manager_get_simulation_camera_updates(int32 camera_index, s_simulation_camera_update* simulation_camera_update_out)
{
ASSERT(VALID_INDEX(camera_index, saved_film_manager_globals.camera_updates.get_count()));
if (!TEST_BIT(saved_film_manager_globals.valid_camera_mask, camera_index))
{
return false;
}
*simulation_camera_update_out = saved_film_manager_globals.camera_updates[camera_index];
return true;
}
int32 saved_film_manager_get_snippet_start_tick()
{
if (!game_in_progress() || !game_is_playback())
{
return NONE;
}
int32 snippet_start_tick = saved_film_manager_globals.snippet_start_tick;
if (!saved_film_manager_snippets_available())
{
return snippet_start_tick;
}
if (saved_film_snippet_get_current_state() == _saved_film_snippet_state_none)
{
return snippet_start_tick;
}
int32 current_snippet_start_tick = NONE;
if (!saved_film_snippet_get_current_start_tick(&current_snippet_start_tick))
{
return snippet_start_tick;
}
return current_snippet_start_tick;
}
e_saved_film_snippet_state saved_film_manager_get_snippet_state()
{
if (!saved_film_manager_snippets_available())
{
return _saved_film_snippet_state_none;
}
return saved_film_snippet_get_current_state();
}
int32 saved_film_manager_get_ticks_remaining()
{
ASSERT(saved_film_manager_globals.initialized);
if (saved_film_manager_globals.saved_film.m_film_state)
{
return NULL;
}
return saved_film_manager_globals.saved_film.get_ticks_remaining();
}
void saved_film_manager_handle_camera_update(uns32 valid_camera_mask, const s_simulation_camera_update* camera_updates)
{
saved_film_manager_globals.valid_camera_mask = valid_camera_mask;
saved_film_manager_globals.camera_updates.clear();
if (TEST_BIT(valid_camera_mask, 0))
{
saved_film_manager_globals.camera_updates[0] = *camera_updates;
}
}
bool saved_film_manager_handle_revert(int32 saved_film_file_position, int32 film_tick)
{
ASSERT(saved_film_manager_globals.initialized);
g_universal_saved_film_tick.set(film_tick);
if (!game_is_authoritative_playback())
{
return true;
}
return saved_film_manager_globals.saved_film.handle_revert(saved_film_file_position, film_tick)
}
bool saved_film_manager_has_pending_global_state_change()
{
if (!game_in_progress() || !game_is_playback())
{
return false;
}
if (saved_film_manager_globals.pending_gamestate_load)
{
return true;
}
return saved_film_manager_revert_desired();
}
void saved_film_manager_initialize_for_new_map()
{
if (game_is_ui_shell() || main_game_reset_in_progress())
{
return;
}
saved_film_manager_clear_playback_state();
if (!game_is_playback())
{
return;
}
if (game_is_authoritative_playback())
{
int32 local_signature_size = 0;
const byte* local_signature_bytes = NULL;
if (!cache_file_get_content_signature(&local_signature_size, &local_signature_bytes))
{
event(_event_warning, "networking:saved_film:manager: signature MISMATCH, failed to get local signature");
saved_film_manager_abort_playback(_saved_film_playback_map_signature_failed);
}
else
{
int32 map_signature_size = saved_film_manager_globals.saved_film.m_film_header.film_header.build_compatibility.map_signature_size;
const byte* map_signature_bytes = saved_film_manager_globals.saved_film.m_film_header.film_header.build_compatibility.map_signature_bytes;
bool use_full_language_dependent_signature = !game_options_valid() || !game_is_campaign();
if (!cache_file_content_signatures_match(local_signature_size, local_signature_bytes, map_signature_size, map_signature_bytes, use_full_language_dependent_signature))
{
event(_event_error, "networking:saved_film:manager: signature MISMATCH (%s), film signature (%s) does not match our local one (%s)",
use_full_language_dependent_signature ? "full language dependent" : "language neutral only",
cache_file_signature_summary(map_signature_size, map_signature_bytes).get_string(),
cache_file_signature_summary(local_signature_size, local_signature_bytes).get_string());
saved_film_manager_abort_playback(_saved_film_playback_map_signature_failed);
}
else
{
event(_event_message, "networking:saved_film:manager: signature match (%s), film signature matches our local one (%s)",
use_full_language_dependent_signature ? "full language dependent" : "language neutral only",
cache_file_signature_summary(local_signature_size, local_signature_bytes).get_string());
}
}
}
saved_film_manager_globals.snippet_start_tick = game_options_get()->playback_start_tick;
saved_film_history_initialize_for_saved_film_playback();
saved_film_snippet_initialize_for_saved_film_playback();
saved_film_manager_globals.screensaver_enabled = false;
}
void saved_film_manager_initialize()
{
ASSERT(!saved_film_manager_globals.initialized);
saved_film_manager_globals.saved_film.initialize(&g_physical_memory_allocation);
saved_film_manager_globals.saved_film_name.clear();
saved_film_manager_globals.show_timestamp = true;
saved_film_manager_globals.disable_version_checking = false;
saved_film_manager_globals.automatic_debug_saving_enabled = false;
saved_film_manager_globals.pending_gamestate_load = false;
saved_film_manager_globals.gamestate_file_position = NONE;
saved_film_manager_globals.film_close_in_progress = false;
saved_film_manager_globals.ui_screen_active = false;
saved_film_manager_clear_playback_state();
saved_film_manager_create_film_directory();
saved_film_manager_globals.initialized = true;
c_saved_film_scratch_memory::get()->initialize();
saved_film_history_initialize();
saved_film_snippet_initialize();
}
bool saved_film_manager_is_reading()
{
ASSERT(saved_film_manager_globals.initialized);
return saved_film_manager_globals.saved_film.m_film_state == _saved_film_open_for_read;
}
//void saved_film_manager_load_pending_gamestate()
//bool saved_film_manager_load_pending_gamestate_to_compressor()
void saved_film_manager_memory_dispose()
{
saved_film_snippet_memory_dispose();
saved_film_history_memory_dispose();
c_saved_film_scratch_memory::get()->memory_dispose();
}
void saved_film_manager_memory_initialize(e_map_memory_configuration memory_configuration)
{
if (!map_memory_configuration_is_saved_film(memory_configuration))
{
return;
}
c_saved_film_scratch_memory::get()->memory_initialize();
saved_film_history_memory_initialize();
saved_film_snippet_memory_initialize();
}
void saved_film_manager_notify_gamestate_decompression_after_load_procs()
{
if (!game_in_progress() || !game_is_playback())
{
return;
}
if (game_is_campaign())
{
game_state_save();
}
determinism_debug_manager_set_file_position(0, 0);
{
LOCAL_TAG_RESOURCE_SCOPE_LOCK;
players_finish_creation();
}
saved_film_manager_globals.authored_cam_set_for_user.clear();
for (int32 user_index = 0; user_index < 4; user_index++)
{
if (!player_mapping_output_user_is_active(user_index))
{
continue;
}
c_director* director = director_get(user_index);
if (director->get_type() != _director_mode_saved_film)
{
continue;
}
c_saved_film_director* saved_film_director = (c_saved_film_director *)director;
saved_film_director->notify_revert();
}
}
void saved_film_manager_notify_gamestate_decompression_before_load_procs()
{
if (!game_in_progress() || !game_is_playback() || !game_is_multiplayer())
{
return;
}
if (saved_film_manager_is_reading())
{
ASSERT(saved_film_manager_globals.pending_gamestate_load);
}
determinism_debug_manager_reset_for_core_load();
saved_film_history_notify_initial_gamestate_loaded();
}
void saved_film_manager_notify_gamestate_load(e_saved_film_game_state_load_source game_state_load_source)
{
if (saved_film_manager_globals.saved_film.m_film_state != _saved_film_open_for_write)
{
return;
}
if (game_state_load_source == _saved_film_game_state_load_source_core && saved_film_manager_globals.saved_film.get_current_tick() > 0)
{
saved_film_manager_close();
saved_film_manager_open_film_for_writing(saved_film_manager_globals.saved_film_name.get_string(), game_options_get());
}
determinism_debug_manager_reset_for_core_load();
if (saved_film_manager_globals.saved_film.get_current_tick())
{
event(_event_error, "networking:saved_film:manager: you are loading a core while a film is in the middle of being recorded, things are going to be bad!");
}
else
{
saved_film_manager_globals.saved_film.write_gamestate();
}
}
void saved_film_manager_notify_out_of_sync()
{
}
void saved_film_manager_notify_remote_end_film()
{
if (!game_in_progress() || !game_is_playback())
{
event(_event_error, "networking:saved_film:manager: remote machine tried to end film but we are not running playback");
return;
}
if (game_is_authoritative_playback())
{
event(_event_error, "networking:saved_film:manager: remote machine tried to end film but we are the authority!");
return;
}
event(_event_message, "networking:saved_film:manager: remote machine ended film");
saved_film_manager_end_film_internal();
}
void saved_film_manager_notify_reverted_gamestate_loaded(int32 history_record_index, int32 update_number, void* gamestate, int32 gamestate_size)
{
event(_event_message, "networking:saved_film:manager: notified reverted [history %d update %d]",
history_record_index,
update_number);
if (saved_film_manager_globals.seek_film_tick != NONE && saved_film_snippet_get_current_state())
{
saved_film_snippet_finished_revert_for_seek(update_number, gamestate, gamestate_size);
}
simulation_notify_saved_film_revert(history_record_index, update_number);
}
void saved_film_manager_notify_snippet_preview_complete()
{
event(_event_message, "networking:saved_film:manager: notified film snippet complete");
ASSERT(game_is_authoritative_playback());
saved_film_manager_globals.playback_locked = true;
saved_film_manager_globals.playback_game_speed = 0.0f;
}
bool saved_film_manager_open_film_for_reading(e_controller_index controller_index, const char* film_name)
{
e_saved_film_state film_state = saved_film_manager_globals.saved_film.m_film_state;
if (film_state != k_saved_film_state_none)
{
event(_event_error, "networking:saved_film:manager: can't open film for reading, current state is %d",
film_state);
return false;
}
c_static_string<128> saved_film_file_path{};
ASSERT(saved_film_manager_globals.initialized);
event(_event_message, "networking:saved_film:manager: opening film %s for reading",
film_name);
if (csstrstr(film_name, "\\"))
{
saved_film_file_path.set(film_name);
}
else
{
saved_film_manager_build_file_path_from_name(film_name, _file_path_for_reading, &saved_film_file_path);
}
// $TODO: clean this up
c_debug_output_path debug_output_path{};
bool valid = saved_film_manager_globals.saved_film.open_for_read(saved_film_file_path.get_string(), controller_index, saved_film_manager_globals.disable_version_checking);
if (valid || !csstrstr(film_name, "\\")
&& (saved_film_file_path.print("%s%s%s.film", debug_output_path.get_root(), "saved_films\\", film_name),
valid = saved_film_manager_globals.saved_film.open_for_read(saved_film_file_path.get_string(), controller_index, saved_film_manager_globals.disable_version_checking)))
{
saved_film_manager_globals.saved_film_name.set(film_name);
saved_film_manager_globals.pending_gamestate_load = saved_film_manager_globals.saved_film.m_film_header.film_header.contains_gamestate;
saved_film_manager_globals.gamestate_file_position = saved_film_manager_globals.saved_film.get_position();
}
else
{
saved_film_manager_globals.saved_film_name.clear();
saved_film_manager_globals.pending_gamestate_load = false;
saved_film_manager_globals.gamestate_file_position = NONE;
}
return valid;
}
bool saved_film_manager_open_film_for_writing(const char* film_name, const game_options* options)
{
e_saved_film_state film_state = saved_film_manager_globals.saved_film.m_film_state;
if (film_state != k_saved_film_state_none)
{
event(_event_error, "networking:saved_film:manager: can't open film for writing, current state is %d",
film_state);
return false;
}
c_static_string<128> saved_film_path{};
ASSERT(saved_film_manager_globals.initialized);
event(_event_message, "networking:saved_film:manager: opening film %s for writing",
film_name);
saved_film_manager_build_file_path_from_name(film_name, _file_path_for_creation, &saved_film_file_path);
bool valid = saved_film_manager_globals.saved_film.open_for_write(saved_film_path.get_string(), options, controller_get_first_non_guest_signed_in_controller());
if (valid)
{
saved_film_manager_globals.saved_film_name.set(film_name);
}
else
{
saved_film_manager_globals.saved_film_name.clear();
}
return valid;
}
void saved_film_manager_perform_global_state_change()
{
ASSERT(saved_film_manager_has_pending_global_state_change());
event(_event_message, "networking:saved_film:manager: performing global state change");
s_saved_film_manager_director_state director_state{};
saved_film_manager_get_director_state(&director_state);
if (saved_film_manager_globals.pending_gamestate_load)
{
saved_film_manager_load_pending_gamestate();
saved_film_manager_globals.pending_gamestate_load = false;
}
bool director_state_set = false;
if (saved_film_manager_revert_desired())
{
saved_film_manager_perform_revert(&director_state_set);
}
if (!director_state_set)
{
saved_film_manager_set_director_state(&director_state);
}
}
void saved_film_manager_perform_revert(bool* set_director_state_out)
{
ASSERT(saved_film_manager_revert_desired());
int32 current_tick_estimate = saved_film_manager_get_current_tick_estimate();
if (saved_film_manager_globals.seek_film_tick == NONE || saved_film_manager_globals.seek_film_tick > current_tick_estimate)
{
if (saved_film_manager_globals.desired_revert_type)
{
event(_event_message, "networking:saved_film:manager: reverting by type %d",
saved_film_manager_globals.desired_revert_type);
saved_film_history_revert_by_type(saved_film_manager_globals.desired_revert_type);
}
else if (saved_film_manager_globals.desired_revert_index != NONE)
{
event(_event_message, "networking:saved_film:manager: reverting by index %d",
saved_film_manager_globals.desired_revert_index);
if (saved_film_manager_globals.desired_revert_index == 0)
{
saved_film_manager_globals.authored_cam_set_for_user.clear();
}
if (!saved_film_history_revert_by_index(saved_film_manager_globals.desired_revert_index))
{
saved_film_manager_abort_playback(_saved_film_playback_history_failed_to_revert_by_index);
}
}
}
else
{
event(_event_message, "networking:saved_film:manager: reverting by film tick %d",
saved_film_manager_globals.seek_film_tick);
saved_film_history_revert_by_film_tick(saved_film_manager_globals.seek_film_tick);
if (saved_film_manager_snippets_available())
{
saved_film_snippets_notify_reverted_for_seek(set_director_state_out);
}
saved_film_manager_update_seeking(current_tick_estimate);
}
for (int32 user_index = 0; user_index < 4; user_index++)
{
if (!player_mapping_output_user_is_active(user_index))
{
continue;
}
c_director* director = director_get(user_index);
if (director->get_type() != _director_mode_saved_film)
{
continue;
}
c_saved_film_director* saved_film_director = (c_saved_film_director *)director;
saved_film_director->notify_revert();
}
saved_film_manager_globals.desired_revert_type = _saved_film_revert_none;
saved_film_manager_globals.desired_revert_index = NONE;
}
void saved_film_manager_play_hs(int16 controller_index, const char* film_name)
{
saved_film_manager_play((e_controller_index)controller_index, film_name);
}
void saved_film_manager_play_last_hs()
{
char filename[64]{};
if (saved_film_manager_get_last_recorded_film(filename, sizeof(filename), NULL))
{
saved_film_manager_play(k_no_controller, filename);
}
}
void saved_film_manager_play(e_controller_index controller_index, const char* film_name)
{
ASSERT(saved_film_manager_globals.initialized);
network_life_cycle_end();
simulation_end(_simulation_abort_reason_preparing_to_play_film);
saved_film_manager_set_playback_game_speed(1.0f);
saved_film_manager_close();
if (!saved_film_manager_open_film_for_reading(controller_index, film_name))
{
event(_event_error, "networking:saved_film:manager: unable to read header from saved film '%s', film is invalid",
film_name);
return;
}
event(_event_message, "networking:saved_film:manager: playing saved film '%s'",
film_name);
saved_film_manager_globals.saved_film_name.set(film_name);
game_options options{};
options = *saved_film_manager_globals.saved_film.get_game_options();
options.game_playback = _game_playback_film;
options.playback_length_in_ticks = saved_film_manager_get_length_in_ticks();
int32 snippet_start_tick = NONE;
if (saved_film_manager_globals.saved_film.m_film_header.film_header.is_snippet)
{
snippet_start_tick = saved_film_manager_globals.saved_film.m_film_header.film_header.snippet_start_tick;
}
options.playback_start_tick = snippet_start_tick;
options.record_saved_film = false;
game_options_validate(&options);
main_game_change(&options);
}
bool saved_film_manager_playback_aborted()
{
return saved_film_manager_globals.playback_aborted;
}
void saved_film_manager_playback_lock_set(real32 playback_game_speed, bool locked)
{
ASSERT(game_is_authoritative_playback());
saved_film_manager_globals.playback_locked = locked;
saved_film_manager_globals.playback_game_speed = MAX(0.0f, MIN(30.0f, playback_game_speed));
}
void saved_film_manager_preview_snippet_start()
{
e_saved_film_snippet_state snippet_state = saved_film_manager_get_snippet_state();
if (snippet_state == _saved_film_snippet_state_none)
{
event(_event_warning, "networking:saved_film:manager: snippts not available (can't start preview)");
return;
}
if (snippet_state != _saved_film_snippet_state_recorded_and_ready)
{
event(_event_warning, "networking:saved_film:manager: can't preview snippet [current in state %d]",
snippet_state);
return;
}
if (!saved_film_snippet_preview_start())
{
saved_film_manager_abort_playback(_saved_film_playback_abort_snippet_failed_to_start_preview);
return;
}
event(_event_message, "networking:saved_film:manager: starting snippet preview");
saved_film_manager_playback_lock_set(0.0f, true);
}
void saved_film_manager_preview_snippet_stop()
{
e_saved_film_snippet_state snippet_state = saved_film_manager_get_snippet_state();
if (snippet_state == _saved_film_snippet_state_none)
{
event(_event_warning, "networking:saved_film:manager: snippts not available (can't stop preview)");
return;
}
if (snippet_state != _saved_film_snippet_state_previewing)
{
event(_event_warning, "networking:saved_film:manager: can't stop snippet preview [current in state %d]",
snippet_state);
return;
}
if (saved_film_manager_get_current_tick_estimate() <= saved_film_manager_get_snippet_start_tick())
{
event(_event_warning, "networking:saved_film:manager: can't stop snippet preview [snippet has not actually started previewing]");
return;
}
if (!saved_film_snippet_preview_stop())
{
saved_film_manager_abort_playback(_saved_film_playback_abort_snippet_failed_to_stop_preview);
return;
}
event(_event_message, "networking:saved_film:manager: stopping snippet preview");
saved_film_manager_playback_lock_set(0.0f, true);
}
bool saved_film_manager_read_simulation_update(const s_saved_film_update* update, struct simulation_update* simulation_update_out)
{
ASSERT(saved_film_manager_globals.initialized);
return saved_film_manager_globals.saved_film.read_simulation_update(update, simulation_update_out);
}
bool saved_film_manager_read_update(s_saved_film_update* update_out)
{
ASSERT(saved_film_manager_globals.initialized);
return saved_film_manager_globals.saved_film.read_update(update_out);
}
void saved_film_manager_render_debug()
{
}
void saved_film_manager_replay_film()
{
if (!game_in_progress() || !game_is_playback())
{
event(_event_error, "networking:saved_film:manager: not running playback, can't replay film");
return;
}
if (!game_is_authoritative_playback())
{
event(_event_error, "networking:saved_film:manager: not the authority, can't request replay film!");
return;
}
if (!saved_film_history_ready_for_revert_or_reset())
{
event(_event_warning, "networking:saved_film:manager: history not ready for revert or reset, can't replay film");
return;
}
event(_event_message, "networking:saved_film:manager: replaying film by reverting to index 0");
saved_film_manager_globals.desired_revert_index = 0;
saved_film_manager_playback_lock_set(1.0f, false);
}
void saved_film_manager_request_end_film()
{
if (!game_in_progress() || !game_is_playback())
{
event(_event_error, "networking:saved_film:manager: tried to end film but we are not running playback");
return;
}
if (!game_is_authoritative_playback())
{
event(_event_error, "networking:saved_film:manager: tried to end film but we are running remote playback and cannot end authoritatively");
return;
}
event(_event_message, "networking:saved_film:manager: local authority ended film");
saved_film_manager_end_film_internal();
}
void saved_film_manager_request_revert_by_index(int32 revert_index)
{
event(_event_message, "networking:saved_film:manager: requesting revert by index %d", revert_index);
ASSERT(!saved_film_manager_globals.playback_locked);
ASSERT(game_is_playback());
ASSERT(!game_is_authoritative_playback());
saved_film_manager_globals.desired_revert_index = revert_index;
}
void saved_film_manager_request_revert(e_saved_film_revert_type desired_revert_type)
{
ASSERT(desired_revert_type != _saved_film_revert_none);
if (!saved_film_manager_can_set_playback_control())
{
event(_event_warning, "networking:saved_film:manager: can't set playback control, can't request revert");
return;
}
if (!saved_film_history_can_revert_by_type(desired_revert_type))
{
event(_event_warning, "networking:saved_film:manager: revert type %d unavailable",
desired_revert_type);
return;
}
event(_event_message, "networking:saved_film:manager: requesting revert by type (%d)",
desired_revert_type);
saved_film_manager_globals.desired_revert_type = desired_revert_type;
}
bool saved_film_manager_revert_desired()
{
if (saved_film_manager_globals.film_ended || !simulation_in_progress())
{
return false;
}
if ((saved_film_manager_globals.seek_film_tick == NONE || saved_film_manager_globals.seek_film_tick > saved_film_manager_get_current_tick_estimate())
&& saved_film_manager_globals.desired_revert_type == _saved_film_revert_none)
{
return saved_film_manager_globals.desired_revert_index != NONE;
}
return true;
}
bool saved_film_manager_rewind_and_seek_to_film_tick(int32 film_tick, bool seek_and_stop)
{
ASSERT(film_tick >= 0);
if (!saved_film_manager_snippets_available())
{
event(_event_warning, "networking:saved_film:manager: film snippets unavailable, can't queue seek");
return false;
}
if (saved_film_manager_has_pending_global_state_change())
{
event(_event_warning, "networking:saved_film:manager: already have global state change pending, can't queue seek");
return false;
}
int32 current_tick_estimate = saved_film_manager_get_current_tick_estimate()
if (film_tick > current_tick_estimate)
{
event(_event_warning, "networking:saved_film:manager: attempting to rewind and seek to a tick in the future? [%d > %d]",
film_tick,
current_tick_estimate);
return false;
}
event(_event_message, "networking:saved_film:manager: seeking to film tick %d (seek and stop %s)",
film_tick,
seek_and_stop ? "TRUE" : "FALSE");
saved_film_manager_playback_lock_set(1.0f, true);
saved_film_manager_globals.seek_film_tick = film_tick;
saved_film_manager_globals.seek_and_stop = seek_and_stop;
return true;
}
void saved_film_manager_seek_to_film_tick_hs(int32 film_tick)
{
if (film_tick < 0)
{
event(_event_warning, "networking:saved_film:manager: invalid film tick %d, can't seek",
film_tick);
return;
}
if (saved_film_manager_has_pending_global_state_change())
{
event(_event_warning, "networking:saved_film:manager: already have global state change queued, can't seek");
return;
}
saved_film_pending_seek_film_tick = film_tick;
}
bool saved_film_manager_seeking(int32* seek_time_available_out)
{
ASSERT(seek_time_available_out);
if (!game_in_progress()
|| !game_is_playback()
|| saved_film_manager_globals.saved_film.m_film_state
|| saved_film_manager_globals.seek_film_tick == NONE
|| saved_film_manager_globals.seek_film_tick < saved_film_manager_get_current_tick_estimate())
{
return false;
}
*seek_time_available_out = saved_film_manager_globals.seek_film_tick - saved_film_manager_get_current_tick_estimate();
return true;
}
// $TODO: check my work
void saved_film_manager_set_director_state(const s_saved_film_manager_director_state* director_state)
{
LOCAL_TAG_RESOURCE_SCOPE_LOCK;
for (int32 user_index = 0; user_index < 4; user_index++)
{
if (!player_mapping_output_user_is_active(user_index))
{
continue;
}
c_director* director = director_get(user_index);
if (director->get_type() != _director_mode_saved_film)
{
continue;
}
s_saved_film_manager_user_director_state* user_director_state = &director_state->user_director_states[user_index];
if (!user_director_state->valid)
{
continue;
}
c_saved_film_director* saved_film_director = (c_saved_film_director *)director;
observer_obsolete_position(user_index);
saved_film_director->force_set_camera_mode(user_director_state->camera_mode, 0.0f);
saved_film_director->m_desired_camera_mode = user_director_state->desired_camera_mode;
saved_film_director->set_watched_player(NONE);
c_camera* camera = saved_film_director->get_camera();
camera->set_target(NONE);
if (user_director_state->camera_target_player_absolute_index != NONE)
{
player_datum* player = DATUM_GET(player_data, player_datum, user_director_state->camera_target_player_absolute_index);
if (player && !TEST_BIT(m_iterator.m_datum->flags, _player_left_game_bit))
{
int32 player_index = player_index_from_absolute_player_index(user_director_state->camera_target_player_absolute_index);
saved_film_director->set_watched_player(player_index);
if (!saved_film_director->in_free_camera_mode())
{
int32 unit_index = player->unit_index;
if (unit_index == NONE)
{
unit_index = player->dead_unit_index;
}
camera->set_target(unit_index)
}
}
}
camera->set_position(&user_director_state->observer_result.position);
camera->set_forward(&user_director_state->observer_result.forward);
}
}
bool saved_film_manager_set_pending_playback_game_speed(real32 game_speed)
{
saved_film_set_pending_playback_game_speed = game_speed;
return true;
}
bool saved_film_manager_set_playback_game_speed(real32 game_speed)
{
if (!saved_film_manager_can_set_playback_control())
{
event(_event_warning, "networking:saved_film:manager: can't set playback control, can't set game speed");
return false;
}
saved_film_manager_globals.playback_game_speed = MAX(0.0f, MIN(30.0f, game_speed));
}
bool saved_film_manager_set_position(int32 position)
{
ASSERT(saved_film_manager_globals.initialized);
return saved_film_manager_globals.saved_film.set_position(position);
}
void saved_film_manager_set_reproduction_enabled(bool reproduction_enabled)
{
if (saved_film_manager_globals.reproduction_mode_enabled == reproduction_enabled)
{
return;
}
saved_film_manager_globals.reproduction_mode_enabled = reproduction_enabled;
event(_event_message, "networking:saved_film:manager: set reproduction mode (%s)",
reproduction_enabled ? "enabled" : "disabled");
if (game_in_progress() && game_is_playback())
{
event(_event_message, "networking:saved_film:manager: reproduction mode changed, forcing recreation of players' output users");
for (int32 user_index = 0; user_index < 4; user_index++)
{
player_mapping_attach_output_user(user_index, NONE);
}
players_finish_creation();
}
}
void saved_film_manager_should_record_film_default_set(bool b)
{
saved_film_manager_should_record_film_default = b;
}
bool saved_film_manager_should_record_film(const game_options* options)
{
ASSERT(options);
if (options->game_mode == _game_mode_campaign)
{
s_level_datum level_data{};
if (levels_try_and_get_campaign_map(options->map_id, &level_data))
{
return level_data.flags.test(_level_allows_saved_films);
}
}
else if (options->game_mode == _game_mode_multiplayer)
{
return true;
}
return saved_film_manager_should_record_film_default;
}
void saved_film_manager_show_timestamp(bool show_timestamp)
{
saved_film_manager_globals.show_timestamp = show_timestamp;
}
bool saved_film_manager_snippets_available()
{
return game_in_progress()
&& game_playback_get() == _game_playback_film
&& saved_film_manager_globals.saved_film.m_film_state == _saved_film_open_for_read
&& !saved_film_manager_globals.film_ended;
}
//void saved_film_manager_start_recording_snippet()
//void saved_film_manager_stop_recording_snippet()
bool saved_film_manager_timestamp_enabled_internal()
{
return saved_film_manager_globals.show_timestamp;
}
void saved_film_manager_toggle_automatic_debug_saving(bool enable)
{
saved_film_manager_globals.automatic_debug_saving_enabled = enable;
}
void saved_film_manager_update_after_simulation_update(const struct simulation_update* update, const s_simulation_update_metadata* metadata)
{
saved_film_history_update_after_simulation_update(update, metadata);
if (!game_in_progress() || !game_is_playback())
{
return;
}
g_universal_saved_film_tick.set(metadata->saved_film_tick);
saved_film_manager_globals.valid_camera_mask = update->valid_camera_update_mask;
saved_film_manager_globals.camera_updates.clear();
if (TEST_BIT(update->valid_camera_update_mask, 0))
{
saved_film_manager_globals.camera_updates = update->camera_updates[0];
}
if (game_is_authoritative_playback())
{
saved_film_manager_update_seeking(metadata->saved_film_tick);
}
if (!saved_film_manager_snippets_available())
{
return;
}
if (!saved_film_snippet_update_after_simulation_update(update, metadata))
{
saved_film_manager_abort_playback(_saved_film_playback_abort_snippet_failed_to_update_after_simulation);
}
}
void saved_film_manager_update_before_simulation_update()
{
saved_film_history_update_before_simulation_update(saved_film_manager_get_snippet_state() != _saved_film_snippet_state_none);
}
void saved_film_manager_update_seeking(int32 current_film_tick)
{
if (!game_in_progress()
|| !game_is_playback()
|| saved_film_manager_globals.saved_film.m_film_state != _saved_film_open_for_read
|| saved_film_manager_globals.seek_film_tick == NONE)
{
return;
}
if (current_film_tick < saved_film_manager_globals.seek_film_tick)
{
e_game_playback_type game_playback = game_playback_get();
if (game_playback == _game_playback_film)
{
saved_film_manager_playback_lock_set(3.0f, game_playback);
return;
}
saved_film_manager_playback_lock_set(2.0f, true);
return;
}
if (current_film_tick != saved_film_manager_globals.seek_film_tick
&& current_film_tick != saved_film_manager_globals.seek_film_tick - 1)
{
return;
}
saved_film_manager_playback_lock_set(saved_film_manager_globals.seek_and_stop ? 0.0f : 1.0f, false);
saved_film_manager_globals.seek_film_tick = NONE;
saved_film_manager_globals.seek_and_stop = false;
}
void saved_film_manager_update_snippet_authored_cameras()
{
if (saved_film_manager_globals.snippet_start_tick == NONE)
{
return;
}
for (int32 user_index = 0; user_index < 4; user_index++)
{
if (!player_mapping_output_user_is_active(user_index))
{
continue;
}
c_director* director = director_get(user_index);
if (director->get_type() != _director_mode_saved_film)
{
continue;
}
if (saved_film_manager_globals.authored_cam_set_for_user.test(user_index))
{
continue;
}
event(_event_message, "networking:saved_film:manager: setting user %d camera to authored for snippet playback",
user_index);
director->set_camera_mode(_camera_mode_authored, 0.0f);
saved_film_manager_globals.authored_cam_set_for_user.set(user_index, true)
}
}
//void saved_film_manager_update_ui_screens()
//void saved_film_manager_update()
//int32 saved_film_manager_upload_start(int32 maximum_file_count, s_file_reference* out_file_list)
bool saved_film_manager_write_simulation_update(const struct simulation_update* update)
{
ASSERT(saved_film_manager_globals.initialized);
if (saved_film_manager_globals.saved_film.m_film_state != _saved_film_open_for_write)
{
event(_event_error, "networking:saved_film:manager: film not open for write, can't write simulation update");
return false;
}
if (!saved_film_manager_globals.saved_film.write_simulation_update(update))
{
event(_event_warning, "networking:saved_film:manager: failed to write simulation update %d to film", update->update_number);
return false;
}
g_universal_saved_film_tick.set(saved_film_manager_get_current_tick());
}
#pragma once
enum e_saved_film_category
{
_saved_film_category_none = 0,
_saved_film_category_recent_films,
_saved_film_category_film_clips,
_saved_film_category_campaign,
_saved_film_category_multiplayer,
_saved_film_category_editor,
_saved_film_category_invalid,
k_saved_film_category_count,
k_saved_film_category_bits = 4,
};
enum e_saved_film_file_path_creation_purpose
{
_file_path_for_creation = 0,
_file_path_for_creation_final = 1,
_file_path_for_reading = 2,
};
enum e_saved_film_game_state_load_source
{
_saved_film_game_state_load_source_core = 0,
_saved_film_game_state_load_source_storage,
k_saved_film_game_state_load_source
};
enum e_saved_film_playback_abort_reason
{
_saved_film_playback_abort_reason_none = 0,
_saved_film_playback_abort_simulation_failed_to_read,
_saved_film_playback_abort_snippet_failed_to_update,
_saved_film_playback_abort_snippet_failed_to_update_after_simulation,
_saved_film_playback_abort_snippet_failed_to_start_recording,
_saved_film_playback_abort_snippet_failed_to_stop_recording,
_saved_film_playback_abort_snippet_failed_to_start_preview,
_saved_film_playback_abort_snippet_failed_to_stop_preview,
_saved_film_playback_abort_snippet_failed_to_delete,
_saved_film_playback_abort_snippet_failed_to_commit,
_saved_film_playback_history_failed_to_write,
_saved_film_playback_history_failed_to_write_record,
_saved_film_playback_history_failed_to_revert,
_saved_film_playback_history_failed_to_revert_by_index,
_saved_film_playback_failed_to_load_gamestate_for_party,
_saved_film_playback_map_signature_failed,
k_saved_film_playback_abort_reason_count
};
enum e_saved_film_revert_type
{
_saved_film_revert_none = 0,
_saved_film_revert_backwards = 1,
_saved_film_revert_forwards = 2,
};
enum e_saved_film_snippet_state
{
_saved_film_snippet_state_none = 0,
_saved_film_snippet_state_recording_waiting_for_seek,
_saved_film_snippet_state_recording_waiting_for_start,
_saved_film_snippet_state_recording,
_saved_film_snippet_state_recorded_and_ready,
_saved_film_snippet_state_previewing_waiting_for_seek,
_saved_film_snippet_state_previewing,
_saved_film_snippet_state_commiting_invoking_title_keyboard,
_saved_film_snippet_state_commiting_waiting_title_keyboard,
_saved_film_snippet_state_commiting_invoking_description_keyboard,
_saved_film_snippet_state_commiting_waiting_description_keyboard,
_saved_film_snippet_state_commiting_initiate_creation,
_saved_film_snippet_state_commiting_wait_for_creation,
_saved_film_snippet_state_commiting_initiate_metadata_update,
_saved_film_snippet_state_commiting_wait_for_metadata_update,
_saved_film_snippet_state_resetting,
k_number_of_saved_film_snippet_states
};
enum e_saved_film_state
{
_saved_film_open_for_read = 0,
_saved_film_open_for_write,
k_saved_film_state_count,
k_saved_film_state_none = -1
};
enum e_saved_film_update_type
{
_saved_film_update_type_simulation_update = 0,
_saved_film_update_type_gamestate,
k_saved_film_update_type_count,
k_saved_film_update_type_none = -1,
};
struct s_saved_film_manager_user_director_state
{
s_observer_result observer_result;
e_camera_mode camera_mode;
e_camera_mode desired_camera_mode;
int32 camera_target_player_absolute_index;
bool valid;
};
struct s_saved_film_manager_director_state
{
c_static_array<s_saved_film_manager_user_director_state,4> user_director_states;
};
struct s_saved_film_hud_interface_state
{
real32 duration_in_seconds;
real32 marker_position_in_seconds;
int32 number_of_chapters_available;
real32 buffered_theta;
real32 current_position_theta;
real32 recording_start_theta;
bool recording;
c_static_array<real32, 10> chapter_mark_theta;
};
struct s_saved_film_update
{
e_saved_film_update_type update_type;
int32 update_size;
};
class c_saved_film;
struct s_saved_film_manager_globals
{
c_static_string<64> saved_film_name;
c_saved_film saved_film;
bool pending_gamestate_load;
int32 gamestate_file_position;
bool film_close_in_progress;
bool reproduction_mode_enabled;
real32 playback_game_speed;
bool playback_locked;
bool show_timestamp;
bool film_ended;
bool film_ended_sound_disabled;
int32 film_ended_system_milliseconds;
int32 seek_film_tick;
bool seek_and_stop;
e_saved_film_revert_type desired_revert_type;
int32 desired_revert_index;
int32 snippet_start_tick;
c_static_flags_no_init<4> authored_cam_set_for_user;
uns32 valid_camera_mask;
c_static_array<s_simulation_camera_update, 1> camera_updates;
bool playback_aborted;
e_saved_film_playback_abort_reason playback_abort_reason;
bool disable_version_checking;
bool automatic_debug_saving_enabled;
bool ui_screen_active;
bool screensaver_enabled;
bool initialized;
};
static const real32 k_saved_film_ended_fade_time_seconds = 4.0f;
namespace
{
extern bool saved_film_manager_should_record_film_default;
}
extern bool saved_film_manager_should_record_film_default;
extern c_interlocked_long g_universal_saved_film_tick;
extern s_saved_film_manager_globals saved_film_manager_globals;
extern void saved_film_manager_abort_playback(e_saved_film_playback_abort_reason abort_reason);
extern bool saved_film_manager_authored_camera_locked_for_snippet();
extern bool saved_film_manager_automatic_debug_saving_enabled();
extern void saved_film_manager_build_file_path_from_name(const char* film_name, e_saved_film_file_path_creation_purpose purpose, c_static_string<128>* film_path_out);
extern bool saved_film_manager_can_revert(e_saved_film_revert_type desired_revert_type);
extern bool saved_film_manager_can_set_playback_control();
extern void saved_film_manager_clear_playback_state();
extern void saved_film_manager_close();
extern void saved_film_manager_commit_snippet_autoname(e_controller_index controller_index);
extern void saved_film_manager_commit_snippet_keyboard(e_controller_index controller_index);
extern void saved_film_manager_copy_film_to_debug_path();
extern void saved_film_manager_create_film_directory();
extern void saved_film_manager_delete_current_snippet();
extern void saved_film_manager_delete_on_level_load(bool delete_on_level_load);
extern void saved_film_manager_disable_version_checking(bool disable);
extern void saved_film_manager_dispose_from_old_map();
extern void saved_film_manager_dispose();
extern void saved_film_manager_end_film_internal();
extern bool saved_film_manager_film_is_ended(real32* out_seconds_ago);
extern bool saved_film_manager_film_valid(e_controller_index controller, const char* film_name);
extern bool saved_film_manager_get_current_film_name(c_static_string<64>* film_name_out);
extern const game_options* saved_film_manager_get_current_game_options();
extern const s_saved_game_item_metadata* saved_film_manager_get_current_metadata();
extern int32 saved_film_manager_get_current_tick_estimate();
extern int32 saved_film_manager_get_current_tick();
extern void saved_film_manager_get_director_state(s_saved_film_manager_director_state* director_state_out);
extern void saved_film_manager_get_hud_interface_state(s_saved_film_hud_interface_state* hud_state);
extern bool saved_film_manager_get_last_recorded_film(char* filepath, int32 maximum_characters, s_saved_game_item_metadata* out_optional_metadata);
extern int32 saved_film_manager_get_length_in_ticks();
extern real32 saved_film_manager_get_pending_playback_game_speed();
extern real32 saved_film_manager_get_playback_game_speed();
extern int32 saved_film_manager_get_position();
extern const char* saved_film_manager_get_recording_directory();
extern bool saved_film_manager_get_reproduction_enabled();
extern uns32 saved_film_manager_get_simulation_camera_update_mask();
extern bool saved_film_manager_get_simulation_camera_updates(int32 camera_index, s_simulation_camera_update* simulation_camera_update_out);
extern int32 saved_film_manager_get_snippet_start_tick();
extern e_saved_film_snippet_state saved_film_manager_get_snippet_state();
extern int32 saved_film_manager_get_ticks_remaining();
extern void saved_film_manager_handle_camera_update(uns32 valid_camera_mask, const s_simulation_camera_update* camera_updates);
extern bool saved_film_manager_handle_revert(int32 saved_film_file_position, int32 film_tick);
extern bool saved_film_manager_has_pending_global_state_change();
extern void saved_film_manager_initialize_for_new_map();
extern void saved_film_manager_initialize();
extern bool saved_film_manager_is_reading();
extern void saved_film_manager_load_pending_gamestate();
extern bool saved_film_manager_load_pending_gamestate_to_compressor();
extern void saved_film_manager_memory_dispose();
extern void saved_film_manager_memory_initialize(e_map_memory_configuration memory_configuration);
extern void saved_film_manager_notify_gamestate_decompression_after_load_procs();
extern void saved_film_manager_notify_gamestate_decompression_before_load_procs();
extern void saved_film_manager_notify_gamestate_load(e_saved_film_game_state_load_source game_state_load_source);
extern void saved_film_manager_notify_out_of_sync();
extern void saved_film_manager_notify_remote_end_film();
extern void saved_film_manager_notify_reverted_gamestate_loaded(int32 history_record_index, int32 update_number, void* gamestate, int32 gamestate_size);
extern void saved_film_manager_notify_snippet_preview_complete();
extern bool saved_film_manager_open_film_for_reading(e_controller_index controller_index, const char* film_name);
extern bool saved_film_manager_open_film_for_writing(const char* film_name, const game_options* options);
extern void saved_film_manager_perform_global_state_change();
extern void saved_film_manager_perform_revert(bool* set_director_state_out);
extern void saved_film_manager_play_hs(int16 controller_index, const char* film_name);
extern void saved_film_manager_play_last_hs();
extern void saved_film_manager_play(e_controller_index controller_index, const char* film_name);
extern bool saved_film_manager_playback_aborted();
extern void saved_film_manager_playback_lock_set(real32 playback_game_speed, bool locked);
extern void saved_film_manager_preview_snippet_start();
extern void saved_film_manager_preview_snippet_stop();
extern bool saved_film_manager_read_simulation_update(const s_saved_film_update* update, struct simulation_update* simulation_update_out);
extern bool saved_film_manager_read_update(s_saved_film_update* update_out);
extern void saved_film_manager_render_debug();
extern void saved_film_manager_replay_film();
extern void saved_film_manager_request_end_film();
extern void saved_film_manager_request_revert_by_index(int32 revert_index);
extern void saved_film_manager_request_revert(e_saved_film_revert_type desired_revert_type);
extern bool saved_film_manager_revert_desired();
extern bool saved_film_manager_rewind_and_seek_to_film_tick(int32 film_tick, bool seek_and_stop);
extern void saved_film_manager_seek_to_film_tick_hs(int32 film_tick);
extern bool saved_film_manager_seeking(int32* seek_time_available_out);
extern void saved_film_manager_set_director_state(const s_saved_film_manager_director_state* director_state);
extern bool saved_film_manager_set_pending_playback_game_speed(real32 game_speed);
extern bool saved_film_manager_set_playback_game_speed(real32 game_speed);
extern bool saved_film_manager_set_position(int32 position);
extern void saved_film_manager_set_reproduction_enabled(bool reproduction_enabled);
extern void saved_film_manager_should_record_film_default_set(bool b);
extern bool saved_film_manager_should_record_film(const game_options* options);
extern void saved_film_manager_show_timestamp(bool show_timestamp);
extern bool saved_film_manager_snippets_available();
extern void saved_film_manager_start_recording_snippet();
extern void saved_film_manager_stop_recording_snippet();
extern bool saved_film_manager_timestamp_enabled_internal();
extern void saved_film_manager_toggle_automatic_debug_saving(bool enable);
extern void saved_film_manager_update_after_simulation_update(const struct simulation_update* update, const s_simulation_update_metadata* metadata);
extern void saved_film_manager_update_before_simulation_update();
extern void saved_film_manager_update_seeking(int32 current_film_tick);
extern void saved_film_manager_update_snippet_authored_cameras();
extern void saved_film_manager_update_ui_screens();
extern void saved_film_manager_update();
extern int32 saved_film_manager_upload_start(int32 maximum_file_count, s_file_reference* out_file_list);
extern bool saved_film_manager_write_simulation_update(const struct simulation_update* update);
void shell_update_launch()
{
if (setup_theater_playback("saved_films\\test_film.film"))
{
launch_game_when_ready();
}
}
bool launch_game_when_ready()
{
e_session_game_start_error out_error = _session_game_start_error_none;
uns32 out_player_error_mask = 0;
if (user_interface_get_session_game_start_status(&out_error, &out_player_error_mask) != _session_game_start_status_ready)
{
return false;
}
user_interface_squad_start_countdown_timer(first_controller(), 1, 0);
return true;
}
bool setup_theater_playback(const char* theater_save_film_file_name)
{
user_interface_squad_set_ui_game_mode(_gui_game_setup_mode_theater);
s_file_reference film_file{};
file_reference_create_from_path(&film_file, theater_save_film_file_name, false);
uns32 error_code = 0;
if (!file_exists(&film_file) || !file_open(&film_file, FLAG(_permission_read_bit), &error_code) || error_code != 0)
{
return false;
}
s_blf_saved_film film_header{};
if (!file_get_size(&film_file, &file_size))
{
file_close(&film_file);
return false;
}
if (!file_read(&film_file, file_size >= sizeof(s_blf_saved_film) ? sizeof(s_blf_saved_film) : file_size, true, &film_header))
{
file_close(&film_file);
return false;
}
set_squad_session_film(theater_save_film_file_name, &film_header);
file_close(&film_file);
return true;
}
bool set_squad_session_film(const char* filename, s_blf_saved_film* film_blf)
{
int32 tick_length = 0;
int32 start_tick = 0;
bool was_valid = false;
bool result = false;
if (!film_blf->copy_to_and_validate(&film_blf->film_header.options, &tick_length, &start_tick, &was_valid) || !was_valid)
{
return false;
}
const s_saved_game_item_metadata* metadata = &film_blf->content_header.metadata;
const game_options* options = &film_blf->film_header.options;
s_saved_film_description description{};
description.category = _saved_film_category_recent_films;
description.campaign_id = _campaign_id_none;
description.map_id = metadata->map_id;
description.difficulty = _campaign_difficulty_level_easy;
description.length_seconds = metadata->length_seconds;
if (!user_interface_squad_set_film(&description))
{
return false;
}
if (options->campaign_id == _campaign_id_none)
{
bool game_variant_set = user_interface_squad_set_game_variant(&options->multiplayer_variant);
if (!game_variant_set || options->map_variant.get_map_id() == _map_id_none)
{
if (!game_variant_set || options->map_id == _map_id_none)
{
return false;
}
c_map_variant default_map_variant{};
default_map_variant.create_default(options->map_id);
return user_interface_squad_set_multiplayer_map(&default_map_variant);
}
return user_interface_squad_set_multiplayer_map(&options->map_variant);
}
// $TODO: campaign logic
return false;
}

Comments are disabled for this gist.