How can I convert JS array to native array ?
In Rhino conversion looked like (Scala code):
val eng = (new javax.script.ScriptEngineManager).getEngineByName("JavaScript")
val obj = eng.eval("[1,2,3,4]")
val arr = obj.asInstanceOf[sun.org.mozilla.javascript.internal.NativeArray]
In Nashorn NativeArray absent, and I can't find any documentation on conversion.
From Java (and Scala), you can also invoke convert method on jdk.nashorn.api.scripting.ScriptUtils class. E.g. from Java:
import jdk.nashorn.api.scripting.ScriptUtils;
...
int[] iarr = (int[])ScriptUtils.convert(arr, int[].class)
my Scala is not too fluent, but I believe the equivalent is:
val iarr = ScriptUtils.convert(arr, Array[Int]).asInstanceOf(Array[Int])
Given a JavaScript array, you can convert it to a Java array using the Java.to() method in oracle nashorn engine which is available in jdk 8
Example
var data = [1,2,3,4,5,6];
var JavaArray = Java.to(data,"int[]");
print(JavaArray[0]+JavaArray[1]+JavaArray[2]);
I found a solution that works for Rhino and Nashorn.
First problem, the script writer must not deal with Java Objects!
That leads to a solution, that only uses Java.
Second it must work with Java 8 and previous versions!
final ScriptEngineManager manager = new ScriptEngineManager();
final ScriptEngine engine = manager.getEngineByName("JavaScript");
try {
Object result = convert(engine.eval("(function() {return ['a', 'b'];})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return [3, 7.75];})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return 'Test';})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return 7.75;})()"));
log.debug("Result: {}", result);
result = convert(engine.eval("(function() {return false;})()"));
log.debug("Result: {}", result);
} catch (final ScriptException e) {
e.printStackTrace();
}
private static Object convert(final Object obj) {
log.debug("JAVASCRIPT OBJECT: {}", obj.getClass());
if (obj instanceof Bindings) {
try {
final Class<?> cls = Class.forName("jdk.nashorn.api.scripting.ScriptObjectMirror");
log.debug("Nashorn detected");
if (cls.isAssignableFrom(obj.getClass())) {
final Method isArray = cls.getMethod("isArray");
final Object result = isArray.invoke(obj);
if (result != null && result.equals(true)) {
final Method values = cls.getMethod("values");
final Object vals = values.invoke(obj);
if (vals instanceof Collection<?>) {
final Collection<?> coll = (Collection<?>) vals;
return coll.toArray(new Object[0]);
}
}
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {}
}
if (obj instanceof List<?>) {
final List<?> list = (List<?>) obj;
return list.toArray(new Object[0]);
}
return obj;
}
Rhino delivers arrays as class sun.org.mozilla.javascript.internal.NativeArray that implement the java.util.List interface, that are easy to handle
13:48:42.400 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class sun.org.mozilla.javascript.internal.NativeArray
13:48:42.405 [main] DEBUG de.test.Tester - Result: [a, b]
13:48:42.407 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class sun.org.mozilla.javascript.internal.NativeArray
13:48:42.407 [main] DEBUG de.test.Tester - Result: [3.0, 7.75]
13:48:42.410 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.String
13:48:42.410 [main] DEBUG de.test.Tester - Result: Test
13:48:42.412 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Double
13:48:42.412 [main] DEBUG de.test.Tester - Result: 7.75
13:48:42.414 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Boolean
13:48:42.415 [main] DEBUG de.test.Tester - Result: false
Nashorn returns a JavaScript array as jdk.nashorn.api.scripting.ScriptObjectMirror that unfortunately doesn't implement the List interface.
I solved it using reflection and I'm wondering why Oracle has made this big change.
13:51:02.488 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class jdk.nashorn.api.scripting.ScriptObjectMirror
13:51:02.495 [main] DEBUG de.test.Tester - Nashorn detected
13:51:02.497 [main] DEBUG de.test.Tester - Result: [a, b]
13:51:02.503 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class jdk.nashorn.api.scripting.ScriptObjectMirror
13:51:02.503 [main] DEBUG de.test.Tester - Nashorn detected
13:51:02.503 [main] DEBUG de.test.Tester - Result: [3.0, 7.75]
13:51:02.509 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.String
13:51:02.509 [main] DEBUG de.test.Tester - Result: Test
13:51:02.513 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Double
13:51:02.513 [main] DEBUG de.test.Tester - Result: 7.75
13:51:02.520 [main] DEBUG de.test.Tester - JAVASCRIPT OBJECT: class java.lang.Boolean
13:51:02.520 [main] DEBUG de.test.Tester - Result: false
The solution is Java.to function to do conversion:
engine.eval("Java.to(" + script + ",'byte[]')").asInstanceOf[Array[Byte]]
engine.eval("Java.to(" + name + ",'java.lang.String[]')").asInstanceOf[Array[String]]
In case there is array of arrays, we have to process recursivelly:
public class Nashorn {
public static List<String> fromScript(final Object obj){
List<String> returnList = new ArrayList<>();
if(obj==null){
returnList.add("");
return returnList;
}
if (obj instanceof Bindings) {
flatArray(returnList, obj);
return returnList;
}
if (obj instanceof List<?>) {
final List<?> list = (List<?>) obj;
returnList.addAll(list.stream().map(String::valueOf).collect(Collectors.toList()));
return returnList;
}
if(obj.getClass().isArray()){
Object[] array = (Object[])obj;
for (Object anArray : array) {
returnList.add(String.valueOf(anArray));
}
return returnList;
}
returnList.add(String.valueOf(obj));
return returnList;
}
//if we have multiple levels of array, flat the structure
private static void flatArray(List<String> returnList, Object partialArray){
try {
final Class<?> cls = Class.forName("jdk.nashorn.api.scripting.ScriptObjectMirror");
if (cls.isAssignableFrom(partialArray.getClass())) {
final Method isArray = cls.getMethod("isArray");
final Object result = isArray.invoke(partialArray);
if (result != null && result.equals(true)) {
final Method values = cls.getMethod("values");
final Object vals = values.invoke(partialArray);
if (vals instanceof Collection<?>) {
final Collection<?> coll = (Collection<?>) vals;
for(Object el : coll) {
if (cls.isAssignableFrom(el.getClass())) {
flatArray(returnList, el);
}
else{
returnList.add(String.valueOf(el));
}
}
}
}
}
} catch (ClassNotFoundException | NoSuchMethodException | SecurityException
| IllegalAccessException | IllegalArgumentException | InvocationTargetException ignored) {}
}
}
For multidimensional arrays:
You can use this code below to get an array with a dynamic dimension, depending on the object you want to convert. There is a usage example below.
public static Object[] toArray(ScriptObjectMirror scriptObjectMirror)
{
if (!scriptObjectMirror.isArray())
{
throw new IllegalArgumentException("ScriptObjectMirror is no array");
}
if (scriptObjectMirror.isEmpty())
{
return new Object[0];
}
Object[] array = new Object[scriptObjectMirror.size()];
int i = 0;
for (Map.Entry<String, Object> entry : scriptObjectMirror.entrySet())
{
Object result = entry.getValue();
System.err.println(result.getClass());
if (result instanceof ScriptObjectMirror && scriptObjectMirror.isArray())
{
array[i] = toArray((ScriptObjectMirror) result);
}
else
{
array[i] = result;
}
i++;
}
return array;
}
Now, use the method like this:
ScriptObjectMirror som = (ScriptObjectMirror) YOUR_NASHORN_ENGINE.eval("['this', ['tricky', ['method', ['works']], 'perfectly'], ':)']");
// create multi-dimensional array
Object[] obj = toArray(som);
And voila, you got it. Below is an example on how to iterate over such arrays:
public static void print(Object o)
{
if (o instanceof Object[])
{
for (Object ob : (Object[]) o)
{
print(ob);
}
}
else
{
System.out.println(o);
}
}
I success to retrieve java/scala array by using method like traditional netscape.javascript.JSObject in Java8/Scala2.12/Nashorn.
val arr = engine.eval(script) match {
case obj:ScriptObjectMirror =>
if(obj.isArray){
for(i <- 0 until obj.size()) yield obj.getSlot(i)
} else Seq.empty
case unexpected => Seq.empty
}
Using ScriptUtil() may retrieve scalar value such as String, but it seems to cause ClassCastException for Array.
Related
Issue:
I have two test cases each pass when ran independently but when ran together the first one ran will pass and the second one ran will fail. What is even more interesting is if I switch the which test is first (NUnit runs in alphabetical order so adding an 'A' to the test method name does the trick)
Here is some code that reproduces my issue. It is clearly related to the JavaScript manipulation but I dont know why.
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Support.UI;
using System;
namespace POMAuctivaTest.TestSuite
{
[TestFixture]
[Category("test")]
public class Test
{
public IWebDriver driver { get; set; }
string listingUrl = "http://cgi.sandbox.ebay.com/ws/eBayISAPI.dll?ViewItem&item=110194475127#ht_1235wt_1139";
[OneTimeSetUp]
public void setUp()
{
driver = new ChromeDriver();
driver.Manage().Window.Maximize();
}
[Test]
public void test_CounterTest()
{
driver.Navigate().GoToUrl(listingUrl);
WaitForElementVisible(By.CssSelector("#but_v4-28binLnk")); // Buy it now button selector
Assert.IsTrue(IsElementPresent(By.CssSelector("#ngvi_desc_div > div > div > div.aucCounter")), "Counter was not found on page");
}
[Test]
public void test_ScrollingGallery()
{
var listingTitleUsed = "Test Listing Do Not Bid Or Buy";
driver.Navigate().GoToUrl(listingUrl);
WaitForElementVisible(By.CssSelector("#but_v4-28binLnk")); // Buy it now button selector
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
var script = "document.getElementById('csgGallery').style.display='block';";
js.ExecuteScript(script);
WaitForElementVisible(By.CssSelector("div[class=csgTitle]"));
var listingName = getSudoElementContent("div[class=csgTitle]", ":after");
listingName = listingName.Replace("/", "");
listingName = listingName.Replace("\"", "");
Assert.AreEqual(listingTitleUsed, listingName);
}
[OneTimeTearDown]
public void tearDown()
{
driver.Quit();
}
public string getSudoElementContent(string selector, string cssEvent)
{
IJavaScriptExecutor js = (IJavaScriptExecutor)driver;
string script = String.Format("return window.getComputedStyle(document.querySelector('{0}'),'{1}').getPropertyValue('content')", selector, cssEvent);
string content = (String)js.ExecuteScript(script);
return content;
}
public void WaitForElementVisible(By element)
{
var attempts = 0;
while (attempts < 2)
{
try
{
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(90));
wait.Until(ExpectedConditions.ElementIsVisible(element));
break;
}
catch (WebDriverException)
{
attempts++;
}
catch (InvalidOperationException)
{
attempts++;
}
}
if (attempts >= 2)
{
throw new NoSuchElementException("Cannot locate " + element.ToString());
}
}
public bool IsElementPresent(By by)
{
try
{
driver.FindElement(by);
return true;
}
catch (WebDriverTimeoutException)
{
return false;
}
catch (NoSuchElementException)
{
return false;
}
catch (WebDriverException)
{
return false;
}
}
}
}
PS When I test this functionality during nightly tests I use a new listing for each test case.
When test_ScrollingGallery is ran second I get this exception
Test Name: test_ScrollingGallery
Test FullName: POMAuctivaTest.TestSuite.Test.test_ScrollingGallery
Test Source: C:\git\auctiva-webdriver\POMAuctivaTest.TestSuite\Test.cs : line 32
Test Outcome: Failed
Test Duration: 0:00:02.311
Result StackTrace:
at OpenQA.Selenium.Remote.RemoteWebDriver.UnpackAndThrowOnError(Response errorResponse)
at OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Remote.RemoteWebDriver.ExecuteScriptCommand(String script, String commandName, Object[] args)
at OpenQA.Selenium.Remote.RemoteWebDriver.ExecuteScript(String script, Object[] args)
at POMAuctivaTest.TestSuite.Test.test_ScrollingGallery() in C:\git\auctiva-webdriver\POMAuctivaTest.TestSuite\Test.cs:line 38
Result Message:
System.InvalidOperationException : unknown error: Cannot read property 'style' of null
(Session info: chrome=56.0.2924.87)
(Driver info: chromedriver=2.25.426923 (0390b88869384d6eb0d5d09729679f934aab9eed),platform=Windows NT 6.1.7601 SP1 x86_64)
And when I run test_Countertest second I get this error
Test Name: test_CounterTest
Test FullName: POMAuctivaTest.TestSuite.Test.test_CounterTest
Test Source: C:\git\auctiva-webdriver\POMAuctivaTest.TestSuite\Test.cs : line 24
Test Outcome: Failed
Test Duration: 0:00:02.34
Result StackTrace:
at OpenQA.Selenium.Remote.RemoteWebDriver.UnpackAndThrowOnError(Response errorResponse)
at OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
at OpenQA.Selenium.Remote.RemoteWebDriver.FindElement(String mechanism, String value)
at OpenQA.Selenium.Remote.RemoteWebDriver.FindElementByCssSelector(String cssSelector)
at OpenQA.Selenium.By.<>c__DisplayClass1e.<CssSelector>b__1c(ISearchContext context)
at OpenQA.Selenium.By.FindElement(ISearchContext context)
at OpenQA.Selenium.Remote.RemoteWebDriver.FindElement(By by)
at POMAuctivaTest.TestSuite.Test.test_CounterTest() in C:\git\auctiva-webdriver\POMAuctivaTest.TestSuite\Test.cs:line 27
Result Message:
OpenQA.Selenium.NoSuchElementException : no such element: Unable to locate element: {"method":"css selector","selector":"#ngvi_desc_div > div > div > div.aucCounter"}
(Session info: chrome=56.0.2924.87)
(Driver info: chromedriver=2.25.426923 (0390b88869384d6eb0d5d09729679f934aab9eed),platform=Windows NT 6.1.7601 SP1 x86_64)
Why does BrowserFunction does not execute my Javascript code?
Java Code:
public void createBrowserFunctions() {
new BrowserFunction(mapView_Browser, "appi") {
public Object function(Object[] arguments) {
syncCenter((Double) arguments[0], (Double) arguments[1]);
syncZoom((Double) arguments[2]);
syncRotation((Double) arguments[3]);
return null;
}
};
}
private void syncCenter(final Double latitude, final Double longitude) {
LatLng newCenter = new LatLng(latitude.doubleValue(), longitude.doubleValue());
CENTER = newCenter;
fireCenterChanged();
}
private void syncZoom(final Double zoom) {
int newZoom = zoom.intValue();
if (newZoom != this.ZOOM) {
this.ZOOM = newZoom;
fireZoomChanged();
}
}
private void syncCenter(final Double latitude, final Double longitude) {
LatLng newCenter = new LatLng(latitude.doubleValue(), longitude.doubleValue());
CENTER = newCenter;
fireCenterChanged();
}
JavaScript Code:
window._handleBoundsChanged = function() {
if( !_blockEvents ) {
appi( 10, 15, 20, 32 );
}
};
Console logs at the time of execution " _handleBoundsChanged ();" :
osgi> !SESSION 2015-07-20 08:34:10.848 -----------------------------------------------
eclipse.buildId=unknown
java.version=1.8.0_45
java.vendor=Oracle Corporation
BootLoader constants: OS=win32, ARCH=x86_64, WS=win32, NL=pl_PL
Command-line arguments: -dev
!ENTRY org.eclipse.rap.ui 4 0 2015-07-20 08:34:21.540
!MESSAGE Unhandled event loop exception
!STACK 0
org.eclipse.swt.SWTException: Failed to evaluate Javascript expression
at org.eclipse.swt.browser.Browser.createException(Browser.java:606)
at org.eclipse.swt.browser.Browser.evaluate(Browser.java:344)
at hc.tool.mapviewer.rap.OLMap.setZoom(OLMap.java:67)
at hc.tool.mapviewer.rap.View$1.widgetSelected(View.java:58)
at org.eclipse.swt.widgets.TypedListener.handleEvent(TypedListener.java:262)
at org.eclipse.swt.widgets.EventTable.sendEvent(EventTable.java:86)
at org.eclipse.swt.widgets.Widget.sendEvent(Widget.java:708)
at org.eclipse.swt.widgets.Widget.notifyListeners(Widget.java:610)
at org.eclipse.swt.widgets.Display.executeNextEvent(Display.java:1216)
at org.eclipse.swt.widgets.Display.runPendingMessages(Display.java:1197)
at org.eclipse.swt.widgets.Display.safeReadAndDispatch(Display.java:1180)
at org.eclipse.swt.widgets.Display.readAndDispatch(Display.java:1172)
at org.eclipse.ui.internal.Workbench.runEventLoop(Workbench.java:2733)
at org.eclipse.ui.internal.Workbench.runUI(Workbench.java:2694)
at org.eclipse.ui.internal.Workbench.access$5(Workbench.java:2530)
at org.eclipse.ui.internal.Workbench$5.run(Workbench.java:701)
at org.eclipse.core.databinding.observable.Realm.runWithDefault(Realm.java:337)
at org.eclipse.ui.internal.Workbench.createAndRunWorkbench(Workbench.java:684)
at org.eclipse.ui.PlatformUI.createAndRunWorkbench(PlatformUI.java:157)
at hc.tool.mapviewer.rap.Application.start(Application.java:18)
at org.eclipse.rap.ui.internal.application.EntryPointApplicationWrapper.createUI(EntryPointApplicationWrapper.java:38)
at org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle.createUI(RWTLifeCycle.java:171)
at org.eclipse.rap.rwt.internal.lifecycle.RWTLifeCycle$UIThreadController.run(RWTLifeCycle.java:283)
at java.lang.Thread.run(Unknown Source)
at org.eclipse.rap.rwt.internal.lifecycle.UIThread.run(UIThread.java:104)
I am eager to method provided data from JavaScript to Java.
I count on your help , thanks.
So the evaluation is failing at Browser:344, after OLMap:67 is called. You should dig into those two spots to see what is going on.
I am ready to bet that OLMap:67 is your function(Object []), and that the ultimate cause is that you are attempting to cast Object (possibly some kind of JSObject) to java Double without any type of conversion.
I have an Rx stream that is an outgoing change feed from a particular component.
Occasionally I want to be able to enter a batch mode on the stream so that items passed to onNext accumulate and are passed on only when the batch mode is exited.
Normally I'll pass single items through the stream:
stream.onNext(1);
stream.onNext(2);
There is a 1-to-1 mapping between the items passed to onNext and the items received in the subscribe, so the previous snippet results in two calls to subscribe with the the values 1 and 2.
The batch mode I am looking for might work something like this:
stream.enterBatchMode();
stream.onNext(1);
stream.onNext(2);
stream.exitBatchMode();
In this case I want subscribe to only be invoked once with the single concatenated array [1, 2].
Just to reiterate, I need batch mode only sometimes, at other times I pass the non-concatenated items through.
How can I achieve this behaviour with Rx?
NOTE: Previously I was passing arrays through onNext although this is mostly so that the type remains the same when in individual mode and when in batch-mode.
Eg:
stream.onNext([1, 2, 3]);
stream.onNext([4, 5, 6]);
Subscribe receives [1, 2, 3] then [4, 5, 6], but when in batch-mode subscribe receives the concatenated results [1, 2, 3, 4, 5, 6].
So when you look at it this way it's more like unconcatenated and concatenated modes (as opposed to individual and batch-mode). But I think the problem is very similar.
Here is the spirit of James World's c# solution converted to JavaScript and tailored to your specific question.
var source = new Rx.Subject();
source.batchMode = new Rx.BehaviorSubject(false);
source.enterBatchMode = function() { this.batchMode.onNext(true); };
source.exitBatchMode = function() { this.batchMode.onNext(false); };
var stream = source
.window(source.batchMode
.distinctUntilChanged()
.skipWhile(function(mode) { return !mode; }))
.map(function(window, i) {
if ((i % 2) === 0) {
// even windows are unbatched windows
return window;
}
// odd windows are batched
// collect the entries in an array
// (e.g. an array of arrays)
// then use Array.concat() to join
// them together
return window
.toArray()
.filter(function(array) { return array.length > 0; })
.map(function(array) {
return Array.prototype.concat.apply([], array);
});
})
.concatAll();
stream.subscribe(function(value) {
console.log("received ", JSON.stringify(value));
});
source.onNext([1, 2, 3]);
source.enterBatchMode();
source.onNext([4]);
source.onNext([5, 6]);
source.onNext([7, 8, 9]);
source.exitBatchMode();
source.onNext([10, 11]);
source.enterBatchMode();
source.exitBatchMode();
source.onNext([12]);
source.enterBatchMode();
source.onNext([13]);
source.onNext([14, 15]);
source.exitBatchMode();
<script src="https://getfirebug.com/firebug-lite-debug.js"></script>
<script src="https://rawgit.com/Reactive-Extensions/RxJS/master/dist/rx.all.js"></script>
James' comment may provide the answer you're looking for, but I'd like to offer a simple alternative that you may also like.
I read your question as this: "how do I pass a value of type A or type B"?
Answer: Define a type with "either" semantics, which contains data of either type A or type B.
var valueOrBuffer = function(value, buffer)
{
this.Value = value;
this.Buffer = buffer;
};
var createValue = function(value) { return new valueOrBuffer(value); }
var createBuffer = function(buffer) { return new valueOrBuffer(undefined, buffer); }
stream.OnNext(createValue(1));
stream.OnNext(createValue(2));
stream.OnNext(createValue(3));
stream.OnNext(createBuffer([4, 5, 6]));
No switching necessary. Your observer may have to change a bit though:
stream.Subscribe(function(v) {
if (v.Value)
onNextValue(v.Value);
else
onNextBuffer(v.Buffer);
});
A solution... of sorts. After failing to achieve anything with the Window function (from James' linked example) I came up with the following solution, although it is in C# and I have no idea how to translate it to RxJS and Javascript.
If someone can better this using built-in Rx functions, please do so, I'll very much appreciate a better solution.
I have implemented a BatchedObservable class that is an observer of type T and an observable of type IEnumerable T. In normal-mode it wraps each T in an array that is passed on. In batch-mode it accumulates Ts until batch-mode is exited, then it passes on the collected list of batched Ts.
public class BatchedObservable<T> : IObservable<IEnumerable<T>>, IObserver<T>
{
bool batching = false;
List<T> batchedItems;
List<IObserver<IEnumerable<T>>> observers = new List<IObserver<IEnumerable<T>>>();
public void EnterBatchMode()
{
batching = true;
batchedItems = new List<T>();
}
public void ExitBatchMode()
{
batching = false;
if (batchedItems.Count > 0)
{
foreach (var observer in observers)
{
observer.OnNext(batchedItems);
}
}
batchedItems = null;
}
public IDisposable Subscribe(IObserver<IEnumerable<T>> observer)
{
observers.Add(observer);
return Disposable.Create(()
=> observers.Remove(observer)
);
}
public void OnCompleted()
{
foreach (var observer in observers)
{
observer.OnCompleted();
}
}
public void OnError(Exception error)
{
foreach (var observer in observers)
{
observer.OnError(error);
}
}
public void OnNext(T value)
{
if (batching)
{
batchedItems.Add(value);
}
else
{
foreach (var observer in observers)
{
observer.OnNext(new T[] { value });
}
}
}
}
Example usage follows. When running, hit any key to toggle batch mode.
static void Main(string[] args)
{
var batched = new BatchedObservable<long>();
var dataStream = Observable.Interval(TimeSpan.FromSeconds(1));
dataStream.Subscribe(batched);
batched.Subscribe(PrintArray);
var batchModeEnabled = false;
while (true)
{
Console.ReadKey();
batchModeEnabled = !batchModeEnabled;
if (batchModeEnabled)
{
batched.EnterBatchMode();
}
else
{
batched.ExitBatchMode();
}
}
}
private static void PrintArray<T>(IEnumerable<T> a)
{
Console.WriteLine("[" + string.Join(", ", a.Select(i => i.ToString()).ToArray()) + "]");
}
So I'm playing around with emscripten and making linked lists. I started with the following code in C -
struct Node {
struct Node *next;
char *value;
};
extern struct Node *ll_new_node(char *value) {
struct Node *node;
node = malloc(sizeof(struct Node));
node->value = value;
return node;
}
extern struct Node *ll_add(struct Node *root, char *value) {
struct Node *node = ll_new_node(value);
if (root == NULL) {
root = node;
} else {
tail(root)->next = node;
}
return root;
}
//....
In the page I'm calling the code this way -
var new_node = Module.cwrap('ll_new_node', 'object', ['string'])
var add = Module.cwrap('ll_add', 'object', ['object', 'string']);
var list = 0;
Zepto(function($){
var printList = function() {
var node = list;
var s = "head -> ";
while (node != null && node != 0) {
s = s + value(node) + " -> ";
node = next(node);
}
s = s + "NULL";
$('#display').text(s);
};
printList();
$('#addBtn').on('click', function() {
var add_value = $('#itemField').val();
if (add_value && add_value.length > 0) {
list = add(list, add_value);
printList();
}
});
//...
});
What was happening was if I put "A" in the text box and added it to the list, "head -> A -> NULL" would print. Then if I but "B" in the text box and added it to the list, "head -> B -> B -> NULL" would print. The same pattern continued as more nodes were added. It was adding the new node, but it seems all node->value fields pointed to a shared string reference.
I made some test code in C to build and print a list, and it worked as expected with no changes. I ended up fixing it via emscripten by using strcpy on the string passed into ll_new_node and assigning the copy to node->value.
Is this "expected behavior" or a bug? Is the "copied reference" to the string due to some problem with how I'm using it on the JavaScript side? Is there some way to tell Emscripten not to do this?
I get this error when I try to run some JS code (this + this) with V8 (tried master from two weaks ago, 3.23.17, 3.24.40, 3.25.5; 3.23.0 doesn't work anymore because of API changes):
#
# Fatal error in ..\..\src\runtime.cc, line 785
# CHECK(V8::ArrayBufferAllocator() != NULL) failed
#
A lot of other JS code has worked already, so I wonder what the problem is.
It's on Win8 with a x64 build. V8 has been build just as described in the official docs (using gyp + MSVC 2012). I don't think that there was a problem because it worked fine already with most other JS code.
I think that there might be an issue with V8 itself, but not sure...
I also asked on the mailing-list here.
Some C++ code, but I don't think that there is a problem with it because it worked fine with other JS code:
#include <string>
#include <assert.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <boost/noncopyable.hpp>
#include <v8.h>
// Create a new isolate (completely isolated JS VM).
struct V8Isolate : boost::noncopyable {
v8::Isolate* isolate;
V8Isolate() : isolate(v8::Isolate::New()) {}
~V8Isolate() { isolate->Dispose(); }
operator v8::Isolate*() { return isolate; }
v8::Isolate* operator->() { return isolate; }
};
struct ReturnType {
std::string err; // non-empty if there is an error
ReturnType(bool success) {
assert(success);
}
ReturnType(const std::string& _err) : err(_err) {
assert(!err.empty());
}
ReturnType(const char* _err) : err(_err) {
assert(!err.empty());
}
operator bool() const { return err.empty(); }
};
#define CHECK_RETURN(cmd) { ReturnType ret = (cmd); if(!ret) return ret; }
using namespace std;
using namespace v8;
ReturnType readFile(const std::string& filename, std::string& res) {
res = "";
FILE* f = fopen(filename.c_str(), "r");
if(!f) return "File '" + filename + "' cannot be opened";
while(!feof(f) && !ferror(f)) {
char buffer[1024 * 8];
size_t s = fread(buffer, 1, sizeof(buffer), f);
if(s > 0)
res.append(buffer, buffer + s);
}
auto err = ferror(f);
fclose(f);
if(err)
return "Error while reading file '" + filename + "'";
return true;
}
ReturnType execJsFile(const std::string& jsSourceDir, const std::string& extfilename) {
v8::TryCatch try_catch;
std::string sourceStr;
CHECK_RETURN(readFile(jsSourceDir + "/" + extfilename, sourceStr));
Local<String> origin = String::NewFromUtf8(Isolate::GetCurrent(), &extfilename[0], String::kNormalString, (int)extfilename.size());
Local<String> source = String::NewFromUtf8(Isolate::GetCurrent(), &sourceStr[0], String::kNormalString, (int)sourceStr.size());
Local<v8::Script> script = Script::Compile(source, origin);
if(script.IsEmpty()) {
assert(try_catch.HasCaught());
return "JS compile failed: " + jsObjToString(try_catch.Exception());
}
// Run the script to setup its environment
Local<Value> result = script->Run();
if(result.IsEmpty()) {
assert(try_catch.HasCaught());
return "JS script execution failed: " + jsReportExceptionToString(Isolate::GetCurrent(), &try_catch);
}
return true;
}
ReturnType loadJsGit() {
V8Isolate isolate;
v8::Isolate::Scope isolateScope(isolate);
HandleScope handleScope(isolate);
Handle<Context> context = Context::New(isolate);
Context::Scope contextScope(context);
auto globalObj = context->Global();
CHECK_RETURN(execJsFile(".", "global.js"));
CHECK_RETURN(execJsFile(".", "jsgit.js"));
return true;
}
int main(int argc, char** argv) {
ReturnType ret = loadJsGit();
if(!ret) cout << "error: " << ret.err << endl;
}
You need to initialize array buffer allocator.
Use malloc, for example:
class MallocArrayBufferAllocator : public v8::ArrayBuffer::Allocator {
public:
virtual void* Allocate(size_t length) { return malloc(length); }
virtual void* AllocateUninitialized(size_t length) { return malloc(length); }
virtual void Free(void* data, size_t length) { free(data); }
};
Initialize:
v8::V8::SetArrayBufferAllocator(new MallocArrayBufferAllocator);