Skip to content

Performance issues in Academy Overview (GRT audit findings) #86

Description

@teodoroleckie

🐛 Performance issues in Academy Overview (GRT audit findings)

Summary

After auditing the GRT codebase, several performance bottlenecks were identified, especially affecting the Academy Overview (_GRCRT_AO.d() and related functions).

With a moderate-to-large dataset (e.g. 100+ towns), these issues cause:

  • Excessive CPU usage
  • Noticeable UI lag
  • Poor scalability due to O(n²) patterns
  • Risk of browser freezing in extreme cases

Most problems stem from nested loops, redundant computations, and lack of caching.


🔍 Observed Impact

  • Severe slowdown when opening Academy Overview
  • CPU spikes due to nested iterations
  • Inefficient DOM access patterns
  • Performance degradation grows exponentially with data size

📌 Identified Problems & Suggested Fixes

❗ Problem 1 — Nested loop O(n²) in _GRCRT_AO.d() (CRITICAL)

Issue:
For each town in N, the code iterates the entire P collection to find a match by ID.

This results in:

  • O(n²) complexity (e.g. 100 towns → 10,000 iterations)

Current code:

$.each(N, function(ia, da) {
    P.forEach(function(U) {
        if (da.id == U.id) { ... }
    });
});

Suggested fix (use hash map for O(1) lookup):

var townMap = {};
P.forEach(function(U) { townMap[U.id] = U; });

$.each(N, function(ia, da) {
    var U = townMap[da.id];
    if (U) { ... }
});

❗ Problem 2 — A() called inside nested loop (CRITICAL)

Issue:
A() recalculates shared state (Y) and is called:

Inside the nested loop (multiple times)
Again inside k() (indirect duplication)

Current code:

$.each(N, function(ia, da) {
    P.forEach(function(U) {
        if (da.id == U.id) {
            A();
            var ua = k(U.id);
        }
    });
});

Suggested fix (compute once):

A();

$.each(N, function(ia, da) {
    var U = townMap[da.id];
    if (U) {
        var ua = k(U.id); // ensure k() does NOT call A()
    }
});

⚠️ Problem 3 — Manual bubble sort O(n²) (HIGH)

Issue:
A custom bubble sort is used instead of native sorting.

Current code:

var X = false;
do {
    X = false;
    for (var aa = 0; aa < N.length - 1; aa++) {
        if (N[aa].name > N[aa + 1].name) {
            X = N[aa]; N[aa] = N[aa + 1]; N[aa + 1] = X; X = true;
        }
    }
} while (X);

Suggested fix:

N.sort(function(a, b) {
    return a.name.localeCompare(b.name);
});

⚠️ Problem 4 — Repeated Object.size() in inner loop (HIGH)

Issue:
Object.size() iterates over the object and is called multiple times per iteration.

Current code:

var ba = Y && Y[U.id] && Object.size(Y[U.id]) == queueLen || false;

$.each(GameData.researches, function(Fa, $a) {
    var db = Y && Y[U.id] && Object.size(Y[U.id]) == queueLen || false;
});

Suggested fix (cache result):

var queueLen = GameDataConstructionQueue.getResearchOrdersQueueLength();
var ySize    = Y && Y[U.id] ? Object.size(Y[U.id]) : 0;
var ba       = ySize == queueLen;

$.each(GameData.researches, function(Fa, $a) {
    var db = ba;
});

⚠️ Problem 5 — Repeated jQuery selector in R() (HIGH)

Issue:
The same DOM query is executed for every iteration.

Current code:

$.each(Ha[Ua.toString()], function(u, H) {
    $("#grcrt_radar_result ul").append(...);
});

Suggested fix (cache selector):

var $ul = $("#grcrt_radar_result ul").html("");

$.each(Ha[Ua.toString()], function(u, H) {
    $ul.append(...);
});

⚠️ Problem 6 — ajaxComplete processes all windows on every request (MEDIUM)

Issue:
Runs on every AJAX call and iterates all open windows regardless of relevance.

Current code:

$(document).ajaxComplete(function(d, k, m) {
    $.each(Layout.wnd.getAllOpen(), function(p, C) {
        ...
    });
});

Suggested fix (early filtering):

$(document).ajaxComplete(function(d, k, m) {
    if (!RepConv.settings[RepConv.Cookie + "_trade"]) return;

    var url = m.url;
    if (url.indexOf("farm_town") === -1 && url.indexOf("island_info") === -1) return;

    $.each(Layout.wnd.getAllOpen(), function(p, C) { ... });
});

⚠️ Problem 7 — Linear search in getPlayerId4Name (MEDIUM)

Issue:
Performs O(n) lookup over all players.

Current code:

this.getPlayerId4Name = function(d) {
    var k;
    $.each(RepConv.cachePlayers, function(m, p) {
        p.name == d && (k = p.id);
    });
    return k || null;
};

Suggested fix (indexed lookup):

// Build index once
RepConv.cachePlayersByName = {};
$.each(k, function(m, p) {
    RepConv.cachePlayersByName[p.name] = p.id;
});

// O(1) lookup
this.getPlayerId4Name = function(d) {
    return RepConv.cachePlayersByName[d] || null;
};

🟡 Problem 8 — GetLabel() without memoization (LOW)

Issue:
Repeated string splitting and traversal on every call.

Current code:

this.GetLabel = function(d) {
    var k, m = d.split("."), p = RepConv.Lang;
    $.each(m, function(C, G) { ... });
    return k || this.getLabelLangArray(d);
};

Suggested fix (memoization):

var _labelCache = {};

this.GetLabel = function(d) {
    if (_labelCache[d] !== undefined) return _labelCache[d];

    var k, m = d.split("."), p = RepConv.Lang;
    $.each(m, function(C, G) { ... });

    return (_labelCache[d] = k || this.getLabelLangArray(d));
};

The most impactful issues (1 & 2) introduce quadratic complexity in critical rendering paths, especially when opening the Academy Overview.

Combined with redundant computations and DOM inefficiencies, these problems significantly affect performance and scalability.

🚀 Expected Outcome After Fixes
Reduced time complexity from O(n²) to O(n) in key paths
Lower CPU usage during rendering
Faster UI response times
Improved scalability for large accounts
Elimination of major browser slowdowns/freezes

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions