//---------------------------------------------------------------------------------------
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()));
      }
    }
  }