Skip to main content

News

Topic: Link.js v0.4.2 (Read 98291 times) previous topic - next topic

0 Members and 4 Guests are viewing this topic.
  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #240

Just noticed this edit (WHY do the forums not add edited posts to the unread list?).


Sorry I have a bad habit of editing over double-posting when it's been a short time since the last time I posted. I probably should double-post just so it can be seen.


If the filtered data doesn't contain the item I ask for, I WANT it to return -1.


Yeah, I was just thinking of potential side-effects. I'm not too sure if they would cause issues or not. Maybe I was over-thinking.


Also, I found a clever use of Link recently:
Code: (javascript) [Select]
// mini.Console.unregister()
// Unregisters a previously-registered entity.
// Arguments:
//     name: The name of the entity as passed to mini.Console.register().
mini.Console.unregister = function(name)
{
this.commands = mini.Link(this.commands)
.where(function(command) { return command.entity != name; })
.toArray();
};



Cool! I love these approaches in GC'd languages, they are simple, fun and you don't have to worry about garbage arrays piling up. (.toArray() makes a new array each time, but I think that's fine in these cases).

Oh, I've added a recursive mode and other fixes and features to Link, so stay tuned!
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #241
So, new features.

1. .indexOf() now takes a predicate.
Code: (javascript) [Select]
var index = Link(array).indexOf(function(item) { return item.name == "Tom"; })


2. .groupBy() now has a container flag.
Code: (javascript) [Select]
var group = Link(array).groupBy(function(item) { return item.key; }, true); // true = as array


3. recurse() will iterate throughout an array of objects with arrays of objects in them.

Code: (javascript) [Select]

// say you get back the contents of the filesystem, folders with files and subfolders with files.
var structure = [{
    items: [{ items: [...] }, { items: [...] }, ...]
}, {
    items: [...]
}];

// you can easily iterate over and load them using 'recurse'
Link(structure).recurse('items').each(function(item) { LoadFile(item); });


These are still part of v3.0 which is still in beta testing. Recurse itself is still experimental. I've used it in a website that had a hierarchical navigation menu and it worked well enough. Of course more type checking and the like would help, so there's always more to fix.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Link.js v0.3.0
Reply #242
That recursive mode is awesome.  I've actually had a few cases where such a thing would be useful, so good work. :D
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #243
I hate seeing stray for loops, but there are two areas where they still seem necessary, despite using Link.

A) When you are deleting something:
Code: (javascript) [Select]

for (...) {
    if (condition) { array.splice(i, 1); i--; }
}


B) When you are computing something.
Code: (javascript) [Select]

for (var i = 0; i < 12; ++i) {
    DrawText(0, 16 * i);
}


Case B can be mitigated using lists of items and iterating over them, but I can still create generators for Link to mitigate these cases where you are doing a raw loop 'n' times.

But, most importantly A was counter-intuitive to Link. But I don't think that has to be the case.

Introducing: Remove
I leverage the Link chain and make an end-element such that whatever values survive until the end, can get gobbled up. I think the implementation can be improved upon, but I do utilize a neat trick. I walk the line backwards when removing elements, this works since the indices I receive from Link are in sorted order.

Now you can do this:
Code: (javascript) [Select]

var a = [1, 2, 3, 0, 4, 5, 8, 9, 7, 0];
Link(a).where(even).remove();
a; // [1, 3, 4, 8, 0]


I'm still testing the code, and I'll push it later today. Maybe I can make it faster? But ultimately it's going to make it easier to remove things, such that human induced error goes down.

BTW, this caused me to do it:
Code: (javascript) [Select]

mini.Delegate.prototype.remove = function(o, method)
{
for (var i = 0; i < this.invokeList.length; ++i) {
if (o == this.invokeList[i].o && method == this.invokeList[i].method) {
this.invokeList.splice(i, 1);
break;
}
}
};


Imagine writing:
Code: (javascript) [Select]

mini.Delegate.prototype.remove = function(o, method)
{
mini.Link(this.invokeList).where(function(item) { return o == item.o && method == item.method; }).remove();
};


Wait... I just realized now that you intended to remove the first item it sees (hence the break), well we can now do that with the new functional parameter for .indexOf:

Code: (javascript) [Select]

mini.Delegate.prototype.remove = function(o, method)
{
var index = mini.Link(this.invokeList).indexOf(function(item, i) { return o == item.o && method == item.method; });
if (index >= 0) this.invokeList.splice(index, 1);
};


But that's good for one at a time, whereas now you can do lists of removals (which ought to be faster than looking up indices each time). :)
  • Last Edit: May 11, 2015, 06:18:41 pm by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Link.js v0.3.0
Reply #244
That delegate code is rather old and was originally written before Link was a thing.  I just didn't bother updating it when I adapted it.  Generally I now know to do search-and-remove like this:

Code: (javascript) [Select]
array = Link(array)
    .where(function(item) { return item != toRemove; })
    .toArray();


So long as it's not important for the array to maintain its identity (and it usually isn't), this works great.  That said, if your .remove() method can remove stuff in-place a la splice(), that's of course even better. :D

