#include "ikarus/objects/entity.h" #include #include #include #include #include #include #include #include #include #include IkarusEntity::IkarusEntity(IkarusProject * project, int64_t id): IkarusObject{project, id} {} std::string_view IkarusEntity::get_table_name() const noexcept { return "entities"; } IkarusEntity * ikarus_entity_create( struct IkarusProject * project, char const * name, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(project, nullptr); IKARUS_FAIL_IF_NAME_INVALID(name, nullptr); IKARUS_VTRYRV_OR_FAIL( int64_t const id, nullptr, "failed to create entity: {}", IkarusErrorInfo_Database_QueryFailed, project->db->transact( [name](auto * db ) -> cppbase::Result { TRY(db->execute( "INSERT INTO `entities`(`name`) VALUES(?, ?)", name )); return cppbase::ok(db->last_insert_rowid()); } ) ); return project->get_entity(id); } void ikarus_entity_delete(IkarusEntity * entity, IkarusErrorData * error_out) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_TRYRV_OR_FAIL( , "unable to delete entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db ->execute("DELETE FROM `entities` WHERE `id` = ?", entity->id) ); entity->project->uncache(entity); } int64_t ikarus_entity_get_id(IkarusEntity const * entity, IkarusErrorData * error_out) { return ikarus::util::object_get_id(entity, error_out); } IkarusProject * ikarus_entity_get_project( IkarusEntity const * entity, IkarusErrorData * error_out ) { return ikarus::util::object_get_project(entity, error_out); } char const * ikarus_entity_get_name( IkarusEntity const * entity, IkarusErrorData * error_out ) { return ikarus::util::object_get_name(entity, error_out); } void ikarus_entity_set_name( IkarusEntity * entity, char const * name, IkarusErrorData * error_out ) { ikarus::util::object_set_name(entity, name, error_out); } bool ikarus_entity_is_linked_to_blueprint( IkarusEntity const * entity, struct IkarusBlueprint const * blueprint, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, false); IKARUS_FAIL_IF_OBJECT_MISSING(entity, false); IKARUS_FAIL_IF_NULL(blueprint, false); IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, false); IKARUS_VTRYRV_OR_FAIL( auto const ret, false, "unable to check whether entity is linked to blueprint", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT EXISTS(SELECT 1 FROM `entity_blueprint_links` WHERE " "`entity` = ? AND " "`blueprint` = ?)", entity->id, blueprint->id ) ) return ret; } void ikarus_entity_link_to_blueprint( IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_FAIL_IF_NULL(blueprint, ); IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, ); IKARUS_TRYRV_OR_FAIL( , "unable to link entity to blueprint: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "INSERT INTO `entity_blueprint_links`(`entity`, `blueprint`) " "VALUES(?, ?) ON " "CONFLICT(`entity`, `blueprint`) DO NOTHING", entity->id, blueprint->id ) ); } void ikarus_entity_unlink_from_blueprint( IkarusEntity * entity, struct IkarusBlueprint * blueprint, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_FAIL_IF_NULL(blueprint, ); IKARUS_FAIL_IF_OBJECT_MISSING(blueprint, ); IKARUS_TRYRV_OR_FAIL( , "unable to unlink entity from blueprint: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "DELETE FROM `entity_blueprint_links` WHERE `entity` = ? AND " "`blueprint` = ?", entity->id, blueprint->id ) ); IKARUS_TRYRV_OR_FAIL( , "unable to remove entity property values: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "DELETE FROM `entity_property_values` WHERE `entity` = ? AND " "`property` IN (SELECT " "`id` FROM `properties` WHERE `blueprint` = ?)", entity->id, blueprint->id ) ) } void ikarus_entity_get_linked_blueprints( IkarusEntity const * entity, struct IkarusBlueprint ** blueprints_out, size_t blueprints_out_size, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_FAIL_IF_NULL(blueprints_out, ); if (blueprints_out_size == 0) { return; } int64_t ids[blueprints_out_size]; IKARUS_TRYRV_OR_FAIL( , "unable to fetch entity linked blueprints from database: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_many_buffered( "SELECT `blueprint` FROM `entity_blueprint_links` WHERE `entity` = " "?", ids, blueprints_out_size, entity->id ) ) for (size_t i = 0; i < blueprints_out_size; ++i) { blueprints_out[i] = entity->project->get_blueprint(ids[i]); } } size_t ikarus_entity_get_linked_blueprint_count( IkarusEntity const * entity, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, 0); IKARUS_FAIL_IF_OBJECT_MISSING(entity, 0); IKARUS_VTRYRV_OR_FAIL( auto const ret, 0, "unable to fetch entity linked blueprint count from database: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT COUNT(`blueprint`) FROM `entity_blueprint_links` WHERE " "`entity` = ?", entity->id ) ); return ret; } bool ikarus_entity_has_value( IkarusEntity const * entity, char const * name, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, false); IKARUS_FAIL_IF_OBJECT_MISSING(entity, false); IKARUS_FAIL_IF_NAME_INVALID(name, false); IKARUS_VTRYRV_OR_FAIL( auto const has_value, false, "unable to check whether entity has value: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT EXISTS(SELECT 1 FROM `entity_values` WHERE `entity` = ? " "AND `name` = ?)", entity->id, name ) ); return has_value; } struct IkarusValue * ikarus_entity_get_value( IkarusEntity const * entity, char const * name, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_OBJECT_MISSING(entity, nullptr); IKARUS_FAIL_IF_VALUE_MISSING(entity, name, nullptr); IKARUS_FAIL_IF_NAME_INVALID(name, nullptr); auto * value = fetch_value_from_db( entity->project, error_out, "SELECT `value` FROM `entity_values` WHERE `entity` = ? AND `name` = ?", entity->id, name ); IKARUS_FAIL_IF_ERROR(nullptr); return value; } void ikarus_entity_set_value( IkarusEntity * entity, char const * name, struct IkarusValue const * value, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_FAIL_IF_NAME_INVALID(name, ); IKARUS_FAIL_IF_NULL(value, ); IKARUS_TRYRV_OR_FAIL( , "unable to set entity value: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "INSERT INTO `entity_values`(`entity`, `name`, `value`) VALUES(?1, " "?2, ?3) ON " "CONFLICT(`entity`, `name`) DO UPDATE SET `value` = ?3", entity->id, name, boost::json::serialize(value->to_json()) ) ); } void ikarus_entity_delete_value( IkarusEntity * entity, char const * name, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_FAIL_IF_VALUE_MISSING(entity, name, ); IKARUS_FAIL_IF_NAME_INVALID(name, ); IKARUS_TRYRV_OR_FAIL( , "unable to delete entity value: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "DELETE FROM `entity_values` WHERE `entity` = ? AND `name` = ?", entity->id, name ) ); } bool ikarus_entity_has_property( IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, false); IKARUS_FAIL_IF_OBJECT_MISSING(entity, false); IKARUS_FAIL_IF_NULL(property, false); IKARUS_FAIL_IF_OBJECT_MISSING(property, false); // given that values are loaded lazily we can't just check // `entity_property_values` here IKARUS_VTRYRV_OR_FAIL( auto const has_property, false, "unable to check whether entity has property: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT EXISTS(\n" " SELECT 1\n" " FROM `entity_blueprint_links`\n" " JOIN `properties` ON `properties`.`blueprint` = " "`entity_blueprint_links`.`blueprint`\n" " WHERE `entity_blueprint_links`.`entity` = ? AND " "`properties`.`id` = ?\n" ")", entity->id, property->id ) ) return has_property; } void ikarus_entity_get_properties( IkarusEntity const * entity, struct IkarusProperty ** properties_out, size_t properties_out_size, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_FAIL_IF_NULL(properties_out, ); if (properties_out_size == 0) { return; } std::tuple ids_and_types[properties_out_size]; // given that values are loaded lazily we can't just check // `entity_property_values` here IKARUS_TRYRV_OR_FAIL( , "unable to fetch properties from entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_many_buffered( "SELECT `properties`.`id`, `properties`.`type`\n" "FROM `entity_blueprint_links`\n" "JOIN `properties` ON `properties`.`blueprint` = " "`entity_blueprint_links`.`blueprint`\n" "WHERE `entity_blueprint_links`.`entity` = ?\n", ids_and_types, properties_out_size, entity->id ) ); for (size_t i = 0; i < properties_out_size; ++i) { auto [id, type] = ids_and_types[i]; properties_out[i] = entity->project->get_property(id, type); } } size_t ikarus_entity_get_property_count( IkarusEntity const * entity, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, 0); IKARUS_FAIL_IF_OBJECT_MISSING(entity, 0); // given that values are loaded lazily we can't just check // `entity_property_values` here IKARUS_VTRYRV_OR_FAIL( size_t const count, 0, "unable to fetch property count from entity: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->query_one( "SELECT COUNT(`properties`.`id`)\n" "FROM `entity_blueprint_links`\n" "JOIN `properties` ON `properties`.`blueprint` = " "`entity_blueprint_links`.`blueprint`\n" "WHERE `entity_blueprint_links`.`entity` = ?\n" ")", entity->id ) ); return count; } struct IkarusValue * ikarus_entity_get_property_value( IkarusEntity const * entity, struct IkarusProperty const * property, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, nullptr); IKARUS_FAIL_IF_OBJECT_MISSING(entity, nullptr); IKARUS_FAIL_IF_NULL(property, nullptr); IKARUS_FAIL_IF_OBJECT_MISSING(property, nullptr); auto * value = fetch_value_from_db( entity->project, error_out, "SELECT IFNULL(\n" " (\n" " SELECT `value`\n" " FROM `entity_property_values`\n" " WHERE `entity` = ?1 AND `property` = ?2\n" " ),\n" " (SELECT `default_value` FROM `properties` WHERE `id` = ?2)\n" ")", entity->id, property->id ); IKARUS_FAIL_IF_ERROR(nullptr); return value; } void ikarus_entity_set_property_value( IkarusEntity * entity, struct IkarusProperty const * property, struct IkarusValue * value, IkarusErrorData * error_out ) { IKARUS_FAIL_IF_NULL(entity, ); IKARUS_FAIL_IF_OBJECT_MISSING(entity, ); IKARUS_FAIL_IF_NULL(property, ); IKARUS_FAIL_IF_OBJECT_MISSING(property, ); IKARUS_FAIL_IF_NULL(value, ); IKARUS_TRYRV_OR_FAIL( , "unable to set entity property value: {}", IkarusErrorInfo_Database_QueryFailed, entity->project->db->execute( "INSERT INTO `entity_property_values`(`entity`, `property`, " "`value`) VALUES(?1, ?2, " "?3) ON CONFLICT(`entity`, `property`) DO UPDATE SET `value` = ?3", entity->id, property->id, boost::json::serialize(value->to_json()) ) ); }