2014-02-03 3 views
2

Я пытаюсь обратиться к parallels.js через JSNI. Parallels предоставляет хороший API для веб-работников, и я написал несколько облегченных оберток кода, который обеспечивает более удобный интерфейс для работников из GWT, чем Elemental. Однако я получаю сообщение об ошибке:GWT/JSNI - «DataCloneError - объект не может быть клонирован» - как мне отлаживать?

com.google.gwt.core.client.JavaScriptException: (DataCloneError) @ io.mywrapper.workers.Parallel :: runParallel ([Ljava/lang/String; (Объект Java: [Ljava.lang.String; @ 1922352522, объект JavaScript (3006), объект JavaScript (3008), объект Lcom/google/gwt/core/client/JavaScriptObject, Lcom/google/gwt/core/client/JavaScriptObject;)]): Объект не может быть клонирован.

Это происходит из, в режиме хоста:

в com.google.gwt.dev.shell.BrowserChannelServer.invokeJavascript (BrowserChannelServer.java:249) в com.google.gwt.dev.shell.ModuleSpaceOOPHM. doInvoke (ModuleSpaceOOPHM.java:136) в com.google.gwt.dev.shell.ModuleSpace.invokeNative (ModuleSpace.java:571) в com.google.gwt.dev.shell.ModuleSpace.invokeNativeVoid (ModuleSpace.java:299) в com.google.gwt.dev.shell.JavaScriptHost.invokeNativeVoid (JavaScriptHost.java:107) в io.mywrapper.workers.Parallel.runParallel (Parallel.java)

Вот мой код:

Пример клиентского вызова для создания рабочего:

Workers.spawnWorker(new String[]{"hello"}, new Worker() { 
     @Override 
     public String[] work(String[] data) { 
      return data; 
     } 

     @Override 
     public void done(String[] data) { 
      int i = data.length; 
     } 
    }); 

API-интерфейс, который обеспечивает общий интерфейс:

public class Workers { 

    public static void spawnWorker(String[] data, Worker worker) { 
     Parallel.runParallel(data, workFunction(worker), callbackFunction(worker)); 
    } 

    /** 
    * Create a reference to the work function. 
    */ 
    public static native JavaScriptObject workFunction(Worker worker) /*-{ 
     return worker == null ? null : $entry(function(x) { 
      [email protected]::work([Ljava/lang/String;)(x); 
     }); 
    }-*/; 

    /** 
    * Create a reference to the done function. 
    */ 
    public static native JavaScriptObject callbackFunction(Worker worker) /*-{ 
     return worker == null ? null : $entry(function(x) { 
      [email protected]::done([Ljava/lang/String;)(x); 
     }); 
    }-*/;   
} 

Worker:

public interface Worker extends Serializable { 

    /** 
    * Called to perform the work. 
    * @param data 
    * @return 
    */ 
    public String[] work(String[] data); 

    /** 
    * Called with the result of the work. 
    * @param data 
    */ 
    public void done(String[] data); 

} 

И, наконец, Parallels обертка:

public class Parallel { 

    /** 
    * @param data Data to be passed to the function 
    * @param work Function to perform the work, given the data 
    * @param callback Function to be called with result 
    * @return 
    */ 
    public static native void runParallel(String[] data, JavaScriptObject work, JavaScriptObject callback) /*-{ 
     var p = new $wnd.Parallel(data); 
     p.spawn(work).then(callback); 
    }-*/; 
} 

Что это вызывает это?

Документы JSNI говорят о массивах:

opaque value that can only be passed back into Java code 

Это довольно лаконично, но в конечном счете, мои массивы передаются обратно в Java код, поэтому я предполагаю, что они в порядке.

EDIT - хорошо, плохое предположение. Массивы, несмотря на то, что они якобы передаются обратно на Java-код, вызывают ошибку (что странно, потому что в DataCloneError очень мало googleability.) Изменение их на String работает; однако String для моих нужд недостаточно. Похоже, объекты сталкиваются с теми же проблемами, что и массивы; Я видел ссылку Томаса на JSArrayUtils в другом потоке StackOverflow, но я не могу понять, как вызвать его с помощью массива строк (он хочет, чтобы массив JavaScriptObjects был введен для не-примитивных типов, что не делает мне ничего хорошего). Есть ли опрятный выход из этого?

EDIT 2 - Изменен для использования JSArrayString везде, где я использовал String []. Новый выпуск; на этот раз нет stacktrace, но в консоли я получаю сообщение об ошибке: Uncaught ReferenceError: __gwt_makeJavaInvoke не определен. Когда я щелкаю по ссылке на сгенерированный сценарий в инструментах разработчика, я получаю этот фрагмент:

self.onmessage = function(e) {self.postMessage((function(){ 
    try { 
     return __gwt_makeJavaInvoke(3)(null, 65626, jsFunction, this, arguments); 
    } 
    catch (e) { 
     throw e; 
    } 
    })(e.data))} 

Я вижу, что _gwt_makeJavaInvoke является частью класса JSNI; так почему бы его не найти?

ответ

1

Если вы никогда режим использования DEV это в настоящее время безопасно делать вид, что Java String[] является массив JS со строками в нем. Это приведет к разрыву в режиме dev, поскольку массивы должны использоваться в Java, а строки - специально обработаны и могут разрываться в будущем, если компилятор оптимизирует массивы по-разному.

случаи, когда это может пойти не так в будущем:

Семантика Java массивов и массивов JavaScript разные - Java массивы не могут быть изменены, и инициализированы конкретными значениями в зависимости от типа компонента (данные в массив). Поскольку вы пишете Java-код, компилятор может мыслить делать предположения на основе подробностей о том, как вы создаете и используете этот массив, который может быть поврежден JS-кодом, который не знает, чтобы никогда не изменять массив.

Некоторые массивы примитивных типов могут быть оптимизированы в TypedArrays в JavaScript, более тесно связанные с семантикой Java с точки зрения изменения размера и поведения Java с точки зрения распределения. Это было бы повышение производительности, а также, но может нарушить любое использование int[], double[] и т.д.


Вместо этого, вы должны скопировать данные в JsArrayString, или просто использовать массив JS для хранения данных, а чем взад и вперед, в зависимости от вашего варианта использования. Различные типы JsArray могут быть изменены и уже существуют как объекты JavaScript, которые за пределами JS могут понимать и работать.


Ответить EDIT 2:

На догадке, сценарий parallel.js пытается запустить код из другой области таких в WebWorker (это точка коды, справа), где ваш код GWT отсутствует. Таким образом, он не может вызвать makeJavaInvoke, который является мостом обратно в режим dev (будет другой сбой с скомпилированным JS). Согласно http://adambom.github.io/parallel.js/, существуют особые требования, с которыми должен отвечать пройденный обратный вызов, который должен быть передан в spawn и, возможно, then - ваши анонимные функции определенно не соответствуют им, и может быть невозможно поддерживать Java-семантику.

Перед тем, как получить гораздо глубже, проверить этот ответ я сделал некоторое время назад решения основных проблем с webworkers и GWT/Java: https://stackoverflow.com/a/11376059/860630

Как отмечено там, WebWorkers эффективно новые процессы, без общего кода или общее состояние с исходным процессом. Код Parallel.js пытается обработать это с небольшим количеством обмана - общее состояние - только доступно в виде содержимого, переданного исходному конструктору Parallel, но вы - , пытающийся передать в случаях ' java 'и методы вызова на них. Эти экземпляры Java имеют собственное состояние и потенциально могут связываться с остальной частью приложения Java по полям экземпляра Worker. Если бы я выполнял Worker и делал то, что ссылалось на другие данные, чем на то, что было передано, я бы увидел дальнейшие причудливые неудачи.

Так функция вы передаете в должна быть полностью автономной - они не должны ссылаться на внешний код в любом случае, с тех пор эта функция не может быть передана от к WebWorker или несколько webworkers, каждый не знает существования друг друга.См https://github.com/adambom/parallel.js/issues/32, например:

That's not possible since it would

  • require a shared state across workers
  • require us to transmit all scope variables (I don't think there's even a possibility to read the available scopes)

The only thing which might be possible would be cache variables, but these can already be defined in the function itself with spawn() and don't make any sense in map (because there's no shared state).

