From 564e16d33e06bcb6bb72b7326c246c2ce4da45eb Mon Sep 17 00:00:00 2001 From: Hu Yueh-Wei Date: Fri, 29 Nov 2024 10:10:08 +0800 Subject: [PATCH] feat!: load go addons through registration function (#346) --- .../addon/extension_group/extension_group.h | 21 --- .../ten_runtime/binding/cpp/detail/addon.h | 45 ----- core/include/ten_runtime/binding/cpp/ten.h | 5 +- .../addon/extension_group/extension_group.h | 15 +- .../ten_runtime/binding/cpp/detail/addon.h | 52 ++++++ .../ten_runtime/binding/cpp/ten.h | 4 +- .../addon/extension_group/extension_group.c | 1 - .../binding/go/interface/ten/addon.go | 115 ------------ .../binding/go/interface/ten/addon.h | 5 +- .../binding/go/interface/ten/addon_manager.go | 175 +++++++++++++++++ .../binding/go/interface/ten/app.go | 5 + .../binding/go/native/addon/addon.c | 20 +- .../binding/python/interface/ten/addon.py | 170 ----------------- .../python/interface/ten/addon_manager.py | 176 ++++++++++++++++++ .../binding/python/interface/ten/decorator.py | 3 +- .../binding/python/interface/ten/ten_env.py | 4 +- .../binding/python/native/addon/addon.c | 1 - .../binding/python/native/addon/decorator.c | 1 - .../builtin/builtin_extension_group.c | 2 +- core/src/ten_runtime/test/test_extension.c | 2 +- .../extension/extension_a/extension.go | 2 +- 21 files changed, 440 insertions(+), 384 deletions(-) delete mode 100644 core/include/ten_runtime/addon/extension_group/extension_group.h create mode 100644 core/include_internal/ten_runtime/binding/cpp/detail/addon.h create mode 100644 core/src/ten_runtime/binding/go/interface/ten/addon_manager.go create mode 100644 core/src/ten_runtime/binding/python/interface/ten/addon_manager.py diff --git a/core/include/ten_runtime/addon/extension_group/extension_group.h b/core/include/ten_runtime/addon/extension_group/extension_group.h deleted file mode 100644 index 92bd6a64a5..0000000000 --- a/core/include/ten_runtime/addon/extension_group/extension_group.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// Copyright © 2024 Agora -// This file is part of TEN Framework, an open source project. -// Licensed under the Apache License, Version 2.0, with certain conditions. -// Refer to the "LICENSE" file in the root directory for more information. -// -#pragma once - -#include "ten_runtime/ten_config.h" - -typedef struct ten_addon_t ten_addon_t; -typedef struct ten_addon_host_t ten_addon_host_t; - -#define TEN_REGISTER_ADDON_AS_EXTENSION_GROUP(NAME, ADDON) \ - TEN_ADDON_REGISTER(extension_group, NAME, ADDON) - -TEN_RUNTIME_API ten_addon_host_t *ten_addon_register_extension_group( - const char *name, const char *base_dir, ten_addon_t *addon); - -TEN_RUNTIME_API ten_addon_t *ten_addon_unregister_extension_group( - const char *name); diff --git a/core/include/ten_runtime/binding/cpp/detail/addon.h b/core/include/ten_runtime/binding/cpp/detail/addon.h index 253cd647f9..78ad3ca1a1 100644 --- a/core/include/ten_runtime/binding/cpp/detail/addon.h +++ b/core/include/ten_runtime/binding/cpp/detail/addon.h @@ -193,18 +193,6 @@ struct addon_context_t { } // namespace -class extension_group_addon_t : public addon_t { - private: - void on_create_instance_impl(ten_env_t &ten_env, const char *name, - void *context) override { - auto *cpp_context = new addon_context_t(); - cpp_context->task = ADDON_TASK_CREATE_EXTENSION_GROUP; - cpp_context->c_context = context; - - on_create_instance(ten_env, name, cpp_context); - } -}; - class extension_addon_t : public addon_t { private: void on_create_instance_impl(ten_env_t &ten_env, const char *name, @@ -219,39 +207,6 @@ class extension_addon_t : public addon_t { } // namespace ten -#define TEN_CPP_REGISTER_ADDON_AS_EXTENSION_GROUP(NAME, CLASS) \ - class NAME##_default_extension_group_addon_t \ - : public ten::extension_group_addon_t { \ - public: \ - void on_create_instance(ten::ten_env_t &ten_env, const char *name, \ - void *context) override { \ - auto *instance = new CLASS(name); \ - ten_env.on_create_instance_done(instance, context); \ - } \ - void on_destroy_instance(ten::ten_env_t &ten_env, void *instance, \ - void *context) override { \ - delete static_cast(instance); \ - ten_env.on_destroy_instance_done(context); \ - } \ - }; \ - static ten::addon_t *g_##NAME##_default_extension_group_addon = nullptr; \ - TEN_CONSTRUCTOR(____ctor_ten_declare_##NAME##_extension_group_addon____) { \ - g_##NAME##_default_extension_group_addon = \ - new NAME##_default_extension_group_addon_t(); \ - ten_string_t *base_dir = \ - ten_path_get_module_path(/* NOLINTNEXTLINE */ \ - (void *) \ - ____ctor_ten_declare_##NAME##_extension_group_addon____); \ - ten_addon_register_extension_group( \ - #NAME, ten_string_get_raw_str(base_dir), \ - g_##NAME##_default_extension_group_addon->get_c_addon()); \ - ten_string_destroy(base_dir); \ - } \ - TEN_DESTRUCTOR(____dtor_ten_declare_##NAME##_extension_group_addon____) { \ - ten_addon_unregister_extension_group(#NAME); \ - delete g_##NAME##_default_extension_group_addon; \ - } - #define TEN_CPP_REGISTER_ADDON_AS_EXTENSION(NAME, CLASS) \ class NAME##_default_extension_addon_t : public ten::extension_addon_t { \ public: \ diff --git a/core/include/ten_runtime/binding/cpp/ten.h b/core/include/ten_runtime/binding/cpp/ten.h index fcb7ef6152..79719db81b 100644 --- a/core/include/ten_runtime/binding/cpp/ten.h +++ b/core/include/ten_runtime/binding/cpp/ten.h @@ -9,8 +9,7 @@ // This header file should be the only header file where outside world should // include in the C++ programming language. -#include "ten_runtime/addon/extension/extension.h" // IWYU pragma: export -#include "ten_runtime/addon/extension_group/extension_group.h" // IWYU pragma: export +#include "ten_runtime/addon/extension/extension.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/addon.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/app.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/common.h" // IWYU pragma: export @@ -25,7 +24,7 @@ #include "ten_runtime/binding/cpp/detail/msg/data.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/msg/msg.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/msg/video_frame.h" // IWYU pragma: export -#include "ten_runtime/binding/cpp/detail/ten_env.h" // IWYU pragma: export +#include "ten_runtime/binding/cpp/detail/ten_env.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/ten_env_impl.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/ten_env_proxy.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/test/env_tester.h" // IWYU pragma: export diff --git a/core/include_internal/ten_runtime/addon/extension_group/extension_group.h b/core/include_internal/ten_runtime/addon/extension_group/extension_group.h index 8e0c1eebf3..80ef8cc544 100644 --- a/core/include_internal/ten_runtime/addon/extension_group/extension_group.h +++ b/core/include_internal/ten_runtime/addon/extension_group/extension_group.h @@ -14,14 +14,25 @@ typedef struct ten_env_t ten_env_t; typedef struct ten_addon_store_t ten_addon_store_t; +typedef struct ten_addon_t ten_addon_t; +typedef struct ten_addon_host_t ten_addon_host_t; + +#define TEN_REGISTER_ADDON_AS_EXTENSION_GROUP(NAME, ADDON) \ + TEN_ADDON_REGISTER(extension_group, NAME, ADDON) TEN_RUNTIME_PRIVATE_API ten_addon_store_t *ten_extension_group_get_global_store( void); -TEN_RUNTIME_API bool ten_addon_create_extension_group( +TEN_RUNTIME_PRIVATE_API bool ten_addon_create_extension_group( ten_env_t *ten_env, const char *addon_name, const char *instance_name, ten_env_addon_on_create_instance_async_cb_t cb, void *user_data); -TEN_RUNTIME_API bool ten_addon_destroy_extension_group( +TEN_RUNTIME_PRIVATE_API bool ten_addon_destroy_extension_group( ten_env_t *ten_env, ten_extension_group_t *extension_group, ten_env_addon_on_destroy_instance_async_cb_t cb, void *user_data); + +TEN_RUNTIME_PRIVATE_API ten_addon_host_t *ten_addon_register_extension_group( + const char *name, const char *base_dir, ten_addon_t *addon); + +TEN_RUNTIME_PRIVATE_API ten_addon_t *ten_addon_unregister_extension_group( + const char *name); diff --git a/core/include_internal/ten_runtime/binding/cpp/detail/addon.h b/core/include_internal/ten_runtime/binding/cpp/detail/addon.h new file mode 100644 index 0000000000..d632ed412f --- /dev/null +++ b/core/include_internal/ten_runtime/binding/cpp/detail/addon.h @@ -0,0 +1,52 @@ +// +// Copyright © 2024 Agora +// This file is part of TEN Framework, an open source project. +// Licensed under the Apache License, Version 2.0, with certain conditions. +// Refer to the "LICENSE" file in the root directory for more information. +// +#pragma once + +class extension_group_addon_t : public addon_t { + private: + void on_create_instance_impl(ten_env_t &ten_env, const char *name, + void *context) override { + auto *cpp_context = new addon_context_t(); + cpp_context->task = ADDON_TASK_CREATE_EXTENSION_GROUP; + cpp_context->c_context = context; + + on_create_instance(ten_env, name, cpp_context); + } +}; + +#define TEN_CPP_REGISTER_ADDON_AS_EXTENSION_GROUP(NAME, CLASS) \ + class NAME##_default_extension_group_addon_t \ + : public ten::extension_group_addon_t { \ + public: \ + void on_create_instance(ten::ten_env_t &ten_env, const char *name, \ + void *context) override { \ + auto *instance = new CLASS(name); \ + ten_env.on_create_instance_done(instance, context); \ + } \ + void on_destroy_instance(ten::ten_env_t &ten_env, void *instance, \ + void *context) override { \ + delete static_cast(instance); \ + ten_env.on_destroy_instance_done(context); \ + } \ + }; \ + static ten::addon_t *g_##NAME##_default_extension_group_addon = nullptr; \ + TEN_CONSTRUCTOR(____ctor_ten_declare_##NAME##_extension_group_addon____) { \ + g_##NAME##_default_extension_group_addon = \ + new NAME##_default_extension_group_addon_t(); \ + ten_string_t *base_dir = \ + ten_path_get_module_path(/* NOLINTNEXTLINE */ \ + (void *) \ + ____ctor_ten_declare_##NAME##_extension_group_addon____); \ + ten_addon_register_extension_group( \ + #NAME, ten_string_get_raw_str(base_dir), \ + g_##NAME##_default_extension_group_addon->get_c_addon()); \ + ten_string_destroy(base_dir); \ + } \ + TEN_DESTRUCTOR(____dtor_ten_declare_##NAME##_extension_group_addon____) { \ + ten_addon_unregister_extension_group(#NAME); \ + delete g_##NAME##_default_extension_group_addon; \ + } diff --git a/core/include_internal/ten_runtime/binding/cpp/ten.h b/core/include_internal/ten_runtime/binding/cpp/ten.h index 87e277df2e..c096956ce7 100644 --- a/core/include_internal/ten_runtime/binding/cpp/ten.h +++ b/core/include_internal/ten_runtime/binding/cpp/ten.h @@ -9,13 +9,13 @@ // This header file should be the only header file where outside world should // include in the C++ programming language. +#include "include_internal/ten_runtime/addon/extension_group/extension_group.h" // IWYU pragma: export #include "include_internal/ten_runtime/binding/cpp/detail/extension_impl.h" // IWYU pragma: export #include "include_internal/ten_runtime/binding/cpp/detail/msg/cmd/timeout.h" // IWYU pragma: export #include "include_internal/ten_runtime/binding/cpp/detail/msg/cmd/timer.h" // IWYU pragma: export #include "include_internal/ten_runtime/binding/cpp/detail/ten_env_impl.h" // IWYU pragma: export #include "include_internal/ten_runtime/binding/cpp/detail/ten_env_internal_accessor.h" // IWYU pragma: export -#include "ten_runtime/addon/extension/extension.h" // IWYU pragma: export -#include "ten_runtime/addon/extension_group/extension_group.h" // IWYU pragma: export +#include "ten_runtime/addon/extension/extension.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/addon.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/app.h" // IWYU pragma: export #include "ten_runtime/binding/cpp/detail/common.h" // IWYU pragma: export diff --git a/core/src/ten_runtime/addon/extension_group/extension_group.c b/core/src/ten_runtime/addon/extension_group/extension_group.c index 42239d9e65..5bdc27a776 100644 --- a/core/src/ten_runtime/addon/extension_group/extension_group.c +++ b/core/src/ten_runtime/addon/extension_group/extension_group.c @@ -11,7 +11,6 @@ #include "include_internal/ten_runtime/engine/engine.h" #include "include_internal/ten_runtime/extension_group/extension_group.h" #include "include_internal/ten_runtime/ten_env/ten_env.h" -#include "ten_runtime/addon/extension_group/extension_group.h" #include "ten_utils/macro/check.h" static ten_addon_store_t g_extension_group_store = { diff --git a/core/src/ten_runtime/binding/go/interface/ten/addon.go b/core/src/ten_runtime/binding/go/interface/ten/addon.go index 62f59adc0a..336e7648bf 100644 --- a/core/src/ten_runtime/binding/go/interface/ten/addon.go +++ b/core/src/ten_runtime/binding/go/interface/ten/addon.go @@ -12,9 +12,6 @@ import "C" import ( "fmt" - "path/filepath" - "runtime" - "unsafe" ) // Addon is the interface for the addon. @@ -73,118 +70,6 @@ func NewDefaultExtensionAddon(constructor func(name string) Extension) Addon { } } -// RegisterAddonAsExtension registers the addon as an extension. -func RegisterAddonAsExtension(addonName string, instance Addon) error { - if len(addonName) == 0 { - return newTenError( - ErrnoInvalidArgument, - "addon name is empty", - ) - } - - _, file, _, ok := runtime.Caller(1) - if !ok { - return newTenError(ErrnoGeneric, "Failed to get the caller information") - } - - baseDir := filepath.Dir(file) - - absBaseDir, err := filepath.Abs(baseDir) - if err != nil { - return newTenError( - ErrnoGeneric, - fmt.Sprintf("Failed to get the absolute file path: %v", err), - ) - } - - addonWrapper := &addon{ - Addon: instance, - } - - addonID := newImmutableHandle(addonWrapper) - - var bridge C.uintptr_t - status := C.ten_go_addon_register_extension( - unsafe.Pointer(unsafe.StringData(addonName)), - C.int(len(addonName)), - unsafe.Pointer(unsafe.StringData(absBaseDir)), - C.int(len(absBaseDir)), - cHandle(addonID), - &bridge, - ) - - if err := withCGoError(&status); err != nil { - loadAndDeleteImmutableHandle(addonID) - return err - } - - addonWrapper.cPtr = bridge - - return nil -} - -// RegisterAddonAsExtensionGroup registers the addon as an extension group. -func RegisterAddonAsExtensionGroup(addonName string, instance Addon) error { - if len(addonName) == 0 { - return newTenError( - ErrnoInvalidArgument, - "addon name is empty", - ) - } - - _, file, _, ok := runtime.Caller(1) - if !ok { - return newTenError(ErrnoGeneric, "Failed to get the caller information") - } - - baseDir := filepath.Dir(file) - - absBaseDir, err := filepath.Abs(baseDir) - if err != nil { - return newTenError( - ErrnoGeneric, - fmt.Sprintf("Failed to get the absolute file path: %v", - err), - ) - } - - addonWrapper := &addon{ - Addon: instance, - } - - addonID := newImmutableHandle(addonWrapper) - - var bridge C.uintptr_t - status := C.ten_go_addon_register_extension_group( - unsafe.Pointer(unsafe.StringData(addonName)), - C.int(len(addonName)), - unsafe.Pointer(unsafe.StringData(absBaseDir)), - C.int(len(absBaseDir)), - cHandle(addonID), - &bridge, - ) - - if err := withCGoError(&status); err != nil { - loadAndDeleteImmutableHandle(addonID) - return err - } - - addonWrapper.cPtr = bridge - - return nil -} - -// unloadAllAddons unloads all addons. -func unloadAllAddons() error { - clearImmutableHandles(func(value any) { - if addon, ok := value.(*addon); ok { - C.ten_go_addon_unregister(addon.cPtr) - } - }) - - return nil -} - //export tenGoAddonOnInit func tenGoAddonOnInit( addonID C.uintptr_t, diff --git a/core/src/ten_runtime/binding/go/interface/ten/addon.h b/core/src/ten_runtime/binding/go/interface/ten/addon.h index d37defe9a5..684c6ef679 100644 --- a/core/src/ten_runtime/binding/go/interface/ten/addon.h +++ b/core/src/ten_runtime/binding/go/interface/ten/addon.h @@ -17,6 +17,7 @@ ten_go_error_t ten_go_addon_register_extension( const void *addon_name, int addon_name_len, const void *base_dir, int base_dir_len, uintptr_t go_addon, uintptr_t *bridge_addr); -ten_go_error_t ten_go_addon_register_extension_group( +ten_go_error_t ten_go_addon_register_extension_v2( const void *addon_name, int addon_name_len, const void *base_dir, - int base_dir_len, uintptr_t go_addon, uintptr_t *bridge_addr); + int base_dir_len, uintptr_t go_addon, uintptr_t *register_ctx, + uintptr_t *bridge_addr); diff --git a/core/src/ten_runtime/binding/go/interface/ten/addon_manager.go b/core/src/ten_runtime/binding/go/interface/ten/addon_manager.go new file mode 100644 index 0000000000..651feac2d9 --- /dev/null +++ b/core/src/ten_runtime/binding/go/interface/ten/addon_manager.go @@ -0,0 +1,175 @@ +// +// Copyright © 2024 Agora +// This file is part of TEN Framework, an open source project. +// Licensed under the Apache License, Version 2.0, with certain conditions. +// Refer to the "LICENSE" file in the root directory for more information. +// + +package ten + +// #include "addon.h" +import "C" + +import ( + "fmt" + "path/filepath" + "runtime" + "sync" + "unsafe" +) + +// Define a registry map to store addon registration functions. +// The key is the addonName (string), and the value is a function that takes +// a registerCtx (interface{}) and returns an error. +var ( + addonRegistry = make(map[string]func(interface{}) error) + addonRegistryMutex = sync.RWMutex{} +) + +// RegisterAddonAsExtension registers the addon as an extension. +func RegisterAddonAsExtension(addonName string, instance Addon) error { + if len(addonName) == 0 { + return newTenError( + ErrnoInvalidArgument, + "addon name is empty", + ) + } + + _, file, _, ok := runtime.Caller(1) + if !ok { + return newTenError(ErrnoGeneric, "Failed to get the caller information") + } + + baseDir := filepath.Dir(file) + + absBaseDir, err := filepath.Abs(baseDir) + if err != nil { + return newTenError( + ErrnoGeneric, + fmt.Sprintf("Failed to get the absolute file path: %v", err), + ) + } + + addonWrapper := &addon{ + Addon: instance, + } + + addonID := newImmutableHandle(addonWrapper) + + var bridge C.uintptr_t + status := C.ten_go_addon_register_extension( + unsafe.Pointer(unsafe.StringData(addonName)), + C.int(len(addonName)), + unsafe.Pointer(unsafe.StringData(absBaseDir)), + C.int(len(absBaseDir)), + cHandle(addonID), + &bridge, + ) + + if err := withCGoError(&status); err != nil { + loadAndDeleteImmutableHandle(addonID) + return err + } + + addonWrapper.cPtr = bridge + + return nil +} + +// RegisterAddonAsExtensionV2 registers the addon as an extension. +func RegisterAddonAsExtensionV2(addonName string, instance Addon) error { + if len(addonName) == 0 { + return newTenError( + ErrnoInvalidArgument, + "addon name is empty", + ) + } + + _, file, _, ok := runtime.Caller(1) + if !ok { + return newTenError(ErrnoGeneric, "Failed to get the caller information") + } + + baseDir := filepath.Dir(file) + + absBaseDir, err := filepath.Abs(baseDir) + if err != nil { + return newTenError( + ErrnoGeneric, + fmt.Sprintf("Failed to get the absolute file path: %v", err), + ) + } + + addonWrapper := &addon{ + Addon: instance, + } + + addonID := newImmutableHandle(addonWrapper) + + // Define the registration function that will be stored in the registry. + registerFunc := func(registerCtx interface{}) error { + var bridge C.uintptr_t + status := C.ten_go_addon_register_extension_v2( + unsafe.Pointer(unsafe.StringData(addonName)), + C.int(len(addonName)), + unsafe.Pointer(unsafe.StringData(absBaseDir)), + C.int(len(absBaseDir)), + cHandle(addonID), + // TODO(Wei): Pass `register_ctx` to the actual cgo function. + nil, + &bridge, + ) + + if err := withCGoError(&status); err != nil { + loadAndDeleteImmutableHandle(addonID) + return err + } + + addonWrapper.cPtr = bridge + + return nil + } + + // Store the registration function in the registry map. + addonRegistryMutex.Lock() + defer addonRegistryMutex.Unlock() + + if _, exists := addonRegistry[addonName]; exists { + return newTenError( + ErrnoInvalidArgument, + fmt.Sprintf("addon '%s' is already registered", addonName), + ) + } + + addonRegistry[addonName] = registerFunc + + return nil +} + +// LoadAllAddons executes all registered addon registration functions. +func LoadAllAddons(registerCtx interface{}) error { + addonRegistryMutex.Lock() + defer addonRegistryMutex.Unlock() + + for name, registerFunc := range addonRegistry { + if err := registerFunc(registerCtx); err != nil { + return fmt.Errorf("failed to register addon %s: %w", name, err) + } + } + + // Clear the addonRegistry to free up memory. + addonRegistry = make(map[string]func(interface{}) error) + + return nil +} + +// unloadAllAddons unloads all addons. +func unloadAllAddons() error { + clearImmutableHandles(func(value any) { + if addon, ok := value.(*addon); ok { + C.ten_go_addon_unregister(addon.cPtr) + } + }) + + return nil +} diff --git a/core/src/ten_runtime/binding/go/interface/ten/app.go b/core/src/ten_runtime/binding/go/interface/ten/app.go index af9e6ad8e6..34b368fa9d 100644 --- a/core/src/ten_runtime/binding/go/interface/ten/app.go +++ b/core/src/ten_runtime/binding/go/interface/ten/app.go @@ -12,6 +12,7 @@ import "C" import ( "fmt" + "log" "runtime" "unsafe" ) @@ -66,6 +67,10 @@ type App interface { } func (p *app) Run(runInBackground bool) { + if err := LoadAllAddons(nil); err != nil { + log.Fatalf("Failed to load all GO addons: %v", err) + } + C.ten_go_app_run(p.cPtr, C.bool(runInBackground)) } diff --git a/core/src/ten_runtime/binding/go/native/addon/addon.c b/core/src/ten_runtime/binding/go/native/addon/addon.c index 2a30751184..82801d57be 100644 --- a/core/src/ten_runtime/binding/go/native/addon/addon.c +++ b/core/src/ten_runtime/binding/go/native/addon/addon.c @@ -6,6 +6,7 @@ // #include "ten_runtime/binding/go/interface/ten/addon.h" +#include #include #include @@ -19,7 +20,6 @@ #include "include_internal/ten_runtime/ten_env/ten_env.h" #include "ten_runtime/addon/addon.h" #include "ten_runtime/addon/extension/extension.h" -#include "ten_runtime/addon/extension_group/extension_group.h" #include "ten_runtime/binding/common.h" #include "ten_runtime/binding/go/interface/ten/common.h" #include "ten_runtime/binding/go/interface/ten/ten_env.h" @@ -76,11 +76,6 @@ void ten_go_addon_unregister(uintptr_t bridge_addr) { ten_string_get_raw_str(&addon_bridge->addon_name)); break; - case TEN_ADDON_TYPE_EXTENSION_GROUP: - ten_addon_unregister_extension_group( - ten_string_get_raw_str(&addon_bridge->addon_name)); - break; - default: TEN_ASSERT(0, "Should not happen."); break; @@ -272,12 +267,6 @@ static ten_go_addon_t *ten_go_addon_register( ten_string_get_raw_str(&base_dir_str), &addon_bridge->c_addon); break; - case TEN_ADDON_TYPE_EXTENSION_GROUP: - ten_addon_register_extension_group( - ten_string_get_raw_str(&addon_bridge->addon_name), - ten_string_get_raw_str(&base_dir_str), &addon_bridge->c_addon); - break; - default: TEN_ASSERT(0, "Not support."); break; @@ -306,9 +295,10 @@ ten_go_error_t ten_go_addon_register_extension( return cgo_error; } -ten_go_error_t ten_go_addon_register_extension_group( +ten_go_error_t ten_go_addon_register_extension_v2( const void *addon_name, int addon_name_len, const void *base_dir, - int base_dir_len, uintptr_t go_addon, uintptr_t *bridge_addr) { + int base_dir_len, uintptr_t go_addon, uintptr_t *register_ctx, + uintptr_t *bridge_addr) { TEN_ASSERT(addon_name && addon_name_len > 0 && go_addon && bridge_addr, "Invalid argument."); @@ -317,7 +307,7 @@ ten_go_error_t ten_go_addon_register_extension_group( ten_go_addon_t *addon_bridge = ten_go_addon_register(addon_name, addon_name_len, base_dir, base_dir_len, - go_addon, TEN_ADDON_TYPE_EXTENSION_GROUP); + go_addon, TEN_ADDON_TYPE_EXTENSION); *bridge_addr = (uintptr_t)addon_bridge; diff --git a/core/src/ten_runtime/binding/python/interface/ten/addon.py b/core/src/ten_runtime/binding/python/interface/ten/addon.py index 83b643e58b..b056e59dcf 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/addon.py +++ b/core/src/ten_runtime/binding/python/interface/ten/addon.py @@ -4,180 +4,10 @@ # Licensed under the Apache License, Version 2.0, with certain conditions. # Refer to the "LICENSE" file in the root directory for more information. # -import json -import os -import importlib.util -from glob import glob -from typing import Callable, Any, Dict from libten_runtime_python import _Addon from .ten_env import TenEnv -class _AddonManager: - # Use the simple approach below, similar to a global array, to detect - # whether a Python module provides the registration function required by the - # TEN runtime. This avoids using `setattr` on the module, which may not be - # supported in advanced environments like Cython. The global array method - # is simple enough that it should work in all environments. - _registration_registry: Dict[str, Callable[[Any], None]] = {} - - @classmethod - def _load_all(cls, register_ctx: object): - base_dir = cls._find_app_base_dir() - - # Read manifest.json under base_dir. - manifest_path = os.path.join(base_dir, "manifest.json") - if not os.path.isfile(manifest_path): - raise FileNotFoundError("manifest.json not found in base_dir") - - with open(manifest_path, "r") as f: - manifest = json.load(f) - - # Note: The logic for loading extensions based on the `dependencies` - # specified in the app's `manifest.json` is currently implemented - # separately in both C and Python where addons need to be loaded. Since - # the logic is fairly simple, a standalone implementation is directly - # written at each required location. In the future, this could be - # consolidated into a unified implementation in C, which could then be - # reused across multiple languages. However, this would require handling - # cross-language information exchange, which may not necessarily be - # cost-effective. - - # Collect names of extensions from dependencies. - extension_names = [] - dependencies = manifest.get("dependencies", []) - for dep in dependencies: - if dep.get("type") == "extension": - extension_names.append(dep.get("name")) - - for module in glob(os.path.join(base_dir, "ten_packages/extension/*")): - if os.path.isdir(module): - module_name = os.path.basename(module) - - if module_name in extension_names: - cls._load_module( - module_full_name=( - f"ten_packages.extension.{module_name}" - ), - module_name=module_name, - register_ctx=register_ctx, - ) - else: - print(f"Skipping module: {module_name}") - - @classmethod - def _load_module( - cls, - module_full_name: str, - module_name: str, - register_ctx: object, - ): - """ - Helper method to load a module, check for the special function, - invoke it, and unload the module if the special function is missing. - """ - try: - spec = importlib.util.find_spec(module_full_name) - if spec is None: - raise ImportError(f"Cannot find module: {module_full_name}") - - _ = importlib.import_module(module_full_name) - print(f"Imported module: {module_name}") - - # Retrieve the registration function from the global registry - registration_func_name = _AddonManager._get_registration_func_name( - module_name - ) - - registration_func = _AddonManager._get_registration_func( - module_name - ) - - if registration_func: - try: - registration_func(register_ctx) - print(f"Successfully registered addon '{module_name}'") - except Exception as e: - print( - ( - "Error during registration of addon " - f"'{module_name}': {e}" - ) - ) - finally: - # Remove the registration function from the global registry. - if ( - registration_func_name - in _AddonManager._registration_registry - ): - del _AddonManager._registration_registry[ - registration_func_name - ] - print( - ( - "Removed registration function for addon " - f"'{registration_func_name}'" - ) - ) - else: - print(f"No {registration_func_name} found in {module_name}") - - except ImportError as e: - print(f"Error importing module {module_name}: {e}") - - @staticmethod - def _get_registration_func_name(addon_name: str) -> str: - return f"____ten_addon_{addon_name}_register____" - - @staticmethod - def _get_registration_func(addon_name: str) -> Callable[[Any], None] | None: - return _AddonManager._registration_registry.get( - _AddonManager._get_registration_func_name(addon_name) - ) - - @staticmethod - def _set_registration_func( - addon_name: str, - registration_func: Callable[[Any], None], - ) -> None: - registration_func_name = _AddonManager._get_registration_func_name( - addon_name - ) - - print( - ( - f"Injected registration function '{registration_func_name}' " - "into module '{module.__name__}'" - ) - ) - - _AddonManager._registration_registry[registration_func_name] = ( - registration_func - ) - - @staticmethod - def _find_app_base_dir(): - current_dir = os.path.dirname(__file__) - - while current_dir != os.path.dirname( - current_dir - ): # Stop at the root directory. - manifest_path = os.path.join(current_dir, "manifest.json") - if os.path.isfile(manifest_path): - with open(manifest_path, "r") as manifest_file: - try: - manifest_data = json.load(manifest_file) - if manifest_data.get("type") == "app": - return current_dir - except json.JSONDecodeError: - pass - current_dir = os.path.dirname(current_dir) - - raise FileNotFoundError( - "App base directory with a valid manifest.json not found." - ) - - class Addon(_Addon): def on_init(self, ten_env: TenEnv) -> None: ten_env.on_init_done() diff --git a/core/src/ten_runtime/binding/python/interface/ten/addon_manager.py b/core/src/ten_runtime/binding/python/interface/ten/addon_manager.py new file mode 100644 index 0000000000..045e10caad --- /dev/null +++ b/core/src/ten_runtime/binding/python/interface/ten/addon_manager.py @@ -0,0 +1,176 @@ +# +# Copyright © 2024 Agora +# This file is part of TEN Framework, an open source project. +# Licensed under the Apache License, Version 2.0, with certain conditions. +# Refer to the "LICENSE" file in the root directory for more information. +# +import json +import os +import importlib.util +from glob import glob +from typing import Callable, Any, Dict + + +class _AddonManager: + # Use the simple approach below, similar to a global array, to detect + # whether a Python module provides the registration function required by the + # TEN runtime. This avoids using `setattr` on the module, which may not be + # supported in advanced environments like Cython. The global array method + # is simple enough that it should work in all environments. + _registration_registry: Dict[str, Callable[[Any], None]] = {} + + @classmethod + def _load_all_addons(cls, register_ctx: object): + base_dir = cls._find_app_base_dir() + + # Read manifest.json under base_dir. + manifest_path = os.path.join(base_dir, "manifest.json") + if not os.path.isfile(manifest_path): + raise FileNotFoundError("manifest.json not found in base_dir") + + with open(manifest_path, "r") as f: + manifest = json.load(f) + + # Note: The logic for loading extensions based on the `dependencies` + # specified in the app's `manifest.json` is currently implemented + # separately in both C and Python where addons need to be loaded. Since + # the logic is fairly simple, a standalone implementation is directly + # written at each required location. In the future, this could be + # consolidated into a unified implementation in C, which could then be + # reused across multiple languages. However, this would require handling + # cross-language information exchange, which may not necessarily be + # cost-effective. + + # Collect names of extensions from dependencies. + extension_names = [] + dependencies = manifest.get("dependencies", []) + for dep in dependencies: + if dep.get("type") == "extension": + extension_names.append(dep.get("name")) + + for module in glob(os.path.join(base_dir, "ten_packages/extension/*")): + if os.path.isdir(module): + module_name = os.path.basename(module) + + if module_name in extension_names: + cls._load_module( + module_full_name=( + f"ten_packages.extension.{module_name}" + ), + module_name=module_name, + register_ctx=register_ctx, + ) + else: + print(f"Skipping module: {module_name}") + + @classmethod + def _load_module( + cls, + module_full_name: str, + module_name: str, + register_ctx: object, + ): + """ + Helper method to load a module, check for the special function, + invoke it, and unload the module if the special function is missing. + """ + try: + spec = importlib.util.find_spec(module_full_name) + if spec is None: + raise ImportError(f"Cannot find module: {module_full_name}") + + _ = importlib.import_module(module_full_name) + print(f"Imported module: {module_name}") + + # Retrieve the registration function from the global registry + registration_func_name = _AddonManager._get_registration_func_name( + module_name + ) + + registration_func = _AddonManager._get_registration_func( + module_name + ) + + if registration_func: + try: + registration_func(register_ctx) + print(f"Successfully registered addon '{module_name}'") + except Exception as e: + print( + ( + "Error during registration of addon " + f"'{module_name}': {e}" + ) + ) + finally: + # Remove the registration function from the global registry. + if ( + registration_func_name + in _AddonManager._registration_registry + ): + del _AddonManager._registration_registry[ + registration_func_name + ] + print( + ( + "Removed registration function for addon " + f"'{registration_func_name}'" + ) + ) + else: + print(f"No {registration_func_name} found in {module_name}") + + except ImportError as e: + print(f"Error importing module {module_name}: {e}") + + @staticmethod + def _get_registration_func_name(addon_name: str) -> str: + return f"____ten_addon_{addon_name}_register____" + + @staticmethod + def _get_registration_func(addon_name: str) -> Callable[[Any], None] | None: + return _AddonManager._registration_registry.get( + _AddonManager._get_registration_func_name(addon_name) + ) + + @staticmethod + def _set_registration_func( + addon_name: str, + registration_func: Callable[[Any], None], + ) -> None: + registration_func_name = _AddonManager._get_registration_func_name( + addon_name + ) + + print( + ( + f"Injected registration function '{registration_func_name}' " + "into module '{module.__name__}'" + ) + ) + + _AddonManager._registration_registry[registration_func_name] = ( + registration_func + ) + + @staticmethod + def _find_app_base_dir(): + current_dir = os.path.dirname(__file__) + + while current_dir != os.path.dirname( + current_dir + ): # Stop at the root directory. + manifest_path = os.path.join(current_dir, "manifest.json") + if os.path.isfile(manifest_path): + with open(manifest_path, "r") as manifest_file: + try: + manifest_data = json.load(manifest_file) + if manifest_data.get("type") == "app": + return current_dir + except json.JSONDecodeError: + pass + current_dir = os.path.dirname(current_dir) + + raise FileNotFoundError( + "App base directory with a valid manifest.json not found." + ) diff --git a/core/src/ten_runtime/binding/python/interface/ten/decorator.py b/core/src/ten_runtime/binding/python/interface/ten/decorator.py index 6bc8574e7b..80a9863976 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/decorator.py +++ b/core/src/ten_runtime/binding/python/interface/ten/decorator.py @@ -7,7 +7,8 @@ import os import sys from typing import Type -from .addon import _AddonManager, Addon +from .addon import Addon +from .addon_manager import _AddonManager from libten_runtime_python import ( _register_addon_as_extension, _register_addon_as_extension_v2, diff --git a/core/src/ten_runtime/binding/python/interface/ten/ten_env.py b/core/src/ten_runtime/binding/python/interface/ten/ten_env.py index 95ef78716a..ebae7038ea 100644 --- a/core/src/ten_runtime/binding/python/interface/ten/ten_env.py +++ b/core/src/ten_runtime/binding/python/interface/ten/ten_env.py @@ -38,7 +38,7 @@ def _on_release(self) -> None: self._release_handler() def on_configure_done(self) -> None: - from .addon import _AddonManager + from .addon_manager import _AddonManager if self._internal._attach_to == _TenEnvAttachTo.APP: # Load all python addons when app on_configure_done. @@ -49,7 +49,7 @@ def on_configure_done(self) -> None: # simply passing `None` is sufficient. If needed in the future, we # can consider what information should be passed to the registration # function of the Python addon. - _AddonManager._load_all(None) + _AddonManager._load_all_addons(None) return self._internal.on_configure_done() def on_init_done(self) -> None: diff --git a/core/src/ten_runtime/binding/python/native/addon/addon.c b/core/src/ten_runtime/binding/python/native/addon/addon.c index 30f6993f33..b24eb68fe1 100644 --- a/core/src/ten_runtime/binding/python/native/addon/addon.c +++ b/core/src/ten_runtime/binding/python/native/addon/addon.c @@ -15,7 +15,6 @@ #include "include_internal/ten_runtime/extension/extension.h" #include "include_internal/ten_runtime/extension_group/extension_group.h" #include "ten_runtime/addon/extension/extension.h" -#include "ten_runtime/addon/extension_group/extension_group.h" #include "ten_runtime/binding/common.h" #include "ten_runtime/ten_env/internal/on_xxx_done.h" #include "ten_runtime/ten_env/ten_env.h" diff --git a/core/src/ten_runtime/binding/python/native/addon/decorator.c b/core/src/ten_runtime/binding/python/native/addon/decorator.c index 27068cf8ab..f202b1f149 100644 --- a/core/src/ten_runtime/binding/python/native/addon/decorator.c +++ b/core/src/ten_runtime/binding/python/native/addon/decorator.c @@ -9,7 +9,6 @@ #include "include_internal/ten_runtime/binding/python/addon/addon.h" #include "include_internal/ten_runtime/binding/python/common/error.h" #include "ten_runtime/addon/extension/extension.h" -#include "ten_runtime/addon/extension_group/extension_group.h" #include "ten_utils/lib/string.h" #include "ten_utils/macro/mark.h" diff --git a/core/src/ten_runtime/extension_group/builtin/builtin_extension_group.c b/core/src/ten_runtime/extension_group/builtin/builtin_extension_group.c index 1b39c0e392..86b6969932 100644 --- a/core/src/ten_runtime/extension_group/builtin/builtin_extension_group.c +++ b/core/src/ten_runtime/extension_group/builtin/builtin_extension_group.c @@ -8,13 +8,13 @@ #include #include "include_internal/ten_runtime/addon/addon.h" +#include "include_internal/ten_runtime/addon/extension_group/extension_group.h" #include "include_internal/ten_runtime/common/constant_str.h" #include "include_internal/ten_runtime/extension/extension_addon_and_instance_name_pair.h" #include "include_internal/ten_runtime/extension_group/extension_group.h" #include "include_internal/ten_runtime/ten_env/metadata.h" #include "include_internal/ten_runtime/ten_env/ten_env.h" #include "ten_runtime/addon/addon.h" -#include "ten_runtime/addon/extension_group/extension_group.h" #include "ten_runtime/extension_group/extension_group.h" #include "ten_runtime/ten.h" #include "ten_runtime/ten_env/internal/log.h" diff --git a/core/src/ten_runtime/test/test_extension.c b/core/src/ten_runtime/test/test_extension.c index 38e0762e39..48afac4186 100644 --- a/core/src/ten_runtime/test/test_extension.c +++ b/core/src/ten_runtime/test/test_extension.c @@ -8,6 +8,7 @@ #include #include "include_internal/ten_runtime/addon/addon.h" +#include "include_internal/ten_runtime/addon/extension_group/extension_group.h" #include "include_internal/ten_runtime/common/constant_str.h" #include "include_internal/ten_runtime/extension/extension.h" #include "include_internal/ten_runtime/extension_group/extension_group.h" @@ -15,7 +16,6 @@ #include "include_internal/ten_runtime/test/env_tester.h" #include "include_internal/ten_runtime/test/extension_tester.h" #include "ten_runtime/addon/addon.h" -#include "ten_runtime/addon/extension_group/extension_group.h" #include "ten_runtime/extension_group/extension_group.h" #include "ten_runtime/ten.h" #include "ten_runtime/ten_env/internal/log.h" diff --git a/tests/ten_runtime/integration/go/access_property_go/access_property_go_app/ten_packages/extension/extension_a/extension.go b/tests/ten_runtime/integration/go/access_property_go/access_property_go_app/ten_packages/extension/extension_a/extension.go index ec317a82e6..8d6a44eba0 100644 --- a/tests/ten_runtime/integration/go/access_property_go/access_property_go_app/ten_packages/extension/extension_a/extension.go +++ b/tests/ten_runtime/integration/go/access_property_go/access_property_go_app/ten_packages/extension/extension_a/extension.go @@ -217,7 +217,7 @@ func (p *aExtension) OnCmd( func init() { // Register addon - err := ten.RegisterAddonAsExtension( + err := ten.RegisterAddonAsExtensionV2( "extension_a", ten.NewDefaultExtensionAddon(newAExtension), )