¿Cómo compartir la memoria de matriz entre JavaScriptCore y Swift?

2020-02-15 swift memory javascriptcore

Estoy tratando de escribir un programa Swift que ejecute JS a través de JavaScriptCore. Deseo compartir memoria entre ambas partes de mi programa de manera que JS escriba en un búfer de matriz tipado creado en Swift, y Swift lo lee y escribe después. Esto será una especie de búfer de comando.

Por ejemplo, aquí hay un pseudocódigo que representa aproximadamente lo que planeo hacer:

// js
let buf;
let i = 0;
setup() {
   buf = new Uint8Array(mem.alloc(N_BYTES));
}

frame() {
   i = 0;
   buf[i++] = some_command_enum;
}

// swift
func alloc(bytes : Int) -> the_memory {
    // allocate bytes uints and save the memory here
    // save a reference to the memory here
    // return the memory to use in JS
}

El problema es que cada vez que intento agregar la implementación a alloc, JS informa por excepción que la función no está definida, lo que significa que algo está mal con la forma en que estoy haciendo las cosas. Las funciones que no regresan están bien, así que tengo eso abajo.

Esta es mi implementación defectuosa (por favor vea los comentarios):

// swift
@objc protocol JSMemoryExports: JSExport {
    static func alloc(_ byte_count: Int) -> JSObjectRef
    static func free(_ memory: JSObjectRef)
}

class JSMemory: NSObject, JSMemoryExports {
                                           // What is the correct return type?
    class func alloc(_ byte_count: Int) -> JSObjectRef {
        // temp
        let jsContext = JS_Controller.js.ctx!

        print("BYTE_COUNT", byte_count)

        // allocating a typed array
        let arr = JSObjectMakeTypedArray(jsContext.jsGlobalContextRef!, kJSTypedArrayTypeUint8Array, byte_count, nil)

        // just testing here to see how I'd write to this buffer (Note: is this the fastest way, or is all this memory binding slow?:
        // getting the raw bytes
        let ptr = JSObjectGetTypedArrayBytesPtr(jsContext.jsGlobalContextRef!, arr, nil)
        //let buf = JSObjectGetTypedArrayBuffer(jsContext.jsGlobalContextRef, arr, nil)
        let u8Ptr = ptr!.bindMemory(to: UInt8.self, capacity: byte_count)
        //u8Ptr[0] = 5
        return arr!
    }
}

...

jsContext["mem"] = JSMemory.self

// js
const buf = new Uint8Array(mem.alloc(8)) // JS Exception: TypeError: mem.alloc is not a function. (In 'mem.alloc(8)', 'mem.alloc' is undefined)

He visto variantes de enlace de funciones que usan algún tipo de atributo @convention . ¿Debo usar eso en su lugar?

¿Qué es lo correcto hacer?

Answers

La documentación no es muy útil a menos que recopile mucha información de fuentes separadas. La solución aparentemente funcional implica el uso de partes de la API de C anterior que se pueden llamar en punteros JSValue? e inseguros, y asegurarse de que los valores de retorno de las funciones enlazadas sean JSValue? s. Eso tiene sentido ya que todas las funciones de JavaScript devuelven un objeto, null o undefined . Un tipo opcional refleja este comportamiento.

Aquí está mi código de trabajo en progreso para cualquiera que necesite algunos leads:

Solo para una actualización, descubrí cómo mezclar la antigua API de C con las nuevas API específicas de Swift más limitadas. Todavía no me he asegurado de que no pierdo memoria, pero parece que he encontrado lo que necesitaba, con suerte.

En caso de que alguna vez quisieras saber:

@objc protocol JSMemoryExports: JSExport {
    // note that I'm returning an optional
    static func Uint8ArrayMake(_ count : JSValue) -> JSValue?
}

class JSMemory: NSObject, JSMemoryExports { 
    class func UInt8ArrayMake(_ count : JSValue) -> JSValue? {
        guard !count.isUndefined && !count.isNull else {
            return nil
        }

        let ref : JSValueRef = JSObjectMakeTypedArray(
            JS_Controller.js.ctx.jsGlobalContextRef!,
            kJSTypedArrayTypeUint8Array,
            Int(count.toInt32()),
            nil
        )!

        // if you want to modify the data
        // let ptr = JSObjectGetTypedArrayBytesPtr(
        //    JS_Controller.js.ctx.jsGlobalContextRef!, ref, nil
        // )

        return JSValue(jsValueRef: ref, in: JS_Controller.js.ctx)    
    }
}

Aquí hay un par de referencias útiles:

punteros en Swift
gestión manual de memoria en Swift

Related