1/*
2 * Copyright (C) 2024 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16#include "SceneJS.h"
17#include "LightJS.h"
18#include "MaterialJS.h"
19static constexpr BASE_NS::Uid IO_QUEUE { "be88e9a0-9cd8-45ab-be48-937953dc258f" };
20#include <meta/api/make_callback.h>
21#include <meta/interface/intf_task_queue.h>
22#include <meta/interface/intf_task_queue_registry.h>
23#include <meta/interface/property/property_events.h>
24#include <scene_plugin/api/camera.h> //for the classid...
25#include <scene_plugin/api/environment_uid.h>
26#include <scene_plugin/api/material_uid.h>
27#include <scene_plugin/api/render_configuration_uid.h>
28#include <scene_plugin/api/scene_uid.h>
29#include <scene_plugin/interface/intf_ecs_scene.h>
30#include <scene_plugin/interface/intf_material.h>
31#include <scene_plugin/interface/intf_render_configuration.h>
32#include <scene_plugin/interface/intf_scene.h>
33
34#include <core/image/intf_image_loader_manager.h>
35#include <core/intf_engine.h>
36#include <render/device/intf_gpu_resource_manager.h>
37#include <render/intf_render_context.h>
38
39#ifdef __SCENE_ADAPTER__
40#include "3d_widget_adapter_log.h"
41#include "scene_adapter/scene_adapter.h"
42#endif
43
44using IntfPtr = BASE_NS::shared_ptr<CORE_NS::IInterface>;
45using IntfWeakPtr = BASE_NS::weak_ptr<CORE_NS::IInterface>;
46
47static META_NS::ITaskQueue::Ptr releaseThread;
48static constexpr BASE_NS::Uid JS_RELEASE_THREAD { "3784fa96-b25b-4e9c-bbf1-e897d36f73af" };
49
50void SceneJS::Init(napi_env env, napi_value exports)
51{
52    using namespace NapiApi;
53    // clang-format off
54    auto loadFun = [](napi_env e,napi_callback_info cb) -> napi_value
55        {
56            FunctionContext<> fc(e,cb);
57            return SceneJS::Load(fc);
58        };
59
60    napi_property_descriptor props[] = {
61        // static methods
62        napi_property_descriptor{ "load", nullptr, loadFun, nullptr, nullptr, nullptr, (napi_property_attributes)(napi_static|napi_default_method)},
63        // properties
64        GetSetProperty<NapiApi::Object, SceneJS, &SceneJS::GetEnvironment, &SceneJS::SetEnvironment>("environment"),
65        GetProperty<NapiApi::Array,SceneJS, &SceneJS::GetAnimations>("animations"),
66        // animations
67        GetProperty<BASE_NS::string, SceneJS, &SceneJS::GetRoot>("root"),
68        // scene methods
69        Method<NapiApi::FunctionContext<BASE_NS::string>, SceneJS, &SceneJS::GetNode>("getNodeByPath"),
70        Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::GetResourceFactory>("getResourceFactory"),
71        Method<NapiApi::FunctionContext<>, SceneJS, &SceneJS::Dispose>("destroy"),
72
73        // SceneResourceFactory methods
74        Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateCamera>("createCamera"),
75        Method<NapiApi::FunctionContext<NapiApi::Object,uint32_t>, SceneJS, &SceneJS::CreateLight>("createLight"),
76        Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateNode>("createNode"),
77        Method<NapiApi::FunctionContext<NapiApi::Object,uint32_t>, SceneJS, &SceneJS::CreateMaterial>("createMaterial"),
78        Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateShader>("createShader"),
79        Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateImage>("createImage"),
80        Method<NapiApi::FunctionContext<NapiApi::Object>, SceneJS, &SceneJS::CreateEnvironment>("createEnvironment")
81    };
82    // clang-format on
83
84    napi_value func;
85    auto status = napi_define_class(env, "Scene", NAPI_AUTO_LENGTH, BaseObject::ctor<SceneJS>(), nullptr,
86        sizeof(props) / sizeof(props[0]), props, &func);
87
88    napi_set_named_property(env, exports, "Scene", func);
89
90    NapiApi::MyInstanceState* mis;
91    napi_get_instance_data(env, (void**)&mis);
92    mis->StoreCtor("Scene", func);
93}
94class AsyncStateBase {
95public:
96    virtual ~AsyncStateBase()
97    {
98        // assert that the promise has been fulfilled.
99        CORE_ASSERT(deferred == nullptr);
100        // assert that the threadsafe func has been released.
101        CORE_ASSERT(termfun == nullptr);
102    }
103
104    // inherit from this
105    napi_deferred deferred { nullptr };
106    napi_threadsafe_function termfun { nullptr };
107    napi_value result { nullptr };
108    virtual bool finally(napi_env env) = 0;
109    template<typename A>
110    A Flip(A& a)
111    {
112        A tmp = a;
113        a = nullptr;
114        return tmp;
115    }
116    void CallIt()
117    {
118        // should be called from engine thread only.
119        // use an extra task in engine to trigger this
120        // to woraround an issue wherer CallIt is called IN an eventhandler.
121        // as there seems to be cases where (uncommon, have no repro. but has happend)
122        // napi_release_function waits for threadsafe function completion
123        // and the "js function" is waiting for the enghen thread (which is blocked releasing the function)
124        if (auto tf = Flip(termfun)) {
125            META_NS::GetTaskQueueRegistry()
126                .GetTaskQueue(JS_RELEASE_THREAD)
127                ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move([tf]() {
128                    napi_call_threadsafe_function(tf, nullptr, napi_threadsafe_function_call_mode::napi_tsfn_blocking);
129                    napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
130                    return false;
131                })));
132        }
133    }
134    // callable from js thread
135    void Fail(napi_env env)
136    {
137        napi_status status;
138        if (auto df = Flip(deferred)) {
139            status = napi_reject_deferred(env, df, result);
140        }
141        if (auto tf = Flip(termfun)) {
142            // if called from java thread. then release it here...
143            napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
144        }
145    }
146    void Success(napi_env env)
147    {
148        napi_status status;
149        // return success
150        if (auto df = Flip(deferred)) {
151            status = napi_resolve_deferred(env, df, result);
152        }
153        if (auto tf = Flip(termfun)) {
154            // if called from java thread. then release it here...
155            napi_release_threadsafe_function(tf, napi_threadsafe_function_release_mode::napi_tsfn_release);
156        }
157    }
158};
159napi_value MakePromise(napi_env env, AsyncStateBase* data)
160{
161    napi_value promise;
162    napi_status status = napi_create_promise(env, &data->deferred, &promise);
163    napi_value name;
164    napi_create_string_latin1(env, "a", 1, &name);
165    status = napi_create_threadsafe_function(
166        env, nullptr, nullptr, name, 1, 1, data /*finalize_data*/,
167        [](napi_env env, void* finalize_data, void* finalize_hint) {
168            AsyncStateBase* data = (AsyncStateBase*)finalize_data;
169            delete data;
170        },
171        data /*context*/,
172        [](napi_env env, napi_value js_callback, void* context, void* inData) {
173            // IN JS THREAD. (so careful with the calls to engine)
174            napi_status status;
175            AsyncStateBase* data = (AsyncStateBase*)context;
176            status = napi_get_undefined(env, &data->result);
177            if (data->finally(env)) {
178                data->Success(env);
179            } else {
180                data->Fail(env);
181            }
182        },
183        &data->termfun);
184    return promise;
185}
186BASE_NS::string FetchResourceOrUri(napi_env e, napi_value arg)
187{
188    napi_valuetype type;
189    napi_typeof(e, arg, &type);
190    if (type == napi_string) {
191        BASE_NS::string uri = NapiApi::Value<BASE_NS::string>(e, arg);
192        // set default format as system resource
193        uri.insert(0, "file://");
194        return uri;
195    }
196    if (type == napi_object) {
197        NapiApi::Object resource(e, arg);
198        uint32_t id = resource.Get<uint32_t>("id");
199        uint32_t type = resource.Get<uint32_t>("type");
200        NapiApi::Array parms = resource.Get<NapiApi::Array>("params");
201        BASE_NS::string uri;
202        if ((id == 0) && (type == 30000)) {
203            // seems like a correct rawfile.
204            uri = parms.Get<BASE_NS::string>(0);
205        }
206        if (!uri.empty()) {
207            // add the schema then
208            uri.insert(0, "OhosRawFile://");
209        }
210        return uri;
211    }
212    return "";
213}
214
215BASE_NS::string FetchResourceOrUri(NapiApi::FunctionContext<>& ctx)
216{
217    BASE_NS::string uri;
218    NapiApi::FunctionContext<NapiApi::Object> resourceContext(ctx);
219    NapiApi::FunctionContext<BASE_NS::string> uriContext(ctx);
220
221    if (uriContext) {
222        // actually not supported anymore.
223        uri = uriContext.Arg<0>();
224        // check if there is a protocol
225        auto t = uri.find("://");
226        if (t == BASE_NS::string::npos) {
227            // no proto . so use default
228            // set default format as system resource
229            uri.insert(0, "file://");
230        }
231    } else if (resourceContext) {
232        // get it from resource then
233        NapiApi::Object resource = resourceContext.Arg<0>();
234        uint32_t id = resource.Get<uint32_t>("id");
235        uint32_t type = resource.Get<uint32_t>("type");
236        NapiApi::Array parms = resource.Get<NapiApi::Array>("params");
237        if ((id == 0) && (type == 30000)) { // 30000 : type
238            // seems like a correct rawfile.
239            uri = parms.Get<BASE_NS::string>(0);
240        }
241        if (!uri.empty()) {
242            // add the schema then
243            uri.insert(0, "OhosRawFile://");
244        }
245    }
246    return uri;
247}
248
249napi_value SceneJS::Load(NapiApi::FunctionContext<>& ctx)
250{
251    BASE_NS::string uri = FetchResourceOrUri(ctx);
252
253    if (uri.empty()) {
254        // unsupported input..
255        return {};
256    }
257    // make sure slashes are correct.. *eh*
258    for (;;) {
259        auto t = uri.find_first_of('\\');
260        if (t == BASE_NS::string::npos) {
261            break;
262        }
263        uri[t] = '/';
264    }
265
266    auto &tr = META_NS::GetTaskQueueRegistry();
267    releaseThread = META_NS::GetTaskQueueRegistry().GetTaskQueue(JS_RELEASE_THREAD);
268    if (!releaseThread) {
269        auto &obr = META_NS::GetObjectRegistry();
270        releaseThread = obr.Create<META_NS::ITaskQueue>(META_NS::ClassId::ThreadedTaskQueue);
271        tr.RegisterTaskQueue(releaseThread, JS_RELEASE_THREAD);
272    }
273
274    struct AsyncState : public AsyncStateBase {
275        BASE_NS::string uri;
276        SCENE_NS::IScene::Ptr scene;
277        META_NS::IEvent::Token onLoadedToken { 0 };
278        bool finally(napi_env env) override
279        {
280            if (scene) {
281                auto obj = interface_pointer_cast<META_NS::IObject>(scene);
282                result = CreateJsObj(env, "Scene", obj, true, 0, nullptr);
283                // link the native object to js object.
284                StoreJsObj(obj, { env, result });
285
286                NapiApi::Object me(env, result);
287                auto curenv = me.Get<NapiApi::Object>("environment");
288                if (!curenv) {
289                    // setup default env
290                    NapiApi::Object argsIn(env);
291                    argsIn.Set("name", "DefaultEnv");
292
293                    auto* tro = (SceneJS*)(me.Native<TrueRootObject>());
294                    auto res = tro->CreateEnvironment(me, argsIn);
295                    res.Set("backgroundType", NapiApi::Value<uint32_t>(env, 1)); // image.. but with null.
296                    me.Set("environment", res);
297                }
298
299#ifdef __SCENE_ADAPTER__
300                // set SceneAdapter
301                auto oo = GetRootObject(env, result);
302
303                auto sceneAdapter = std::make_shared<OHOS::Render3D::SceneAdapter>();
304                sceneAdapter->SetSceneObj(oo->GetNativeObject());
305                auto sceneJs = static_cast<SceneJS*>(oo);
306                sceneJs->scene_ = sceneAdapter;
307#endif
308                return true;
309            }
310            scene.reset();
311            return false;
312        };
313    };
314    AsyncState* data = new AsyncState();
315    data->uri = uri;
316
317    auto fun = [data]() {
318        // IN ENGINE THREAD! (so no js calls)
319        using namespace SCENE_NS;
320        auto& obr = META_NS::GetObjectRegistry();
321        auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
322        auto scene = interface_pointer_cast<SCENE_NS::IScene>(obr.Create(SCENE_NS::ClassId::Scene, params));
323        if (!scene) {
324            // insta fail
325            data->CallIt();
326            return false;
327        }
328        data->scene = scene;
329
330        auto onLoaded = META_NS::MakeCallback<META_NS::IOnChanged>([data]() {
331            bool complete = false;
332            auto scene = data->scene;
333            auto status = scene->Status()->GetValue();
334            if (status == SCENE_NS::IScene::SCENE_STATUS_READY) {
335                // still in engine thread..
336                complete = true;
337            } else if (status == SCENE_NS::IScene::SCENE_STATUS_LOADING_FAILED) {
338                data->scene.reset(); // make sure we don't have anything in result if error.
339                complete = true;
340            }
341
342            if (complete) {
343                scene->Status()->OnChanged()->RemoveHandler(data->onLoadedToken);
344                data->onLoadedToken = 0;
345                if (scene) {
346                    auto& obr = META_NS::GetObjectRegistry();
347                    // make sure we have renderconfig
348                    auto rc = scene->RenderConfiguration()->GetValue();
349                    if (!rc) {
350                        // Create renderconfig
351                        rc = obr.Create<SCENE_NS::IRenderConfiguration>(SCENE_NS::ClassId::RenderConfiguration);
352                        scene->RenderConfiguration()->SetValue(rc);
353                    }
354                    /*if (auto env = rc->Environment()->GetValue(); !env) {
355                        // create default env then
356                        env = scene->CreateNode<SCENE_NS::IEnvironment>("default_env");
357                        env->Background()->SetValue(SCENE_NS::IEnvironment::CUBEMAP);
358                        rc->Environment()->SetValue(env);
359                    }*/
360
361                    interface_cast<IEcsScene>(scene)->RenderMode()->SetValue(IEcsScene::RenderMode::RENDER_ALWAYS);
362                    auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
363                    auto duh = params->GetArrayPropertyByName<IntfWeakPtr>("Scenes");
364                    duh->AddValue(interface_pointer_cast<CORE_NS::IInterface>(scene));
365                }
366                // call the threadsafe func here. (let the javascript know we are done)
367                data->CallIt();
368            }
369        });
370        data->onLoadedToken = scene->Status()->OnChanged()->AddHandler(onLoaded);
371        scene->Asynchronous()->SetValue(false);
372        scene->Uri()->SetValue(data->uri);
373        return false;
374    };
375    // Should it be possible to cancel? (ie. do we need to store the token for something..)
376    META_NS::GetTaskQueueRegistry()
377        .GetTaskQueue(ENGINE_THREAD)
378        ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
379
380    return MakePromise(ctx, data);
381}
382
383napi_value SceneJS::Dispose(NapiApi::FunctionContext<>& ctx)
384{
385    LOG_F("SCENE_JS::Dispose");
386    DisposeNative();
387    return {};
388}
389void SceneJS::DisposeNative()
390{
391    LOG_F("SCENE_JS::DisposeNative");
392    // dispose
393    while (!disposables_.empty()) {
394        auto env = disposables_.begin()->second.GetObject();
395        if (env) {
396            NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
397            if (func) {
398                func.Invoke(env);
399            }
400        }
401    }
402
403    // dispose all cameras/env/etcs.
404    while (!strongDisposables_.empty()) {
405        auto it = strongDisposables_.begin();
406        auto token = it->first;
407        auto env = it->second.GetObject();
408        if (env) {
409            NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
410            if (func) {
411                func.Invoke(env);
412            }
413        }
414    }
415    if (auto env = environmentJS_.GetObject()) {
416        NapiApi::Function func = env.Get<NapiApi::Function>("destroy");
417        if (func) {
418            func.Invoke(env);
419        }
420    }
421    environmentJS_.Reset();
422    for (auto b : bitmaps_) {
423        b.second.reset();
424    }
425    if (auto scene = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject())) {
426        // reset the native object refs
427        SetNativeObject(nullptr, false);
428        SetNativeObject(nullptr, true);
429
430        ExecSyncTask([scn = BASE_NS::move(scene)]() {
431            auto r = scn->RenderConfiguration()->GetValue();
432            auto e = r->Environment()->GetValue();
433            e.reset();
434            r.reset();
435            scn->RenderConfiguration()->SetValue(nullptr);
436            return META_NS::IAny::Ptr {};
437        });
438    }
439}
440void* SceneJS::GetInstanceImpl(uint32_t id)
441{
442    if (id == SceneJS::ID) {
443        return this;
444    }
445    return nullptr;
446}
447void SceneJS::Finalize(napi_env env)
448{
449    // hmm.. do i need to do something BEFORE the object gets deleted..
450    BaseObject<SceneJS>::Finalize(env);
451}
452
453SceneJS::SceneJS(napi_env e, napi_callback_info i) : BaseObject<SceneJS>(e, i)
454{
455    LOG_F("SceneJS ++");
456    NapiApi::FunctionContext<NapiApi::Object> fromJs(e, i);
457
458    if (!fromJs) {
459        // okay internal create. we will receive the object after.
460        return;
461    }
462
463    // Construct native object
464    SCENE_NS::IScene::Ptr scene;
465    ExecSyncTask([&scene]() {
466        using namespace SCENE_NS;
467        auto& obr = META_NS::GetObjectRegistry();
468        auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
469        scene = interface_pointer_cast<SCENE_NS::IScene>(obr.Create(SCENE_NS::ClassId::Scene, params));
470        if (scene) {
471            // only asynch false works.. (otherwise nodes are in random state when scene is ready.. )
472            scene->Asynchronous()->SetValue(false);
473            interface_cast<IEcsScene>(scene)->RenderMode()->SetValue(IEcsScene::RenderMode::RENDER_ALWAYS);
474            auto duh = params->GetArrayPropertyByName<IntfWeakPtr>("Scenes");
475            duh->AddValue(interface_pointer_cast<CORE_NS::IInterface>(scene));
476        }
477        return META_NS::IAny::Ptr {};
478    });
479
480    // process constructor args..
481    NapiApi::Object meJs(e, fromJs.This());
482    SetNativeObject(interface_pointer_cast<META_NS::IObject>(scene), true /* KEEP STRONG REF */);
483    StoreJsObj(interface_pointer_cast<META_NS::IObject>(scene), meJs);
484
485    NapiApi::Object args = fromJs.Arg<0>();
486    if (args) {
487        if (auto name = args.Get("name")) {
488            meJs.Set("name", name);
489        }
490        if (auto uri = args.Get("uri")) {
491            meJs.Set("uri", uri);
492        }
493    }
494}
495
496SceneJS::~SceneJS()
497{
498    LOG_F("SceneJS --");
499    if (!GetNativeObject()) {
500        return;
501    }
502    ExecSyncTask([scene = GetNativeObject()]() {
503        auto& obr = META_NS::GetObjectRegistry();
504        auto params = interface_pointer_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
505        auto duh = params->GetArrayPropertyByName<IntfWeakPtr>("Scenes");
506        if (duh) {
507            for (auto i = 0; i < duh->GetSize();) {
508                auto w = duh->GetValueAt(i);
509                if (w.lock() == nullptr) {
510                    duh->RemoveAt(i);
511                } else {
512                    i++;
513                }
514            }
515        }
516        return META_NS::IAny::Ptr {};
517    });
518}
519
520napi_value SceneJS::GetNode(NapiApi::FunctionContext<BASE_NS::string>& ctx)
521{
522    // verify that path starts from "correct root" and then let the root node handle the rest.
523    NapiApi::Object meJs(ctx, ctx.This());
524    NapiApi::Object root = meJs.Get<NapiApi::Object>("root");
525    BASE_NS::string rootName = root.Get<BASE_NS::string>("name");
526    NapiApi::Function func = root.Get<NapiApi::Function>("getNodeByPath");
527    BASE_NS::string path = ctx.Arg<0>();
528
529    // remove the "root nodes name" (make sure it also matches though..)
530    auto pos = path.find('/', 0);
531    BASE_NS::string_view step = path.substr(0, pos);
532    if (step != rootName) {
533        // root not matching
534        return ctx.GetNull();
535    }
536
537    if (pos != BASE_NS::string_view::npos) {
538        BASE_NS::string rest(path.substr(pos + 1));
539        napi_value newpath = nullptr;
540        napi_status status = napi_create_string_utf8(ctx, rest.c_str(), rest.length(), &newpath);
541        if (newpath) {
542            return func.Invoke(root, 1, &newpath);
543        }
544        return ctx.GetNull();
545    }
546    return root;
547}
548napi_value SceneJS::GetRoot(NapiApi::FunctionContext<>& ctx)
549{
550    if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
551        SCENE_NS::INode::Ptr root;
552        auto cb = META_NS::MakeCallback<META_NS::ITaskQueueWaitableTask>([scene, &root]() {
553            root = scene->RootNode()->GetValue();
554            if (root) {
555                // make sure our direct descendants exist.
556                root->BuildChildren(SCENE_NS::INode::BuildBehavior::NODE_BUILD_ONLY_DIRECT_CHILDREN);
557            }
558            return META_NS::IAny::Ptr {};
559        });
560        META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD)->AddWaitableTask(cb)->Wait();
561        auto obj = interface_pointer_cast<META_NS::IObject>(root);
562
563        if (auto cached = FetchJsObj(obj)) {
564            // always return the same js object.
565            return cached;
566        }
567
568        NapiApi::StrongRef sceneRef { ctx, ctx.This() };
569        if (!GetNativeMeta<SCENE_NS::IScene>(sceneRef.GetObject())) {
570            CORE_LOG_F("INVALID SCENE!");
571        }
572
573        NapiApi::Object argJS(ctx);
574        napi_value args[] = { sceneRef.GetObject(), argJS };
575
576        return CreateFromNativeInstance(ctx, obj, false /*these are owned by the scene*/, BASE_NS::countof(args), args);
577    }
578    return ctx.GetUndefined();
579}
580
581napi_value SceneJS::GetEnvironment(NapiApi::FunctionContext<>& ctx)
582{
583    if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
584        SCENE_NS::IEnvironment::Ptr environment;
585        ExecSyncTask([scene, &environment]() {
586            auto rc = scene->RenderConfiguration()->GetValue();
587            if (rc) {
588                environment = rc->Environment()->GetValue();
589            }
590            return META_NS::IAny::Ptr {};
591        });
592        if (environment) {
593            auto obj = interface_pointer_cast<META_NS::IObject>(environment);
594            if (auto cached = FetchJsObj(obj)) {
595                // always return the same js object.
596                return cached;
597            }
598
599            NapiApi::StrongRef sceneRef { ctx, ctx.This() };
600            if (!GetNativeMeta<SCENE_NS::IScene>(sceneRef.GetObject())) {
601                CORE_LOG_F("INVALID SCENE!");
602            }
603
604            NapiApi::Object argJS(ctx);
605            napi_value args[] = { sceneRef.GetObject(), argJS };
606
607            environmentJS_ = { ctx, CreateFromNativeInstance(ctx, obj, false, BASE_NS::countof(args), args) };
608            return environmentJS_.GetValue();
609        }
610    }
611    return ctx.GetNull();
612}
613
614void SceneJS::SetEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
615{
616    NapiApi::Object env = ctx.Arg<0>();
617
618    if (auto currentlySet = environmentJS_.GetObject()) {
619        if ((napi_value)currentlySet == (napi_value)env) {
620            // setting the exactly the same environment. do nothing.
621            return;
622        }
623    }
624    environmentJS_.Reset();
625    if (env) {
626        environmentJS_ = { env };
627    }
628    SCENE_NS::IEnvironment::Ptr environment;
629    if (env) {
630        environment = GetNativeMeta<SCENE_NS::IEnvironment>(env);
631    }
632    if (auto scene = interface_cast<SCENE_NS::IScene>(GetNativeObject())) {
633        ExecSyncTask([scene, environment]() {
634            auto rc = scene->RenderConfiguration()->GetValue();
635            if (!rc) {
636                // no render config, so create it.
637                auto& obr = META_NS::GetObjectRegistry();
638                rc = obr.Create<SCENE_NS::IRenderConfiguration>(SCENE_NS::ClassId::RenderConfiguration);
639                scene->RenderConfiguration()->SetValue(rc);
640            }
641            if (rc) {
642                rc->Environment()->SetValue(environment);
643            }
644            return META_NS::IAny::Ptr {};
645        });
646    }
647}
648
649// resource factory
650
651napi_value SceneJS::GetResourceFactory(NapiApi::FunctionContext<>& ctx)
652{
653    // just return this. as scene is the factory also.
654    return ctx.This();
655}
656NapiApi::Object SceneJS::CreateEnvironment(NapiApi::Object scene, NapiApi::Object argsIn)
657{
658    napi_env env = scene.GetEnv();
659    napi_value args[] = { scene, argsIn };
660    auto result = NapiApi::Object(GetJSConstructor(env, "Environment"), BASE_NS::countof(args), args);
661    auto ref = NapiApi::StrongRef { env, result };
662    return { env, ref.GetValue() };
663}
664napi_value SceneJS::CreateEnvironment(NapiApi::FunctionContext<NapiApi::Object>& ctx)
665{
666    struct AsyncState : public AsyncStateBase {
667        NapiApi::StrongRef this_;
668        NapiApi::StrongRef args_;
669        bool finally(napi_env env) override
670        {
671            auto* tro = (SceneJS*)(this_.GetObject().Native<TrueRootObject>());
672            result = tro->CreateEnvironment(this_.GetObject(), args_.GetObject());
673            return true;
674        };
675    };
676    AsyncState* data = new AsyncState();
677    data->this_ = NapiApi::StrongRef(ctx, ctx.This());
678    data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
679    napi_value result = MakePromise(ctx, data);
680    // and just instantly complete it
681    data->finally(ctx);
682    data->Success(ctx);
683    return result;
684}
685
686napi_value SceneJS::CreateCamera(NapiApi::FunctionContext<NapiApi::Object>& ctx)
687{
688    struct AsyncState : public AsyncStateBase {
689        NapiApi::StrongRef this_;
690        NapiApi::StrongRef args_;
691        bool finally(napi_env env) override
692        {
693            napi_value args[] = {
694                this_.GetValue(), // scene..
695                args_.GetValue()  // params.
696            };
697            result = NapiApi::Object(GetJSConstructor(env, "Camera"), BASE_NS::countof(args), args);
698            return true;
699        };
700    };
701    AsyncState* data = new AsyncState();
702    data->this_ = NapiApi::StrongRef(ctx, ctx.This());
703    data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
704    napi_value result = MakePromise(ctx, data);
705    // and just instantly complete it
706    data->finally(ctx);
707    data->Success(ctx);
708    return result;
709}
710
711napi_value SceneJS::CreateLight(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
712{
713    struct AsyncState : public AsyncStateBase {
714        NapiApi::StrongRef this_;
715        NapiApi::StrongRef args_;
716        uint32_t lightType_;
717        bool finally(napi_env env) override
718        {
719            napi_value args[] = {
720                this_.GetValue(), // scene..
721                args_.GetValue()  // params.
722            };
723            NapiApi::Function func;
724            switch (lightType_) {
725                case BaseLight::DIRECTIONAL: {
726                    func = GetJSConstructor(env, "DirectionalLight");
727                    break;
728                }
729                case BaseLight::POINT: {
730                    func = GetJSConstructor(env, "PointLight");
731                    break;
732                }
733                case BaseLight::SPOT: {
734                    func = GetJSConstructor(env, "SpotLight");
735                    break;
736                }
737                default:
738                    break;
739            }
740            if (!func) {
741                return false;
742            }
743            result = NapiApi::Object(func, BASE_NS::countof(args), args);
744            return true;
745        };
746    };
747    AsyncState* data = new AsyncState();
748    data->this_ = NapiApi::StrongRef(ctx, ctx.This());
749    data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
750    data->lightType_ = ctx.Arg<1>();
751    napi_value result = MakePromise(ctx, data);
752    // and just instantly complete it
753    data->finally(ctx);
754    data->Success(ctx);
755    return result;
756}
757
758napi_value SceneJS::CreateNode(NapiApi::FunctionContext<NapiApi::Object>& ctx)
759{
760    struct AsyncState : public AsyncStateBase {
761        NapiApi::StrongRef this_;
762        NapiApi::StrongRef args_;
763        bool finally(napi_env env) override
764        {
765            napi_value args[] = {
766                this_.GetValue(), // scene..
767                args_.GetValue()  // params.
768            };
769            result = NapiApi::Object(GetJSConstructor(env, "Node"), BASE_NS::countof(args), args);
770            return true;
771        };
772    };
773    AsyncState* data = new AsyncState();
774    data->this_ = NapiApi::StrongRef(ctx, ctx.This());
775    data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
776
777    napi_value ret = MakePromise(ctx, data);
778    // and just instantly complete it
779    data->finally(ctx);
780    data->Success(ctx);
781    return ret;
782}
783
784napi_value SceneJS::CreateMaterial(NapiApi::FunctionContext<NapiApi::Object, uint32_t>& ctx)
785{
786    struct AsyncState : public AsyncStateBase {
787        NapiApi::StrongRef this_;
788        NapiApi::StrongRef args_;
789        uint32_t type_;
790        BASE_NS::string name_;
791        SCENE_NS::IMaterial::Ptr material_;
792        SCENE_NS::IScene::Ptr scene_;
793        bool finally(napi_env env) override
794        {
795            napi_value args[] = {
796                this_.GetValue(), // scene..
797                args_.GetValue()  // params.
798            };
799
800            if (type_ == BaseMaterial::SHADER) {
801                MakeNativeObjectParam(env, material_, BASE_NS::countof(args), args);
802                NapiApi::Object materialJS(GetJSConstructor(env, "ShaderMaterial"), BASE_NS::countof(args), args);
803                result = materialJS;
804            } else {
805                // fail..
806                material_.reset();
807            }
808            return true;
809        };
810    };
811    AsyncState* data = new AsyncState();
812    data->this_ = NapiApi::StrongRef(ctx, ctx.This());
813    data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
814    data->type_ = ctx.Arg<1>();
815    if (ctx.Arg<0>()) {
816        NapiApi::Object parms = ctx.Arg<0>();
817        data->name_ = parms.Get<BASE_NS::string>("name");
818    }
819
820    napi_value result = MakePromise(ctx, data);
821    data->scene_ = interface_pointer_cast<SCENE_NS::IScene>(GetNativeObject());
822
823    // create an engine task and complete it there..
824    auto fun = [data]() {
825        data->material_ = data->scene_->CreateMaterial(data->name_);
826        data->CallIt();
827        return false;
828    };
829
830    META_NS::GetTaskQueueRegistry()
831        .GetTaskQueue(ENGINE_THREAD)
832        ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
833
834    return result;
835}
836
837napi_value SceneJS::CreateShader(NapiApi::FunctionContext<NapiApi::Object>& ctx)
838{
839    struct AsyncState : public AsyncStateBase {
840        NapiApi::StrongRef this_;
841        NapiApi::StrongRef args_;
842        BASE_NS::string uri_;
843        BASE_NS::string name_;
844        SCENE_NS::IShader::Ptr shader_;
845        bool finally(napi_env env) override
846        {
847            napi_value args[] = {
848                this_.GetValue(), // scene..
849                args_.GetValue()  // params.
850            };
851            NapiApi::Object parms(env, args[1]);
852
853            napi_value null;
854            napi_get_null(env, &null);
855            parms.Set("Material", null); // not bound to anything...
856
857            MakeNativeObjectParam(env, shader_, BASE_NS::countof(args), args);
858            NapiApi::Object shaderJS(GetJSConstructor(env, "Shader"), BASE_NS::countof(args), args);
859            result = shaderJS;
860
861            return true;
862        };
863    };
864    AsyncState* data = new AsyncState();
865    data->this_ = NapiApi::StrongRef(ctx, ctx.This());
866    data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
867    if (ctx.Arg<0>()) {
868        NapiApi::Object parms = ctx.Arg<0>();
869        data->name_ = parms.Get<BASE_NS::string>("name");
870        data->uri_ = FetchResourceOrUri(ctx, parms.Get("uri"));
871    }
872
873    napi_value result = MakePromise(ctx, data);
874
875    auto fun = [data]() {
876        auto& obr = META_NS::GetObjectRegistry();
877        auto doc = interface_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
878        auto renderContext = doc->GetPropertyByName<IntfPtr>("RenderContext")->GetValue();
879
880        auto params =
881            interface_pointer_cast<META_NS::IMetadata>(META_NS::GetObjectRegistry().Create(META_NS::ClassId::Object));
882
883        params->AddProperty(META_NS::ConstructProperty<IntfPtr>(
884            "RenderContext", renderContext, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
885
886        params->AddProperty(META_NS::ConstructProperty<BASE_NS::string>(
887            "uri", data->uri_, META_NS::ObjectFlagBits::INTERNAL | META_NS::ObjectFlagBits::NATIVE));
888
889        data->shader_ = META_NS::GetObjectRegistry().Create<SCENE_NS::IShader>(SCENE_NS::ClassId::Shader, params);
890        data->CallIt();
891        return false;
892    };
893
894    META_NS::GetTaskQueueRegistry()
895        .GetTaskQueue(ENGINE_THREAD)
896        ->AddTask(META_NS::MakeCallback<META_NS::ITaskQueueTask>(BASE_NS::move(fun)));
897
898    return result;
899}
900
901void SceneJS::StoreBitmap(BASE_NS::string_view uri, SCENE_NS::IBitmap::Ptr bitmap)
902{
903    CORE_NS::UniqueLock lock(mutex_);
904    if (bitmap) {
905        bitmaps_[uri] = bitmap;
906    } else {
907        // setting null. releases.
908        bitmaps_.erase(uri);
909    }
910}
911SCENE_NS::IBitmap::Ptr SceneJS::FetchBitmap(BASE_NS::string_view uri)
912{
913    CORE_NS::UniqueLock lock(mutex_);
914    auto it = bitmaps_.find(uri);
915    if (it != bitmaps_.end()) {
916        return it->second;
917    }
918    return {};
919}
920
921napi_value SceneJS::CreateImage(NapiApi::FunctionContext<NapiApi::Object>& ctx)
922{
923    using namespace RENDER_NS;
924
925    struct AsyncState : public AsyncStateBase {
926        NapiApi::StrongRef this_;
927        NapiApi::StrongRef args_;
928        BASE_NS::shared_ptr<IRenderContext> renderContext_;
929        BASE_NS::string name_;
930        BASE_NS::string uri_;
931        SCENE_NS::IBitmap::Ptr bitmap_;
932        RenderHandleReference imageHandle_;
933        SceneJS* owner_;
934        CORE_NS::IImageLoaderManager::LoadResult imageLoadResult_;
935        bool cached_ { false };
936        bool finally(napi_env env) override
937        {
938            if (!bitmap_) {
939                // return the error string..
940                napi_create_string_utf8(env, imageLoadResult_.error, NAPI_AUTO_LENGTH, &result);
941                return false;
942            }
943            if (!cached_) {
944                // create the jsobject if we don't have one.
945                napi_value args[] = {
946                    this_.GetValue(), // scene..
947                    args_.GetValue()  // params.
948                };
949                MakeNativeObjectParam(env, bitmap_, BASE_NS::countof(args), args);
950                owner_->StoreBitmap(uri_, BASE_NS::move(bitmap_));
951                NapiApi::Object imageJS(GetJSConstructor(env, "Image"), BASE_NS::countof(args), args);
952                result = imageJS;
953            }
954            return true;
955        };
956    };
957    AsyncState* data = new AsyncState();
958    data->owner_ = this;
959    data->this_ = NapiApi::StrongRef(ctx, ctx.This());
960    data->args_ = NapiApi::StrongRef(ctx, ctx.Arg<0>());
961
962    NapiApi::Object args = ctx.Arg<0>();
963    if (args) {
964        if (auto n = args.Get<BASE_NS::string>("name")) {
965            data->name_ = n;
966        }
967        data->uri_ = FetchResourceOrUri(ctx, args.Get("uri"));
968    }
969
970    napi_value result = MakePromise(ctx, data);
971    if (auto bitmap = FetchBitmap(data->uri_)) {
972        // no aliasing.. so the returned bitmaps name is.. the old one.
973        // *fix*
974        // oh we have it already, no need to do anything in engine side.
975        data->cached_ = true;
976        data->bitmap_ = bitmap;
977        auto obj = interface_pointer_cast<META_NS::IObject>(data->bitmap_);
978        data->result = FetchJsObj(obj);
979        data->Success(ctx);
980        return result;
981    }
982
983    auto& obr = META_NS::GetObjectRegistry();
984    auto doc = interface_cast<META_NS::IMetadata>(obr.GetDefaultObjectContext());
985    data->renderContext_ =
986        interface_pointer_cast<IRenderContext>(doc->GetPropertyByName<IntfPtr>("RenderContext")->GetValue());
987
988    // create an IO task (to load the cpu data)
989    auto fun = [data]() -> META_NS::IAny::Ptr {
990        uint32_t imageLoaderFlags = CORE_NS::IImageLoaderManager::IMAGE_LOADER_GENERATE_MIPS;
991        auto& imageLoaderMgr = data->renderContext_->GetEngine().GetImageLoaderManager();
992        data->imageLoadResult_ = imageLoaderMgr.LoadImage(data->uri_, imageLoaderFlags);
993        return {};
994    };
995
996    // create final engine task (to create gpu resource)
997    auto fun2 = [data](const META_NS::IAny::Ptr&) -> META_NS::IAny::Ptr {
998        if (!data->imageLoadResult_.success) {
999            CORE_LOG_E("Could not load image asset: %s", data->imageLoadResult_.error);
1000            data->bitmap_ = nullptr;
1001        } else {
1002            auto& gpuResourceMgr = data->renderContext_->GetDevice().GetGpuResourceManager();
1003            RenderHandleReference imageHandle {};
1004            GpuImageDesc gpuDesc = gpuResourceMgr.CreateGpuImageDesc(data->imageLoadResult_.image->GetImageDesc());
1005            gpuDesc.usageFlags = CORE_IMAGE_USAGE_SAMPLED_BIT | CORE_IMAGE_USAGE_TRANSFER_DST_BIT;
1006            if (gpuDesc.engineCreationFlags & EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_GENERATE_MIPS) {
1007                gpuDesc.usageFlags |= CORE_IMAGE_USAGE_TRANSFER_SRC_BIT;
1008            }
1009            gpuDesc.memoryPropertyFlags = CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
1010            data->imageHandle_ = gpuResourceMgr.Create(data->uri_, gpuDesc, std::move(data->imageLoadResult_.image));
1011            data->bitmap_ = META_NS::GetObjectRegistry().Create<SCENE_NS::IBitmap>(SCENE_NS::ClassId::Bitmap);
1012            data->bitmap_->Uri()->SetValue(data->uri_);
1013            data->bitmap_->SetRenderHandle(data->imageHandle_, { gpuDesc.width, gpuDesc.height });
1014        }
1015        data->CallIt();
1016        return {};
1017    };
1018
1019    // execute first step in io thread..
1020    // second in engine thread.
1021    // and the final in js thread (with threadsafefunction)
1022    auto ioqueue = META_NS::GetTaskQueueRegistry().GetTaskQueue(IO_QUEUE);
1023    auto enginequeue = META_NS::GetTaskQueueRegistry().GetTaskQueue(ENGINE_THREAD);
1024    ioqueue->AddWaitableTask(META_NS::MakeCallback<META_NS::ITaskQueueWaitableTask>(BASE_NS::move(fun)))
1025        ->Then(META_NS::MakeCallback<META_NS::IFutureContinuation>(BASE_NS::move(fun2)), enginequeue);
1026
1027    return result;
1028}
1029napi_value SceneJS::GetAnimations(NapiApi::FunctionContext<>& ctx)
1030{
1031    auto scene = interface_pointer_cast<SCENE_NS::IScene>(GetThisNativeObject(ctx));
1032    if (!scene) {
1033        return ctx.GetUndefined();
1034    }
1035
1036    BASE_NS::vector<META_NS::IAnimation::Ptr> animRes;
1037    ExecSyncTask([scene, &animRes]() {
1038        animRes = scene->GetAnimations();
1039        return META_NS::IAny::Ptr {};
1040    });
1041
1042    napi_value tmp;
1043    auto status = napi_create_array_with_length(ctx, animRes.size(), &tmp);
1044    size_t i = 0;
1045    napi_value args[] = { ctx.This() };
1046    for (const auto& node : animRes) {
1047        napi_value val;
1048        val = CreateFromNativeInstance(
1049            ctx, interface_pointer_cast<META_NS::IObject>(node), false, BASE_NS::countof(args), args);
1050        status = napi_set_element(ctx, tmp, i++, val);
1051
1052        // disposables_[
1053    }
1054
1055    return tmp;
1056}
1057
1058void SceneJS::DisposeHook(uintptr_t token, NapiApi::Object obj)
1059{
1060    disposables_[token] = { obj };
1061}
1062void SceneJS::ReleaseDispose(uintptr_t token)
1063{
1064    auto it = disposables_.find(token);
1065    if (it != disposables_.end()) {
1066        it->second.Reset();
1067        disposables_.erase(it->first);
1068    }
1069}
1070
1071void SceneJS::StrongDisposeHook(uintptr_t token, NapiApi::Object obj)
1072{
1073    strongDisposables_[token] = { obj };
1074}
1075void SceneJS::ReleaseStrongDispose(uintptr_t token)
1076{
1077    auto it = strongDisposables_.find(token);
1078    if (it != strongDisposables_.end()) {
1079        it->second.Reset();
1080        strongDisposables_.erase(it->first);
1081    }
1082}
1083