/// <summary>
        /// Creates a new JavaScript Runtime.
        /// </summary>
        /// <param name="attributes"></param>
        /// <returns></returns>
        public BaristaRuntime CreateRuntime(JavaScriptRuntimeAttributes attributes = JavaScriptRuntimeAttributes.None)
        {
            var contextService = m_serviceProvider.GetRequiredService <IBaristaContextFactory>();

            var runtimeHandle = m_engine.JsCreateRuntime(attributes, null);
            var result        = m_runtimePool.GetOrAdd(runtimeHandle, () => {
                var rt = new BaristaRuntime(m_engine, contextService, runtimeHandle);
                //Do not wire up a runtime's BeforeCollect handler with the factory as this will
                //remove and dispose of the runtime on ANY garbage collect, which will eventually
                //crash the engine.

                //After a runtime handle is disposed, remove the handle from the pool.
                void afterDispose(object sender, EventArgs args)
                {
                    rt.AfterDispose -= afterDispose;
                    m_runtimePool.RemoveHandle(runtimeHandle);
                }

                rt.AfterDispose += afterDispose;
                return(rt);
            });

            if (result == null)
            {
                throw new InvalidOperationException("Unable to create or retrieve a new Barista Runtime.");
            }

            return(result);
        }
        public BaristaContext CreateContext(BaristaRuntime runtime)
        {
            if (runtime == null)
            {
                throw new ArgumentNullException(nameof(runtime));
            }

            if (runtime.IsDisposed)
            {
                throw new ObjectDisposedException(nameof(runtime));
            }

            var contextHandle = m_engine.JsCreateContext(runtime.Handle);

            return(m_contextPool.GetOrAdd(contextHandle, () =>
            {
                var moduleRecordFactory = m_serviceProvider.GetRequiredService <IBaristaModuleRecordFactory>();
                var valueFactoryBuilder = m_serviceProvider.GetRequiredService <IBaristaValueFactoryBuilder>();
                var conversionStrategy = m_serviceProvider.GetRequiredService <IBaristaConversionStrategy>();

                //For flexability, a promise task queue is not required.
                var promiseTaskQueue = m_serviceProvider.GetService <IPromiseTaskQueue>();

                //Set the handle that will be called prior to the engine collecting the context.
                var context = new BaristaContext(m_engine, valueFactoryBuilder, conversionStrategy, moduleRecordFactory, promiseTaskQueue, contextHandle);

                void beforeCollect(object sender, BaristaObjectBeforeCollectEventArgs args)
                {
                    context.BeforeCollect -= beforeCollect;
                    m_contextPool.RemoveHandle(new JavaScriptContextSafeHandle(args.Handle));
                }
                context.BeforeCollect += beforeCollect;
                return context;
            }));
        }
        public JsArrayBuffer CreateArrayBuffer(string data)
        {
            if (data == null)
            {
                data = String.Empty;
            }

            JavaScriptValueSafeHandle externalArrayHandle;
            IntPtr ptrData = Marshal.StringToHGlobalUni(data);

            try
            {
                GCHandle freeCallbackHandle = default(GCHandle);
                JavaScriptObjectFinalizeCallback freeCallback = (ptr) =>
                {
                    Marshal.ZeroFreeGlobalAllocUnicode(ptrData);
                    if (freeCallbackHandle.IsAllocated)
                    {
                        freeCallbackHandle.Free();
                    }
                };
                freeCallbackHandle = GCHandle.Alloc(freeCallback);

                externalArrayHandle = m_engine.JsCreateExternalArrayBuffer(ptrData, (uint)data.Length, freeCallback, IntPtr.Zero);
            }
            catch (Exception)
            {
                //If anything goes wrong, free the unmanaged memory.
                //This is not a finally as if success, the memory will be freed automagially.
                Marshal.ZeroFreeGlobalAllocUnicode(ptrData);
                throw;
            }

            var result = m_valuePool.GetOrAdd(externalArrayHandle, () =>
            {
                return(new JsManagedExternalArrayBuffer(m_engine, Context, externalArrayHandle, ptrData));
            });

            var resultArrayBuffer = result as JsArrayBuffer;

            if (resultArrayBuffer == null)
            {
                throw new InvalidOperationException($"Expected the result object to be a JsArrayBuffer, however the value was {result.GetType()}");
            }

            return((JsArrayBuffer)result);
        }
        private BaristaModuleRecord CreateBaristaModuleRecordInternal(BaristaContext context, string moduleName, JavaScriptValueSafeHandle specifier, BaristaModuleRecord parentModule = null, bool setAsHost = false, IBaristaModuleLoader moduleLoader = null)
        {
            JavaScriptModuleRecord moduleRecord = null;

            if (m_specifierModuleLookup.ContainsKey(specifier))
            {
                moduleRecord = m_specifierModuleLookup[specifier];
            }

            if (moduleRecord == null || moduleRecord.IsClosed || moduleRecord.IsInvalid)
            {
                if (m_specifierModuleLookup.ContainsKey(specifier))
                {
                    m_specifierModuleLookup.Remove(specifier);
                }

                moduleRecord = m_engine.JsInitializeModuleRecord(parentModule == null ? JavaScriptModuleRecord.Invalid : parentModule.Handle, specifier);
            }

            return(m_moduleReferencePool.GetOrAdd(moduleRecord, () =>
            {
                //If a module loader hasn't be specified in the parameters, obtain one from DI.
                if (moduleLoader == null)
                {
                    moduleLoader = m_serviceProvider.GetRequiredService <IBaristaModuleLoader>();
                }

                var module = new BaristaModuleRecord(moduleName.ToString(), specifier, parentModule, m_engine, context, this, moduleLoader, moduleRecord);
                m_specifierModuleLookup.Add(specifier, moduleRecord);

                //If specified, indicate as host module.
                if (setAsHost == true)
                {
                    m_engine.JsSetModuleHostInfo(moduleRecord, JavaScriptModuleHostInfoKind.HostDefined, specifier.DangerousGetHandle());
                }

                void beforeCollect(object sender, BaristaObjectBeforeCollectEventArgs args)
                {
                    context.BeforeCollect -= beforeCollect;
                    m_moduleReferencePool.RemoveHandle(new JavaScriptModuleRecord(args.Handle));
                    if (sender is BaristaModuleRecord baristaModuleRecord)
                    {
                        m_specifierModuleLookup.Remove(baristaModuleRecord.Specifier);
                    }
                }
                module.BeforeCollect += beforeCollect;
                return module;
            }));
        }