//--------------------------------------------------------------------------------------- void SkUERemote::get_project_info(SkProjectInfo * out_project_info_p) { // Get platform id string out_project_info_p->m_platform_id = FStringToAString(UGameplayStatics::GetPlatformName()); // Get engine id string out_project_info_p->m_engine_id.ensure_size(20); out_project_info_p->m_engine_id.format("UE%d.%d.%d-%s", ENGINE_MAJOR_VERSION, ENGINE_MINOR_VERSION, ENGINE_PATCH_VERSION, BUILT_FROM_CHANGELIST ? "Installed" : "Compiled"); // Get game name out_project_info_p->m_project_name = FStringToAString(FApp::GetGameName()); // Name of generated scripts overlay TCHAR const * const generated_overlay_name_p = TEXT("Project-Generated"); // Look for default SkookumScript project file in engine folder. FString default_project_path(FPaths::EnginePluginsDir() / TEXT("SkookumScript/Scripts/Skookum-project-default.ini")); SK_ASSERTX(FPaths::FileExists(default_project_path), a_str_format("Cannot find default project settings file '%S'!", *default_project_path)); out_project_info_p->m_default_project_path = FStringToAString(FPaths::ConvertRelativePathToFull(default_project_path)); // Check if we have loaded any game if (!out_project_info_p->m_project_name.is_empty()) { // Look for specific SkookumScript project in game/project folder. // 1) Check permanent location FString project_path(FPaths::GameDir() / TEXT("Scripts/Skookum-project.ini")); if (FPaths::FileExists(project_path)) { #if WITH_EDITORONLY_DATA if (m_editor_interface_p) { m_editor_interface_p->set_overlay_path(FPaths::GetPath(project_path), generated_overlay_name_p); } #endif } else { project_path.Empty(); #if WITH_EDITORONLY_DATA if (m_editor_interface_p) { // 2) Check/create temp location project_path = m_editor_interface_p->ensure_temp_project(generated_overlay_name_p); SK_ASSERTX(!project_path.IsEmpty(), a_str_format("Could not generated project file '%S' for project '%s'!", *project_path, out_project_info_p->m_project_name.as_cstr())); } #endif } out_project_info_p->m_project_path = FStringToAString(FPaths::ConvertRelativePathToFull(project_path)); } }
//--------------------------------------------------------------------------------------- // Execute a blueprint event void SkUEBlueprintInterface::mthd_trigger_event(SkInvokedMethod * scope_p, SkInstance ** result_pp) { const EventEntry & event_entry = static_cast<const EventEntry &>(*ms_singleton_p->m_binding_entry_array[scope_p->get_invokable()->get_user_data()]); SK_ASSERTX(event_entry.m_type == BindingType_Event, "BindingEntry has bad type!"); // Create parameters on stack const K2ParamEntry * param_entry_array = event_entry.get_param_entry_array(); UFunction * ue_function_p = event_entry.m_ue_function_p.Get(); // Invoke the first one uint8_t * k2_params_storage_p = a_stack_allocate(ue_function_p->ParmsSize, uint8_t); for (uint32_t i = 0; i < event_entry.m_num_params; ++i) { const K2ParamEntry & param_entry = param_entry_array[i]; (*param_entry.m_getter_p)(k2_params_storage_p + param_entry.m_offset, scope_p->get_arg(i)); } // Invoke K2 script event with parameters AActor * actor_p = scope_p->this_as<SkUEActor>(); if (!event_entry.m_ue_function_to_invoke_p.IsValid()) { // Find Kismet copy of our method to invoke event_entry.m_ue_function_to_invoke_p = actor_p->FindFunctionChecked(*ue_function_p->GetName()); } // Check if this event is actually present in any Blueprint graph SK_ASSERTX(event_entry.m_ue_function_to_invoke_p->Script.Num() > 0, a_str_format("Warning: Call to %S has no effect as no such Blueprint event node exists.", *ue_function_p->GetName())); actor_p->ProcessEvent(event_entry.m_ue_function_to_invoke_p.Get(), k2_params_storage_p); // No return value if (result_pp) *result_pp = SkBrain::ms_nil_p; return; }
//--------------------------------------------------------------------------------------- // Actor@named(Name name) <ThisClass_> static void mthdc_named(SkInvokedMethod * scope_p, SkInstance ** result_pp) { if (result_pp) // Do nothing if result not desired { // Find actor SkClass * sk_class_p; UClass * ue_class_p; SkInstance * instance_p; SkInstance * name_p = scope_p->get_arg(SkArg_1); AActor * actor_p = find_named( name_p->get_class() == SkUEName::get_class() ? name_p->as<SkUEName>() : AStringToFName(name_p->as<SkString>()), scope_p, &sk_class_p, &ue_class_p, &instance_p); #if (SKOOKUM & SK_DEBUG) if (!actor_p) { SK_ERRORX(a_str_format("Tried to get instance named '%s' from class '%s', but no such instance exists!\n", scope_p->get_arg<SkString>(SkArg_1).as_cstr(), ((SkMetaClass *)scope_p->get_topmost_scope())->get_class_info()->get_name().as_cstr_dbg())); } #endif // Create instance from our actor, even if null *result_pp = instance_p ? instance_p : SkUEActor::new_instance(actor_p, ue_class_p, sk_class_p); } }
//--------------------------------------------------------------------------------------- // Resolve the raw data info of each raw data member of the given class void SkUEClassBindingHelper::resolve_raw_data(SkClass * class_p) { // By default, inherit raw pointer and accessor functions from super class SkClass * super_class_p = class_p->get_superclass(); if (super_class_p) { if (!class_p->get_raw_pointer_func()) { class_p->register_raw_pointer_func(super_class_p->get_raw_pointer_func()); } if (!class_p->get_raw_accessor_func()) { class_p->register_raw_accessor_func(super_class_p->get_raw_accessor_func()); } } // First check if it's a class UStruct * ue_struct_or_class_p = get_ue_class_from_sk_class(class_p); if (!ue_struct_or_class_p) { // Not a class, must be a struct then ue_struct_or_class_p = get_static_ue_struct_from_sk_class(class_p); } if (ue_struct_or_class_p) { // Resolve raw data resolve_raw_data(class_p, ue_struct_or_class_p); } else { // In cooked builds, don't bother as unused classes might have been optimized out #if WITH_EDITORONLY_DATA // Potentially report error tSkTypedNameRawArray & raw_data = class_p->get_instance_data_raw_for_resolving(); if (!raw_data.is_empty()) { // Check if maybe all variables are already resolved bool all_resolved = true; for (auto var_p : raw_data) { if (var_p->m_raw_data_info == SkRawDataInfo_Invalid) { all_resolved = false; break; } } // In commandlet mode, SkookumScript code is never run // If all resolved already, no problem either SK_ASSERTX(all_resolved || IsRunningCommandlet(), a_str_format("Class '%s' has raw data but no known class mapping to UE4 for resolving.", class_p->get_name_cstr_dbg())); } #endif } }
//--------------------------------------------------------------------------------------- // Helper function for various from methods below static bool setup_from(SkInvokedMethod * scope_p, AActor ** actor_pp, SkClass ** sk_class_pp, SkClass ** known_sk_class_pp, UClass ** ue_class_pp) { // Class we are looking for SkClass * sk_class_p = *sk_class_pp = ((SkMetaClass *)scope_p->get_topmost_scope())->get_class_info(); // Actor we are getting the component(s) from AActor * actor_p = *actor_pp = scope_p->get_arg<SkUEActor>(SkArg_1); SK_ASSERTX(actor_p, a_str_format("Failed to get a '%s' from null actor.", sk_class_p->get_name_cstr_dbg())); // Figure out which UE4 class to look for bool is_class_valid = true; if (actor_p) { SkClass * known_sk_class_p = *known_sk_class_pp = SkUEClassBindingHelper::find_most_derived_super_class_known_to_ue(sk_class_p, ue_class_pp); is_class_valid = sk_class_p->is_component_class() || known_sk_class_p == sk_class_p; SK_ASSERTX(is_class_valid, a_str_format("Failed to get a '%s' from actor '%S'. Make sure that '%s' is either a class known to UE4 (a Blueprint class or an exposed UCLASS), or it is erived from SkookumScriptBehaviorComponent.", sk_class_p->get_name_cstr_dbg(), *actor_p->GetName(), sk_class_p->get_name_cstr_dbg())); } return actor_p && is_class_valid; }
void SkUEBlueprintInterface::exec_coroutine(FFrame & stack, void * const result_p) { const FunctionEntry & function_entry = static_cast<const FunctionEntry &>(*ms_singleton_p->m_binding_entry_array[stack.CurrentNativeFunction->RepOffset]); SK_ASSERTX(function_entry.m_type == BindingType_Function, "BindingEntry has bad type!"); SK_ASSERTX(function_entry.m_sk_invokable_p->get_invoke_type() == SkInvokable_coroutine, "Must not be coroutine at this point."); // Create invoked coroutine SkInvokedCoroutine * icoroutine_p = SkInvokedCoroutine::pool_new(static_cast<SkCoroutine *>(function_entry.m_sk_invokable_p)); // Get instance of this object SkInstance * this_p = SkUEEntity::new_instance(stack.Object); // Set parameters icoroutine_p->reset(SkCall_interval_always, nullptr, this_p, nullptr, nullptr); #if defined(SKDEBUG_COMMON) // Set with SKDEBUG_ICALL_STORE_GEXPR stored here before calls to argument expressions // overwrite it. const SkExpressionBase * call_expr_p = SkInvokedContextBase::ms_last_expr_p; #endif SKDEBUG_ICALL_SET_EXPR(icoroutine_p, call_expr_p); // Fill invoked coroutine's argument list const SkParamEntry * param_entry_array = function_entry.get_param_entry_array(); icoroutine_p->data_ensure_size(function_entry.m_num_params); for (uint32_t i = 0; i < function_entry.m_num_params; ++i) { const SkParamEntry & param_entry = param_entry_array[i]; icoroutine_p->data_append_arg((*param_entry.m_fetcher_p)(stack)); } // Done with stack - now increment the code ptr unless it is null stack.Code += !!stack.Code; SKDEBUG_HOOK_EXPR(call_expr_p, icoroutine_p, nullptr); #if (SKOOKUM & SK_DEBUG) if (!this_p->get_class()->is_class(*function_entry.m_sk_class_p)) { SK_ERRORX(a_str_format("Attempted to invoke coroutine '%s@%s' via a blueprint of type '%s'. You might have forgotten to specify the SkookumScript type of this blueprint as '%s' in its SkookumScript component.", function_entry.m_sk_class_p->get_name_cstr(), function_entry.m_invokable_name.as_cstr(), this_p->get_class()->get_name_cstr(), function_entry.m_sk_class_p->get_name_cstr())); } else #endif { // Invoke the coroutine on this_p - might return immediately icoroutine_p->on_update(); } // Free if not in use by our invoked coroutine this_p->dereference(); }
bool USkookumScriptListener::coro_wait_event(SkInvokedCoroutine * scope_p, tUnregisterCallback register_f, tUnregisterCallback unregister_f) { UObject * this_p = scope_p->this_as<SkUEEntity>(); SK_ASSERTX(this_p, a_str_format("Tried to wait for an event on an Entity of type '%s' but it is null!", scope_p->get_this()->get_class()->get_name_cstr())); // If this_p is null, we can't listen for events so return immediately if (!this_p) return true; // Just started? if (scope_p->m_update_count == 0u) { // If this is null, treat it as if there's nothing to do if (!this_p) return true; // Install and store away event listener USkookumScriptListener * listener_p = SkookumScriptListenerManager::get_singleton()->alloc_listener(this_p, scope_p, unregister_f); scope_p->append_user_data<FSkookumScriptListenerAutoPtr, USkookumScriptListener *>(listener_p); (*register_f)(this_p, listener_p); // Suspend coroutine scope_p->suspend(); // Coroutine not complete yet - call again when resumed return false; } // Get back stored event listener USkookumScriptListener * listener_p = scope_p->get_user_data<FSkookumScriptListenerAutoPtr>()->Get(); SK_ASSERTX(listener_p->has_event(), "Must have event at this point as coroutine was resumed by delegate object."); // Return first event queued up on listener // and *DISCARD* potential other events SkClosure * closure_p = scope_p->get_arg_data<SkClosure>(SkArg_1); uint32_t num_arguments = listener_p->get_num_arguments(); bool exit = false; USkookumScriptListener::EventInfo * event_p = listener_p->pop_event(); for (uint32_t i = 0; i < num_arguments; ++i) { scope_p->set_arg(SkArg_1 + i, event_p->m_argument_p[i]); // Store parameters as return values if exiting } listener_p->free_event(event_p, false); // Ok done, return event parameters and quit return true; }
void SkUEBlueprintInterface::exec_method(FFrame & stack, void * const result_p, SkInstance * this_p) { const FunctionEntry & function_entry = static_cast<const FunctionEntry &>(*ms_singleton_p->m_binding_entry_array[stack.CurrentNativeFunction->RepOffset]); SK_ASSERTX(function_entry.m_type == BindingType_Function, "BindingEntry has bad type!"); SK_ASSERTX(function_entry.m_sk_invokable_p->get_invoke_type() == SkInvokable_method, "Must not be coroutine or atomic at this point."); SkInvokedMethod imethod(nullptr, this_p, static_cast<SkMethodBase *>(function_entry.m_sk_invokable_p), a_stack_allocate(function_entry.m_sk_invokable_p->get_invoked_data_array_size(), SkInstance*)); SKDEBUG_ICALL_SET_INTERNAL(&imethod); SKDEBUG_HOOK_SCRIPT_ENTRY(function_entry.m_invokable_name); // Fill invoked method's argument list const SkParamEntry * param_entry_array = function_entry.get_param_entry_array(); imethod.data_ensure_size(function_entry.m_num_params); for (uint32_t i = 0; i < function_entry.m_num_params; ++i) { const SkParamEntry & param_entry = param_entry_array[i]; imethod.data_append_arg((*param_entry.m_fetcher_p)(stack)); } // Done with stack - now increment the code ptr unless it is null stack.Code += !!stack.Code; #if (SKOOKUM & SK_DEBUG) if (!this_p->get_class()->is_class(*function_entry.m_sk_class_p)) { SK_ERRORX(a_str_format("Attempted to invoke method '%s@%s' via a blueprint of type '%s'. You might have forgotten to specify the SkookumScript type of this blueprint as '%s' in its SkookumScript component.", function_entry.m_sk_class_p->get_name_cstr(), function_entry.m_invokable_name.as_cstr(), this_p->get_class()->get_name_cstr(), function_entry.m_sk_class_p->get_name_cstr())); } else #endif { // Call method SkInstance * result_instance_p = SkBrain::ms_nil_p; static_cast<SkMethod *>(function_entry.m_sk_invokable_p)->SkMethod::invoke(&imethod, nullptr, &result_instance_p); // We know it's a method so call directly if (function_entry.m_result_getter) { (*function_entry.m_result_getter)(result_p, result_instance_p); } } SKDEBUG_HOOK_SCRIPT_EXIT(); }
//--------------------------------------------------------------------------------------- // Determine SkookumScript class from UClass SkClass * SkUEClassBindingHelper::get_object_class(UObject * obj_p, UClass * def_uclass_p /*= nullptr*/, SkClass * def_class_p /*= nullptr*/) { SkClass * class_p = def_class_p; if (obj_p) { UClass * obj_uclass_p = obj_p->GetClass(); if (obj_uclass_p != def_uclass_p) { // Crawl up class hierarchy until we find a class known to Sk SkClass * obj_class_p = nullptr; for (; !obj_class_p && obj_uclass_p; obj_uclass_p = obj_uclass_p->GetSuperClass()) { obj_class_p = get_sk_class_from_ue_class(obj_uclass_p); } SK_ASSERTX(obj_class_p, a_str_format("UObject of type '%S' has no matching SkookumScript type!", *obj_p->GetClass()->GetName())); class_p = obj_class_p; } } return class_p; }
int32_t SkUEBlueprintInterface::try_add_binding_entry(UClass * ue_class_p, SkInvokableBase * sk_invokable_p) { // Only look at methods that are annotated as blueprint if (sk_invokable_p->get_annotation_flags() & SkAnnotation_Blueprint) { // If it's a method with no body... if (sk_invokable_p->get_invoke_type() == SkInvokable_method_func || sk_invokable_p->get_invoke_type() == SkInvokable_method_mthd) { // ...it's an event return add_event_entry(ue_class_p, static_cast<SkMethodBase *>(sk_invokable_p)); } else if (sk_invokable_p->get_invoke_type() == SkInvokable_method || sk_invokable_p->get_invoke_type() == SkInvokable_coroutine) { // ...otherwise it's a function/coroutine return add_function_entry(ue_class_p, sk_invokable_p); } else { SK_ERRORX(a_str_format("Trying to export coroutine %s to Blueprints which is atomic. Currently only scripted coroutines can be invoked via Blueprints.", sk_invokable_p->get_name_cstr())); } } return -1; }
void SkUEBlueprintInterface::delete_binding_entry(uint32_t binding_index) { BindingEntry * binding_entry_p = m_binding_entry_array[binding_index]; if (binding_entry_p) { SK_ASSERTX(binding_entry_p->m_ue_function_p.IsValid() || !binding_entry_p->m_ue_class_p.IsValid(), a_str_format("UFunction %s was deleted outside of SkUEBlueprintInterface and left dangling links behind in its owner UClass (%S).", binding_entry_p->m_invokable_name.as_cstr(), *binding_entry_p->m_ue_class_p->GetName())); if (binding_entry_p->m_ue_function_p.IsValid()) { UFunction * ue_function_p = binding_entry_p->m_ue_function_p.Get(); UClass * ue_class_p = binding_entry_p->m_ue_class_p.Get(); // Unlink from its owner class ue_class_p->RemoveFunctionFromFunctionMap(ue_function_p); // Unlink from the Children list as well UField ** prev_field_pp = &ue_class_p->Children; for (UField * field_p = *prev_field_pp; field_p; prev_field_pp = &field_p->Next, field_p = *prev_field_pp) { if (field_p == ue_function_p) { *prev_field_pp = field_p->Next; break; } } // Destroy the function along with its attached properties ue_function_p->ConditionalBeginDestroy(); } AMemory::free(binding_entry_p); m_binding_entry_array.set_at(binding_index, nullptr); } }
//--------------------------------------------------------------------------------------- // Set remote connection mode. // // #Author(s): Conan Reis void SkUERemote::set_mode(eSkLocale mode) { if (m_mode != mode) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Stop old mode if (m_socket_p) { SkDebug::print(a_str_format("SkookumScript: Disconnecting... %s\n", get_socket_str().as_cstr()), SkLocale_local); ISocketSubsystem * socket_system_p = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM); if (!m_socket_p->Close()) { SkDebug::print(a_str_format(" error closing socket: %i\n", (int32)socket_system_p->GetLastErrorCode()), SkLocale_local); } // Free the memory the OS allocated for this socket socket_system_p->DestroySocket(m_socket_p); m_socket_p = NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Start new mode SkookumRemoteBase::set_mode(mode); // $Revisit - CReis Update debug UI of Skookum IDE connection state switch (mode) { case SkLocale_embedded: set_connect_state(ConnectState_disconnected); SkDebug::print("\nSkookumScript: Skookum IDE not connected (off-line)\n\n", SkLocale_local); break; case SkLocale_runtime: { SkDebug::print("SkookumScript: Attempting to connect to remote IDE\n", SkLocale_local); set_connect_state(ConnectState_connecting); m_socket_p = FTcpSocketBuilder(TEXT("SkookumIDE.RemoteConnection")) .AsReusable() .AsBlocking(); bool success = false; if (m_socket_p) { TSharedPtr<FInternetAddr> ip_addr = get_ip_address_local(); // Check if there's a file named "ide-ip.txt" present in the compiled binary folder // If so, use the ip stored in it to connect to the IDE FString ip_file_path; if (static_cast<SkUERuntime*>(SkUERuntime::ms_singleton_p)->content_file_exists(TEXT("ide-ip.txt"), &ip_file_path)) { ip_file_path /= TEXT("ide-ip.txt"); FString ip_text; if (FFileHelper::LoadFileToString(ip_text, *ip_file_path)) { bool is_valid; ip_addr->SetIp(*ip_text, is_valid); } } ip_addr->SetPort(SkUERemote_ide_port); success = m_socket_p->Connect(*ip_addr); } if (!success) { SkDebug::print("\nSkookumScript: Failed attempt to connect with remote IDE.!\n\n", SkLocale_local); set_mode(SkLocale_embedded); return; } SkDebug::print(a_str_format("SkookumScript: Connected %s\n", get_socket_str().as_cstr()), SkLocale_local); set_connect_state(ConnectState_authenticating); break; } } } }
bool USkookumScriptListener::coro_on_event_do(SkInvokedCoroutine * scope_p, tRegisterCallback register_f, tUnregisterCallback unregister_f, bool do_until) { UObject * this_p = scope_p->this_as<SkUEEntity>(); SK_ASSERTX(this_p, a_str_format("Tried to attach an event handler to an Entity of type '%s' but it is null!", scope_p->get_this()->get_class()->get_name_cstr())); // If this_p is null, we can't listen for events so return immediately if (!this_p) return true; // Just started? if (scope_p->m_update_count == 0u) { // Install and store away event listener USkookumScriptListener * listener_p = SkookumScriptListenerManager::get_singleton()->alloc_listener(this_p, scope_p, unregister_f); scope_p->append_user_data<FSkookumScriptListenerAutoPtr, USkookumScriptListener *>(listener_p); (*register_f)(this_p, listener_p); // Suspend coroutine scope_p->suspend(); // Coroutine not complete yet - call again when resumed return false; } // Get back stored event listener USkookumScriptListener * listener_p = scope_p->get_user_data<FSkookumScriptListenerAutoPtr>()->Get(); SK_ASSERTX(listener_p->has_event(), "Must have event at this point as coroutine was resumed by delegate object."); // Run closure on each event accumulated in the listener SkClosure * closure_p = scope_p->get_arg_data<SkClosure>(SkArg_1); uint32_t num_arguments = listener_p->get_num_arguments(); bool exit = false; SkInstance * closure_result_p = SkBrain::ms_nil_p; SkInstance * return_value_p = SkBrain::ms_nil_p; do { // Use event parameters to invoke closure, then recycle event USkookumScriptListener::EventInfo * event_p = listener_p->pop_event(); if (do_until) { // Add reference to potential return values so they survive closure_method_call for (uint32_t i = 0; i < num_arguments; ++i) { event_p->m_argument_p[SkArg_1 + i]->reference(); } } closure_p->closure_method_call(&event_p->m_argument_p[0], listener_p->get_num_arguments(), &closure_result_p, scope_p); if (do_until) { exit = closure_result_p->as<SkBoolean>(); if (exit) { for (uint32_t i = 0; i < num_arguments; ++i) { scope_p->set_arg(SkArg_2 + i, event_p->m_argument_p[i]); // Store parameters as return values if exiting } } else { for (uint32_t i = 0; i < num_arguments; ++i) { event_p->m_argument_p[i]->dereference(); // Dereference parameters if not needed after all } } closure_result_p->dereference(); // Free Boolean return value } listener_p->free_event(event_p, false); } while (listener_p->has_event() && !exit); if (!do_until || !exit) { // We're not done - wait for more events scope_p->suspend(); return false; } // Ok done, return event parameters and quit return true; }
void SkUEBlueprintInterface::bind_event_method(SkMethodBase * sk_method_p) { SK_ASSERTX(!sk_method_p->is_bound() || static_cast<SkMethodFunc *>(sk_method_p)->m_atomic_f == &mthd_trigger_event, a_str_format("Trying to bind Blueprint event method '%s' but it is already bound to a different atomic implementation!", sk_method_p->get_name_cstr_dbg())); if (!sk_method_p->is_bound()) { sk_method_p->get_scope()->register_method_func(sk_method_p->get_name(), &mthd_trigger_event, SkBindFlag_instance_no_rebind); } }
//--------------------------------------------------------------------------------------- void USkookumScriptComponent::create_sk_instance() { SK_ASSERTX(!m_instance_p, "Tried to create instance when instance already present!"); // Find the actor I belong to AActor * actor_p = GetOwner(); SK_ASSERTX(actor_p, "USkookumScriptComponent must be attached to an actor."); // Determine SkookumScript class of my actor SkClass * class_p = nullptr; FString class_name = ScriptClassName; if (!class_name.IsEmpty()) { AString class_name_ascii(*class_name, class_name.Len()); class_p = SkBrain::get_class(class_name_ascii.as_cstr()); SK_ASSERTX(class_p, a_cstr_format("Cannot find Script Class Name '%s' specified in SkookumScriptComponent of '%S'. Misspelled?", class_name_ascii.as_cstr(), *actor_p->GetName())); if (!class_p) goto set_default_class; // Recover from bad user input // Do some extra checking in non-shipping builds #if (SKOOKUM & SK_DEBUG) // Find most derived SkookumScript class known to UE4 SkClass * known_super_class_p; for (known_super_class_p = class_p; known_super_class_p; known_super_class_p = known_super_class_p->get_superclass()) { if (SkUEClassBindingHelper::get_ue_class_from_sk_class(known_super_class_p)) break; } // Find most derived UE4 class known to SkookumScript SkClass * mapped_super_class_p = nullptr; for (UClass * obj_uclass_p = actor_p->GetClass(); !mapped_super_class_p && obj_uclass_p; obj_uclass_p = obj_uclass_p->GetSuperClass()) { mapped_super_class_p = SkUEClassBindingHelper::get_sk_class_from_ue_class(obj_uclass_p); } SK_ASSERTX(class_p->is_mind_class() || (mapped_super_class_p && mapped_super_class_p == known_super_class_p), a_cstr_format("Script Class Name '%s' in SkookumScriptComponent of '%S' is not properly related to Actor. Either the Script Class Name must be derived from Mind, or both the Script Class Name '%s' and the UE4 class of '%S' ('%S') must share the topmost ancestor class known to both SkookumScript and UE4. Right now these ancestor classes are different ('%s' for '%s' and '%s' for '%S').", class_name_ascii.as_cstr(), *actor_p->GetName(), class_name_ascii.as_cstr(), *actor_p->GetName(), *actor_p->GetClass()->GetName(), known_super_class_p ? known_super_class_p->get_name_cstr_dbg() : "<none>", class_name_ascii.as_cstr(), mapped_super_class_p ? mapped_super_class_p->get_name_cstr_dbg() : "<none>", *actor_p->GetClass()->GetName())); #endif } else { set_default_class: // Find most derived UE4 class known to SkookumScript class_p = nullptr; // Is already null when we get here, but set again for clarity for (UClass * obj_uclass_p = actor_p->GetClass(); !class_p && obj_uclass_p; obj_uclass_p = obj_uclass_p->GetSuperClass()) { class_p = SkUEClassBindingHelper::get_sk_class_from_ue_class(obj_uclass_p); } SK_ASSERTX(class_p, a_cstr_format("No parent class of %S is known to SkookumScript!", *actor_p->GetClass()->GetName())); if (!class_p) { class_p = SkBrain::get_class(ASymbol_Actor); // Recover from bad user input } } // Based on the desired class, create SkInstance or SkDataInstance // Currently, we support only actors and minds SK_ASSERTX(class_p->is_actor_class() || class_p->is_mind_class(), a_str_format("Trying to create a SkookumScriptComponent of class '%s' which is neither an actor nor a mind.", class_p->get_name_cstr_dbg())); SkInstance * instance_p = class_p->new_instance(); if (class_p->is_actor_class()) { instance_p->construct<SkUEActor>(actor_p); // Keep track of owner actor } m_instance_p = instance_p; }
//--------------------------------------------------------------------------------------- // Modifiers: static // Author(s): Conan Reis ASymbolRef * ASymbolTable::symbol_reference(uint32_t sym_id, const char * cstr_p, uint32_t length, eATerm term) { if (sym_id == ASymbol_id_null) { #if defined(A_SYMBOL_REF_LINK) return ASymbol::ms_null.m_ref_p; #else return const_cast<ASymbolRef *>(&ASymbolRef::get_null()); #endif } // Ensure symbol string no larger than 255 characters since only 1-byte is used to store // length in binary. A_ASSERTX( length <= UINT8_MAX, AErrMsg( a_str_format( "Tried to create symbol '%s' (0x%X) but it too long!\n" "Its length is %u and the max length is 255 characters.\n" "[Try to use a different shorter string if possible.]", cstr_p, sym_id, length), AErrLevel_notify)); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Use existing symbol reference if it is already registered. uint32_t idx; ASymbolRef * sym_ref_p = m_sym_refs.get(sym_id, 1u, &idx); if (sym_ref_p) { // Found existing symbol reference // Check for name collision A_ASSERTX( sym_ref_p->m_str_ref_p->is_equal(cstr_p, length), AErrMsg( a_str_format( "Symbol id collision! The new string '%s' and the string '%s' are different,\n" "but they both have the same id 0x%X.\n" "[Try to use a different string if possible and hope that it has a unique id.]", cstr_p, sym_ref_p->m_str_ref_p->m_cstr_p, sym_id), AErrLevel_notify)); return sym_ref_p; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Create new symbol reference AStringRef * str_ref_p = (term == ATerm_long) ? AStringRef::pool_new(cstr_p, length, length + 1u, 1u, false, true) : AStringRef::pool_new_copy(cstr_p, length); sym_ref_p = ASymbolRef::pool_new(str_ref_p, sym_id); m_sym_refs.insert(*sym_ref_p, idx); return sym_ref_p; }
//--------------------------------------------------------------------------------------- // Resolve the raw data info of each raw data member of the given class void SkUEClassBindingHelper::resolve_raw_data(SkClass * class_p, UStruct * ue_struct_or_class_p) { // This loop assumes that the data members of the Sk class were created from this very UE4 class // I.e. that therefore, except for unsupported properties, they must be in the same order // So all we should have to do is loop forward and skip the occasional non-exported UE4 property UProperty * ue_var_p = nullptr; ASymbol ue_var_name; TFieldIterator<UProperty> property_it(ue_struct_or_class_p, EFieldIteratorFlags::ExcludeSuper); tSkTypedNameRawArray & raw_data = class_p->get_instance_data_raw_for_resolving(); for (auto var_p : raw_data) { // Skip variable if already resolved if (var_p->m_raw_data_info != SkRawDataInfo_Invalid) { continue; } // Try to find it in the UE4 reflection data while (property_it) { ue_var_p = *property_it; ue_var_name = ASymbol::create_existing(FStringToAString(FSkookumScriptGeneratorBase::skookify_var_name(ue_var_p->GetName(), ue_var_p->IsA(UBoolProperty::StaticClass()), true))); ++property_it; if (var_p->get_name() == ue_var_name) break; } // Store raw data info in the raw data member object if (var_p->get_name() == ue_var_name) { var_p->m_raw_data_info = compute_raw_data_info(ue_var_p); } else { // Oops didn't find matching variable // This is probably due to an unsaved blueprint variable change in the UE4 editor during the previous session // If this is the case, a recompile would have been triggered when this class was loaded by get_ue_class_from_sk_class() // Which means binaries would be recompiled and reloaded once more, fixing this issue // So make sure this assumption is true SK_ASSERTX(FModuleManager::Get().GetModulePtr<ISkookumScriptRuntime>("SkookumScriptRuntime")->is_freshen_binaries_pending(), a_str_format("Sk Variable '%s.%s' not found in UE4 reflection data.", class_p->get_name_cstr_dbg(), var_p->get_name_cstr())); } } }