Не будучи на самом деле знакомы с тем, как parallel.js реализуется (все этого ответа до сих пор читает документы и быстрый поиск Google для «parallel.js общего состояния», а также имеющие экспериментировал с WebWorkers в течение дня или около того и решил, что моя настоящая проблема еще не стоит того, чтобы беспокоиться), я бы предположил, что then неограничен, и вы можете передать его, как хотите, но , и reduce должны быть написанные таким образом, что их JS можно передать в новый JS-процесс и полностью остаться там.

Это может возможно из обычного кода Java при компиляции, если у вас есть только один вариант осуществления работника и что осущ никогда не использует состояние, кроме того, что непосредственно передается. В этом случае компилятор должен переписать свои методы для быть статичным, чтобы они были безопасны в этом контексте. Однако это не делает для очень полезной библиотеки, как кажется, вы пытаетесь достичь. Имея это в виду, вы можете сохранить свой рабочий код в JSNI, чтобы обеспечить соблюдение правил parallel.js.

И наконец, в соответствии с нормальными правилами GWT, избегайте $entry для вызовов, которые вы ожидаете в других контекстах, поскольку эти рабочие не имеют доступа к нормальной обработке исключений и планированию, которое разрешает $ entry.

(и, наконец, в конце концов, это, вероятно, все еще возможно, если вы очень осторожны при реализации Worker письма и написать генератор, который запускает каждый реализации уборщица в очень специфических способов, чтобы убедиться, что com.google.gwt.dev.jjs.impl.MakeCallsStatic и com.google.gwt.dev.jjs.impl.Pruner может правильно действовать, чтобы нокаутировать this в тех случаях, когда они были переписаны как функции JS. I думаю, Самый чистый способ сделать это - испустить JSNI в самом генераторе, вызвать статический метод, написанный на реальной Java, и из этого вызова статического метода метод конкретного экземпляра, который делает тяжелый подъем для икры и т. д.)