Incidentally, do you know how much more awesome Link would be if we would get Fat Arrow support already?  All those "function"s everywhere clutter everything up.  I hadn't noticed it before, until I started working with promises, where you're passing anonymous functions all over the place.  A proper lambda operator would be a lifesaver.
  • Last Edit: May 11, 2015, 07:51:31 pm by Lord English
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #245

That delegate code is rather old and was originally written before Link was a thing.  I just didn't bother updating it when I adapted it.  Generally I now know to do search-and-remove like this:

Code: (javascript) [Select]
array = Link(array)
    .where(function(item) { return item != toRemove; })
    .toArray();



I know, but my solution is a little nicer in the fact we keep the original array reference, it's faster than rebuilding a list (esp. if it's long) and we aern't throwing the old reference away to get gc'd each time. But I will say your approach above is faster on smaller lists. Link was designed for any sized list though, so I was thinking about millions of items when I wrote this.


Incidentally, do you know how much more awesome Link would be if we would get Fat Arrow support already?  All those "function"s everywhere clutter everything up.  I hadn't noticed it before, until I started working with promises, where you're passing anonymous functions all over the place.  A proper lambda operator would be a lifesaver.


I know! It's in ES6, I'd love to see it too. It'll make Link feel much closer to LINQ. :P
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Link.js v0.3.0
Reply #246
Is there any possibility of making .each() chainable?  Such that this:
Code: (javascript) [Select]
mini.Link(this.threads)
.where(function(thread) { return thread.id == threadID })
.each(function(thread) { thread.isValid = false; });

mini.Link(this.threads)
.where(function(thread) { return thread.id == threadID })
.remove();


Could become this:
Code: (javascript) [Select]
mini.Link(this.threads)
.where(function(thread) { return thread.id == threadID })
.each(function(thread) { thread.isValid = false; });
.remove();


The above step (setting thread.isValid to false) is required so that if a thread is killed in the middle of an update cycle (which runs over a copy of the array), it doesn't get erroneously updated again.  But since each() is an endpoint, I have to do it as two operations.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #247
I like what you propose, I too run into these situations. I was actually thinking about this earlier today at work (funnily enough also due to the addition of remove). The only issue I see here is that as soon as an endpoint is called, the chain gets executed. To get around this I could create a new method or involve a parameter.

These are the options:
Code: (javascript) [Select]

Link(array).execute(function() { /* work */ }).remove();

Link(array).each(function() { /* work */ }, true).remove();


I'll likely implement both, but I'm concerned with the .each because what other use could I use a parameter for? Usually in an each or foreach method in JS the second parameter means to use that context. But we have .bind() for that now. And I don't want to remove a feature after introducing it. So here's what are you okay with .execute()? All methods are Link.alias()-able in the system, so you could rename it.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Link.js v0.3.0
Reply #248
.execute() is fine.  Although maybe .exec() would be more succinct?  I agree about .each() though, the extra parameter is unnecessary and not self-documenting at all.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #249
Okay, I added .execute().

Also, I made a new branch called no-runners it's a version of Link that strips the runners potion away. It reduces the size of the library and I've since seen no discernable impact in the modern browsers with the runners off.

The runners used to kick-start a loop by suggesting to the compiler that it can inline the first calls of a chain of commands. Instead of going at least one level into the chain to being using where() map(), etc. it would do it right then and there.

Code: (javascript) [Select]

// a runner:
while (...) {
    if (where(a[i], i)) a[i].next.exec(a[i], i);
    i++;
}

// a .exec() method in a node:
this.start.exec();


The runner was more code, but it actually did improve things. Each node that needed kick-starting had a .run() method where you can tell I inline the behavior of the node right inside the logical area of the loop.

Nowadays JIT's have gotten so good, I'm now not noticing much of an impact removing them. So, it's still experimental, but the branch will be the new 0.4.0 once it's ready.

I think I'll release v0.3.0 soon and start supporting it up until I release v0.4.0 which will receive features on it's own.
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Link.js v0.3.0
Reply #250
I'd be curious to see a benchmark comparison between the normal and no-runners version under minisphere, just to see if there's any discernible difference on non-JIT engines like Duktape.
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Link.js v0.3.0
Reply #251
So, I've been using the new features quite a bit.  Well, except for the recursive support, I haven't managed to shoehorn in a use case for that yet. :P

But .execute() and .remove() are great:
Code: (c) [Select]
var itemCount = 0;
mini.Link(this.items)
.filterBy('itemID', itemID)
.execute(function(usable) { itemCount += usable.usesLeft })
.remove();
if (itemCount > 0)
mini.Console.write(itemCount + "x " + Game.items[itemID].name
+ " deleted from " + this.name + "'s inventory");
else
mini.Console.write("No " + Game.items[itemID].name + " in " + this.name + "'s inventory");

  • Last Edit: May 20, 2015, 12:29:12 pm by Lord English
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #252
This is the thickest piece of code I wrote, it's using a new clone method to do a shallow clone of the item in question. I say shallow clone because debatably, a deep clone is really very hard to accomplish in JS (and you could write your own in a map function anyways, besides the best method is that you program a .clone() into the prototype of each object you intend to clone). This shallow clone is more for plain old javascript objects that usually contain raw data (IE: primitive types like numbers, strings, and booleans).

