Working with Native Objects & Data

From MediaMonkey Wiki
Jump to navigation Jump to search

In MediaMonkey 5, data from the database is managed by native objects called Shared Objects. Unless your addon requires heavy customization, you will not need to handle database calls yourself.

Concepts

Shared

These objects are not normal JavaScript objects with properties. They contain native (compiled) code and their properties are accessible from all windows. So unlike normal JS objects, changing one of their properties will cause it to be immediately updated everywhere else in the code. It's an advantage of the way MM5 was programmed - It lets us do all our intensive database operations in multithreaded native code, freeing up the JavaScript thread to just handle UI.

If you open the Chromium dev tools (on a debug build, by right click > Inspect Element, and print out one of the shared objects to the console, you'll notice that it says "native code" instead of showing a JavaScript function declaration.

> trackList.forEach
< ƒ forEach() { [native code] }

Read/Write Locks

In order for these database operations to be multithreaded, we need to implement a concept called read locks and write locks. Consider what could happen if one thread attempted to write while another thread attempted to read data. The thread attempting a read will have no guarantee that the data is intact. It could be read either before the new value is written or after, or it could be read at the exact moment while the data is being overwritten, which would produce garbage data.

How do we avoid that issue? We prevent reads and writes from happening at the same time. Before writing data, the thread will wait for all readers to finish, and then it writes. Conversely, before reading data, the thread will wait for a write to finish before starting. Think of it like a stop light at a highway. Reads are like the highway with multiple lanes; Multiple read cars can pass at once, and while read cars are passing, write cars have to stop. Once the reads finish, the write car can pass through.

SharedList.locked() enters a read lock. If you need to access individual items of a list, and need to work with indices, use locked().

trackList.locked(function () {
	// This code will execute as soon as a read lock has been acquired.
	// There is NO GUARANTEE that it will execute immediately.
	var firstTrack = trackList.getValue(0);
	var lastTrack = trackList.getValue(trackList.count - 1);
});

SharedList.modifyAsync() enters a write lock. If you need to modify tracks in a list, use modifyAsync(). Note: You are allowed to read values in a write lock. It will simply prevent other threads from reading that list until you are done.

trackList.modifyAsync(function () {
	// This code will execute as soon as a write lock has been acquired.
	// There is NO GUARANTEE that it will execute immediately.
	var firstTrack = trackList.getValue(0);
	firstTrack.custom1 = "Hello World";
});

If you do not need to access list items with indices, use the more convenient SharedList.forEach() function. It will execute your callback for each item in the list, and it will automatically wait for a read lock.

trackList.forEach(function (track) {
	// This code will execute as soon as a write lock has been acquired.
	// There is NO GUARANTEE that it will execute immediately.
	console.log(track.title);
});

Methods like clear() and selectRangeAsync() automatically enter write locks, so you do not need to call modifyAsync().

For performance reasons, please use asynchronous methods (with "Async" at the end of the method name) as much as possible, especially if you are processing a lot of data. Unlike the native objects, JavaScript only has one thread, so if you do a lot of processing, it will block the rest of the UI.

There is one exception to the above rule: If you need to retrieve all values of one particular property in a large list (For example: a list of all track titles), consider using the synchronous method getAllValues(). It is much faster than using forEach().

var listOfTitles = trackList.getAllValues('title');

TL;DR:

  1. SharedLists work differently from normal JavaScript arrays and objects.
  2. For most cases, you can use forEach() to get data from each item in the list.
  3. To modify contents of a list, use modifyAsync().
  4. Don't do tons of calculations / operations in a synchronous function; Make your code as asynchronous as possible.

More coming soon