+0

Спасибо. Итак, законно создавать и заполнять JsArrayString в Java-коде? И я могу передать это как с Java на Javascript, так и обратно? – user717847

+0

Да, это правильно - любой JSO (подкласс JavaScriptObject) может быть передан между Java/JSNI/JavaScript и использоваться так, как если бы он действительно был там. В объявлении (и создании экземпляров) JSOs допускаются только ограничения, они должны иметь защищенный ctor и создаваться только в JS/JSNI, все методы должны быть окончательными. О, и вы не можете называть методы JSO изнутри JSNI, хотя это редко вызывает беспокойство, поскольку вы можете назвать версии JS. Используйте 'JsArrayString array = JavaScriptObject.createArray(). Cast()' для его создания и java-look методы для заполнения. –

+0

Спасибо, я приближаюсь. См. Мой «EDIT 2» выше; Я изменил свой код, чтобы использовать JSArrayString, где я ранее использовал String [], и теперь я получаю также ungoogleable «Uncaught ReferenceError: __gwt_makeJavaInvoke не определен». (Мне особенно не повезло или не так много людей делают это?) Любые идеи о том, что вызывает это? – user717847

3

Вы можете найти рабочий пример GWT и WebWork ers здесь: https://github.com/tomekziel/gwtwwlinker/

Это предварительная работа, но с использованием этого шаблона я смог передать объекты GWT в веб-сайт и из него с использованием сериализации, предоставляемой AutoBeanFactory.

+0

Спасибо, я возьму этот подход в качестве отправной точки. – user717847

Смежные вопросы