/*
* Copyright (C) 2011 Canonical, Ltd.
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License
* version 3.0 as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*
* Authored by Neil Jagdish Patel
*
*/
namespace Unity {
/**
* Destroy an object.
*
* Unreferences object returned by one of unity_*_new constructors.
*/
public static void object_unref (void* object)
{
Object* o_ptr = (Object*) object;
// we don't want people using unity_object_unref for non Unity classes
if (o_ptr is Object && o_ptr->get_type ().name ().has_prefix ("Unity"))
{
delete o_ptr;
}
else
{
critical ("Unable to unref object, invalid object type");
}
}
namespace Internal {
/* Cateories: [id, display_name, icon, renderer_name, hints] */
internal const string[] CATEGORIES_SCHEMA = {"s", "s", "s", "s", "a{sv}"};
internal enum CategoryColumn
{
ID,
DISPLAY_NAME,
ICON_HINT,
RENDERER_NAME,
HINTS,
N_COLUMNS
}
/* Filters: [id, display_name, icon, renderer_name, hints,
visible, collapsed, filtering] */
internal const string[] FILTERS_SCHEMA = {"s", "s", "s", "s", "a{sv}",
"b", "b", "b"};
internal enum FilterColumn
{
ID,
DISPLAY_NAME,
ICON_HINT,
RENDERER_NAME,
RENDERER_STATE,
VISIBLE,
COLLAPSED,
FILTERING,
N_COLUMNS
}
/* Results: [uri, icon, category, result_type, mimetype,
title, comment, dnd_uri, metadata] */
internal const string[] RESULTS_SCHEMA = {"s", "s", "u", "u", "s",
"s", "s", "s", "a{sv}"};
internal const string[] RESULTS_COLUMN_NAMES = {
"uri", "icon_hint", "category", "result_type", "mimetype",
"title", "comment", "dnd_uri", "metadata"};
internal enum ResultColumn
{
URI,
ICON_HINT,
CATEGORY,
RESULT_TYPE,
MIMETYPE,
TITLE,
COMMENT,
DND_URI,
METADATA,
N_COLUMNS
}
internal const string VAR_MEASURED_SEARCHES = "LIBUNITY_TIME_SEARCHES";
internal const string VAR_SYNC_DBUS_SEARCHES = "LIBUNITY_SYNC_DBUS";
internal const string SEARCH_FILTER_ROW_HINT = "changed-filter-row";
internal const string SEARCH_SEQNUM_HINT = "model-seqnum";
internal const string SEARCH_TIME_HINT = "search-time";
internal const string SEARCH_SUBSCOPES_HINT = "subscopes-filter";
internal const string SEARCH_NO_RESULTS_HINT = "no-results-hint";
internal const string ACTIVATE_PREVIEW_ACTION_HINT = "preview-action-id";
namespace Utils
{
[Compact]
private class DelegateWrapper
{
public SourceFunc callback;
public DelegateWrapper (owned SourceFunc cb)
{
callback = (owned) cb;
}
}
internal class AsyncOnce
{
private enum OperationState
{
NOT_STARTED,
IN_PROGRESS,
DONE
}
private G inner;
private OperationState state;
private DelegateWrapper[] callbacks = {};
public AsyncOnce ()
{
state = OperationState.NOT_STARTED;
}
public unowned G get_data ()
{
return inner;
}
public bool is_initialized ()
{
return state == OperationState.DONE;
}
public async bool enter ()
{
if (state == OperationState.NOT_STARTED)
{
state = OperationState.IN_PROGRESS;
return true;
}
else if (state == OperationState.IN_PROGRESS)
{
yield wait_async ();
}
return false;
}
public void leave (G result)
{
if (state != OperationState.IN_PROGRESS)
{
warning ("Incorrect usage of AsyncOnce");
return;
}
state = OperationState.DONE;
inner = result;
notify_all ();
}
/* Once probably shouldn't have this, but it's useful */
public void reset ()
{
state = OperationState.NOT_STARTED;
inner = null;
}
private void notify_all ()
{
foreach (unowned DelegateWrapper wrapper in callbacks)
{
wrapper.callback ();
}
callbacks = {};
}
private async void wait_async ()
{
callbacks += new DelegateWrapper (wait_async.callback);
yield;
}
}
internal class AsyncMutex
{
private bool is_locked;
private GLib.Queue callbacks;
public AsyncMutex ()
{
callbacks = new GLib.Queue ();
is_locked = false;
}
public bool try_lock ()
{
if (!is_locked)
{
is_locked = true;
return true;
}
return false;
}
public async void lock ()
{
if (!is_locked)
{
is_locked = true;
return;
}
yield wait_async ();
is_locked = true;
}
public void unlock ()
{
if (!is_locked)
{
warning ("Unlock failed: AsyncMutex was already unlocked");
return;
}
is_locked = false;
notify ();
}
private void notify ()
{
if (callbacks.is_empty ()) return;
DelegateWrapper? cb = callbacks.pop_head ();
cb.callback ();
}
private async void wait_async ()
{
callbacks.push_tail (new DelegateWrapper (wait_async.callback));
yield;
}
}
internal static async void wait_for_model_synchronization (Dee.SharedModel model)
{
if (model.is_synchronized ()) return;
var sig_id = model.notify["synchronized"].connect (() =>
{
if (model.is_synchronized ())
{
wait_for_model_synchronization.callback ();
}
});
if (sig_id == 0)
{
critical ("Internal error, unable to wait for synchronization");
return;
}
yield;
SignalHandler.disconnect (model, sig_id);
}
internal static Variant hash_table_to_asv (HashTable hash)
{
var b = new VariantBuilder (new VariantType ("a{sv}"));
var iter = HashTableIter (hash);
string key;
Variant val;
while (iter.next (out key, out val))
{
b.add ("{sv}", key, val);
}
return b.end ();
}
internal static string icon_to_string (Icon? icon)
{
return icon != null ? icon.to_string () : "";
}
namespace Diff
{
internal delegate bool ResultSetCompareFunc (int index_a, int index_b);
private struct Context
{
// Lengths of the analyzed sets
int x_length;
int y_length;
// Buffer for finding the minimal edit path, contains space for both
// forwards and backwards paths
int[] real_diag;
// Buffer for finding forwards edit path, indexed from (-y_length-1)
// to (x_length+1)
int* f_diag;
// Buffer for finding backwards edit path, indexed from (-y_length-1)
// to (x_length+1)
int* b_diag;
// Maximum cost, finding the optimal one is often too costly
int max_cost;
// Buffer combining the x and y change buffers
uint8[] real_changes;
// Buffer for recording which items in the original set were removed
uint8* x_changes;
// Buffer for recording which items in the target set were added
uint8* y_changes;
Context (int x_set_length, int y_set_length)
{
x_length = x_set_length;
y_length = y_set_length;
int diags = (x_set_length + 1 + y_set_length + 1) + 1;
// allocate one big buffer for the forward and backward vectors
real_diag = new int[diags * 2];
// offset the pointers, cause we're sharing the same mem block,
// plus the indices can be negative
f_diag = real_diag;
f_diag += y_set_length + 1;
b_diag = real_diag;
b_diag += diags;
b_diag += y_set_length + 1;
// another one big buffer to record the changes
real_changes = new uint8[diags];
x_changes = real_changes;
y_changes = real_changes;
y_changes += x_set_length + 1;
// finding the optimal solution could be too expensive, use suboptimal
// if the cost is getting too high
max_cost = 1;
// fast sqrt(diags) approximation
while (diags != 0)
{
diags /= 4;
max_cost *= 2;
}
max_cost = int.max (max_cost, 256);
}
}
private struct Partition
{
// find the midpoints where we need to split the two sets
int x_mid;
int y_mid;
// is the upper and lower part of the edit path optimal?
bool lo_minimal;
bool hi_minimal;
}
internal struct Change
{
int x_offset;
int y_offset;
int inserted;
int deleted;
}
internal static SList run (int x_set_length, int y_set_length,
ResultSetCompareFunc cmp_func)
{
Context ctx = Context (x_set_length, y_set_length);
compare_sequences (0, x_set_length, 0, y_set_length,
false, ref ctx, cmp_func);
return build_edit_script (ref ctx);
}
// Note that this produces changeset in reversed order
private static SList build_edit_script (ref Context ctx)
{
SList script = new SList ();
int x_length = ctx.x_length;
int y_length = ctx.y_length;
uint8* x_changes = ctx.x_changes;
uint8* y_changes = ctx.y_changes;
int x = 0;
int y = 0;
// find continous change sets and record them, so we're able
// to transform the first set into the second
while (x < x_length || y < y_length)
{
if ((x_changes[x] | y_changes[y]) != 0)
{
int xx = x;
int yy = y;
while (x_changes[x] != 0) x++;
while (y_changes[y] != 0) y++;
script.prepend ({xx, yy, y - yy, x - xx});
}
x++;
y++;
}
return script;
}
/* Find the midpoint of the shortest edit script for a given subset
of the two vectors */
private static void find_diag (int x_offset, int x_limit,
int y_offset, int y_limit,
ResultSetCompareFunc equal_func,
ref Context ctx,
ref Partition partition)
{
int d_min = x_offset - y_limit;
int d_max = x_limit - y_offset;
int f_mid = x_offset - y_offset;
int b_mid = x_limit - y_limit;
int f_min = f_mid;
int f_max = f_mid;
int b_min = b_mid;
int b_max = b_mid;
int cost;
bool is_odd = ((f_mid - b_mid) & 1) != 0;
int* f_diag = ctx.f_diag;
int* b_diag = ctx.b_diag;
f_diag[f_mid] = x_offset;
b_diag[b_mid] = x_limit;
for (cost = 1; ; cost++)
{
int d;
// extend the forwards search by an edit step in each diagonal
if (f_min > d_min) f_diag[--f_min - 1] = -1;
else ++f_min;
if (f_max < d_max) f_diag[++f_max + 1] = -1;
else --f_max;
for (d = f_max; d >= f_min; d -= 2)
{
int x, y;
int t_lo = f_diag[d - 1];
int t_hi = f_diag[d + 1];
int x0 = t_lo < t_hi ? t_hi : t_lo + 1;
for (x = x0, y = x0 - d;
x < x_limit && y < y_limit && equal_func (x, y);
x++, y++)
{
continue;
}
f_diag[d] = x;
if (is_odd && b_min <= d && d <= b_max && b_diag[d] <= x)
{
partition.x_mid = x;
partition.y_mid = y;
partition.lo_minimal = partition.hi_minimal = true;
return;
}
}
// and extend the backwards search
if (b_min > d_min) b_diag[--b_min - 1] = int.MAX;
else ++b_min;
if (b_max < d_max) b_diag[++b_max + 1] = int.MAX;
else --b_max;
for (d = b_max; d >= b_min; d -= 2)
{
int x, y;
int t_lo = b_diag[d - 1];
int t_hi = b_diag[d + 1];
int x0 = t_lo < t_hi ? t_lo : t_hi - 1;
for (x = x0, y = x0 - d;
x_offset < x && y_offset < y && equal_func (x-1, y-1);
x--, y--)
{
continue;
}
b_diag[d] = x;
if (!is_odd && f_min <= d && d <= f_max && x <= f_diag[d])
{
partition.x_mid = x;
partition.y_mid = y;
partition.lo_minimal = partition.hi_minimal = true;
return;
}
}
// Chance to implement heuristic to speed things up at the cost
// of loosing optimal path
// If cost is too high, give up and report halfway between best results
if (cost >= ctx.max_cost)
{
int fxy_best, bxy_best;
int fx_best = 0;
int bx_best = 0;
fxy_best = -1;
for (d = f_max; d >= f_min; d -= 2)
{
int x = int.min (f_diag[d], x_limit);
int y = x - d;
if (y_limit < y)
{
x = y_limit + d;
y = y_limit;
}
if (fxy_best < x + y)
{
fxy_best = x + y;
fx_best = x;
}
}
bxy_best = int.MAX;
for (d = b_max; d >= b_min; d -= 2)
{
int x = int.max (x_offset, b_diag[d]);
int y = x - d;
if (y < y_offset)
{
x = y_offset + d;
y = y_offset;
}
if (x + y < bxy_best)
{
bxy_best = x + y;
bx_best = x;
}
}
if ((x_limit + y_limit) - bxy_best < fxy_best - (x_offset + y_offset))
{
partition.x_mid = fx_best;
partition.y_mid = fxy_best - fx_best;
partition.lo_minimal = true;
partition.hi_minimal = false;
}
else
{
partition.x_mid = bx_best;
partition.y_mid = bxy_best - bx_best;
partition.lo_minimal = false;
partition.hi_minimal = true;
}
return;
}
}
}
/* Compare contigous sequences of two sets.
*
* Return true if terminated through early abort, false otherwise.
*/
private static bool compare_sequences (int x_offset, int x_limit,
int y_offset, int y_limit,
bool find_minimal, ref Context ctx,
ResultSetCompareFunc equal_func)
{
// slide down the bottom diagonal in the forwards path
while (x_offset < x_limit && y_offset < y_limit && equal_func (x_offset, y_offset))
{
x_offset++;
y_offset++;
}
// and slide up the top diagonal in the backwards path
while (x_offset < x_limit && y_offset < y_limit && equal_func (x_limit-1, y_limit-1))
{
x_limit--;
y_limit--;
}
if (x_offset == x_limit)
{
// these items were added to the target set
while (y_offset < y_limit)
{
ctx.y_changes[y_offset] = 1;
y_offset++;
}
}
else if (y_offset == y_limit)
{
// these items were removed from the original set
while (x_offset < x_limit)
{
ctx.x_changes[x_offset] = 1;
x_offset++;
}
}
else
{
Partition partition = { 0, 0, false, false };
// split into two subproblems
find_diag (x_offset, x_limit, y_offset, y_limit,
equal_func, ref ctx, ref partition);
if (compare_sequences (x_offset, partition.x_mid,
y_offset, partition.y_mid,
partition.lo_minimal,
ref ctx, equal_func))
return true;
if (compare_sequences (partition.x_mid, x_limit,
partition.y_mid, y_limit,
partition.hi_minimal,
ref ctx, equal_func))
return true;
}
return false;
}
} /* namespace Unity.Internal.Utils.Diff */
}
} /* namespace Unity.Internal */
} /* namespace Unity */