Code: (javascript) [Select]

// create flattened array of items whose sub-items get linked to parent items:
var array = Link($scope.menu).execute(function(item) { item.level = 0; }).recurse("items").clone().map(function(item, idx) {
item.is_parent = item.items.length > 0;
item.index = idx;
Link(item.items).each(function(inner) { inner.level = item.level + 1; });
delete item['items']; // don't need this around anymore.
return item;
}).toArray();


It's for a product menu for a website I'm building. You have categories of items and this flattens the array so it can be stored sequentially into a database. It keeps track of who the parent is and strips away children that don't need to be sent to the database.

It's so dang complex that I delete items from an item. But that's okay since I've used recursion before the clone and since it's a shallow copy, modifications to the inner items array are copied through the next cycle. It's a mind-blowing show-stealing piece of terrible crap that's highly, highly unreadable and can kill at any time if I change the core mechanics of clone or recurse in Link.js so don't try this at home. But I'll still use this since it is so damn efficient.
  • Last Edit: May 31, 2015, 04:23:52 am by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here

  • Fat Cerberus
  • [*][*][*][*][*]
  • Global Moderator
  • Sphere Developer
Re: Link.js v0.3.0
Reply #253
Haha, wow, I couldn't even begin to understand what that code is doing if it weren't for your explanation of it.  If you didn't need to flatten the array, I was going to say, I made my own function a while back for deep-cloning POJOs recursively.  It even preserves circular refs:

Code: (javascript) [Select]
// clone() function
// Creates a deep copy of an object, preserving circular references.
// Arguments:
//     o: The object to clone.
// Returns:
//     The new, cloned object.
function clone(o)
{
var clones = arguments.length >= 2 ? arguments[1] : [];
if (typeof o === 'object' && o !== null) {
for (var i = 0; i < clones.length; ++i) {
if (o === clones[i].original) {
return clones[i].dolly;
}
}
var dolly = o instanceof Array ? []
: 'clone' in o && typeof o.clone === 'function' ? o.clone()
: {};
clones.push({ original: o, dolly: dolly });
if (o instanceof Array || !('clone' in o) || typeof o.clone !== 'function') {
for (var p in o) {
dolly[p] = clone(o[p], clones);
}
}
return dolly;
} else {
return o;
}
}


Notice that if any object in the tree includes a .clone() method, it calls that instead.  It's been quite useful in Specs for copying, e.g. item definitions and such from the gamedef.
  • Last Edit: May 31, 2015, 12:28:10 pm by Lord English
neoSphere 5.9.2 - neoSphere engine - Cell compiler - SSj debugger
forum thread | on GitHub

  • Radnen
  • [*][*][*][*][*]
  • Senior Staff
  • Wise Warrior
Re: Link.js v0.3.0
Reply #254
Casually continuing after many months, I needed the shallow clone in that last post (to keep loose references to child arrays).

Anyways, I've been thinking about cloning and wondered what more I can do to it. I've been thinking about creating a Sphere data storage API. It's interesting because we can use it to standardize save files in Sphere, and not just for games, but for data. Like I use link.js to do a LINQ-to-SQL like implementation in JS for Sphere.

Code: (javascript) [Select]

// set up a database
var enemies = new Link.db({ idfield: "_id" });

// get an id of an item put into it:
var ID = DbLink(enemies).put({ name: "rodent", atk: 5, def: 2 }).getLastId();

// grab that item you added:
var enemy = DbLink(enemies).get({ _id: ID }); // a new instance of an enemy

// But instead of working on objects, you always get clones back.
// Before, where gives you the object. Now, it gives you a copy of the data you stored in the database. To update it, you'll have to do things like:

DbLink(enemies).where({ _id: ID }).update({ name: "rodent2" }); // updates all that match

// might also use?
DbLink(enemies).update({ _id: ID }, { name: "rodent2" }); // matching in the update method

// then we can remove it, completing the CRUD:
DbLink(enemies).where({ _id: ID }).delete();

// or:
DbLink(enemies).delete({ _id: ID });


Notice it's more JSON based than before. I like no-sql like document-driven databases. It may seem like reinventing the wheel for those who use MongoDB or CouchDB etc. But I think we can make a Sphere centric DB and I get to use more JSON in my Link library. I'll need to think a bit more about the API, but I see this as a natural next step. This will of course, amend Link.js as a separate library (or not, I mean get is the same as where, except that you get a copy, it can fit in nicely with the overall structure, and there was no way to put something into it before).

I've called it DbLink as a test, it may just amend Link, nothing more.
  • Last Edit: September 29, 2015, 01:03:55 pm by Radnen
If you use code to help you code you can use less code to code. Also, I have approximate knowledge of many things.

Sphere-sfml here
Sphere Studio editor here