You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fast, flexible full‑text search for GameMaker
Built for small to medium datasets (10k‑50k+ docs) with real‑time indexing, fuzzy search, n‑grams, BM25/TF‑IDF scoring, and persistence.
Field weighting – title (3x), tags (2x), custom metadata
N‑gram indexing for typo‑tolerant search
Stop words & minimum word length filtering
Persistence – save/load entire index to/from JSON
Memory safe – all DS maps properly cleaned
Scale – handles 50,000+ documents with good performance
Installation
Import the GMLiteSearch_Core script into your GameMaker project.
Call gmls_init() once, for example in a Create event or a persistent controller object.
Start adding documents and searching.
No external DLLs or extensions – pure GML.
Quick Start
// Initialize the search enginegmls_init();
// Add a document with weighted fields (title gets 3x weight, tags 2x)
var metadata = {
title: "The Legend of Zelda",
tags: ["adventure", "fantasy", "Nintendo"],
author: "Shigeru Miyamoto"
};
gmls_add_document_weighted("zelda_manual",
"Link must rescue Princess Zelda and defeat Ganon.",
metadata);
// Search with BM25 (default)
var results = gmls_search("zelda adventure", 5);
for (var i = 0; i < array_length(results); i++) {
show_debug_message(results[i].id + " | score: " + string(results[i].score));
}
// Fuzzy search for typos
var fuzzy = gmls_fuzzy_search("zleda", 3, 0.6);
// Prefix search for autocomplete
var prefix = gmls_search_prefix("zel", 5);
// Hybrid search (exact then prefix)
var hybrid = gmls_search_hybrid("legnd", 10);
// N-gram search for character-level typos
var ngram = gmls_search_ngrams("excalibr", 5);
// Remove a documentgmls_remove_document("zelda_manual");
// Get statistics
var stats = gmls_get_stats();
show_debug_message("Docs: " + string(stats.document_count) +
" | Words: " + string(stats.unique_words));
// Save index to file
var saveStr = gmls_save_to_string();
var file = file_text_open_write("search_index.json");
file_text_write_string(file, saveStr);
file_text_close(file);
// Load index from fileif (file_exists("search_index.json")) {
file = file_text_open_read("search_index.json");
var loadedStr = file_text_read_string(file);
file_text_close(file);
gmls_load_from_string(loadedStr);
}
// Clear all documents (keep config)gmls_clear();
// Complete cleanupgmls_cleanup();
Configuration Examples
// Change global settingsgmls_set_config(true, // case sensitivefalse, // stemming (placeholder)2, // minimum word length"bm25"); // scoring: "tfidf" or "bm25"// Tune BM25 parameters (default: k1=1.2, b=0.75)gmls_set_bm25_params(1.5, 0.8);
// Add custom stop wordsgmls_add_stop_word("wizard");
gmls_add_stop_word("dragon");
// Direct configuration access
global.gmls.enable_ngrams = true;
global.gmls.ngram_size = 3;
global.gmls.max_doc_size = 50000;
Document Addition Methods
// Plain document (only text is indexed)gmls_add_document("doc1", "The quick brown fox jumps over the lazy dog");
// Enhanced – repeats title & tags twice (higher relevance)gmls_add_document_enhanced("doc2", "Main content...",
{ title: "My Awesome Article", tags: ["tutorial", "gamedev"] });
// Weighted – title 3x, tags 2x, others 1x (most control)gmls_add_document_weighted("doc3", "content...",
{ title: "Important", tags: ["guide"], author: "John", description: "A full guide" });
// Access stored document
var doc = gmls_get_document("doc3");
if (doc != undefined) {
show_debug_message(doc.text);
show_debug_message(doc.metadata.title);
show_debug_message("Word count: " + string(doc.word_count));
}
Search Results Structure
var results = gmls_search("example query", 10);
for (var i = 0; i < array_length(results); i++) {
var res = results[i];
// Available fields
var _id = res.id; // Document identifier
var _score = res.score; // Relevance score
var text = res.document.text; // Full document text
var title = res.document.metadata.title; // Metadata title
var tags = res.document.metadata.tags; // Metadata tags
var snippet = res.snippet; // Highlighted excerpt
var matched = res.matched_terms; // Array of matched termsshow_debug_message("ID: " + _id);
show_debug_message("Score: " + string(_score));
show_debug_message("Title: " + title);
show_debug_message("Snippet: " + snippet);
show_debug_message("Matched: " + string(matched));
}
Real-World Example: In-Game Item Database
// Initialize at game startgmls_init();
gmls_set_config(false, false, 2, "bm25");
// Add items from your game
var items = [
{ id: "sword1", name: "Iron Sword", desc: "A basic iron sword", type: "weapon", damage: 15 },
{ id: "sword2", name: "Steel Sword", desc: "A sharp steel blade", type: "weapon", damage: 25 },
{ id: "potion1", name: "Health Potion", desc: "Restores 50 HP", type: "consumable", heal: 50 },
{ id: "potion2", name: "Mana Potion", desc: "Restores 30 MP", type: "consumable", heal: 30 },
{ id: "armor1", name: "Leather Armor", desc: "Light protective gear", type: "armor", defense: 10 }
];
for (var i = 0; i < array_length(items); i++) {
var it = items[i];
gmls_add_document_weighted(it.id, it.desc,
{ title: it.name, tags: [it.type], damage: it.damage, heal: it.heal, defense: it.defense });
}
// Search function with fallback
function search_items(query) {
var results = gmls_search(query, 10);
if (array_length(results) == 0) {
results = gmls_fuzzy_search(query, 10, 0.5);
}
if (array_length(results) == 0 && global.gmls.enable_ngrams) {
results = gmls_search_ngrams(query, 10);
}
return results;
}
// Usage in game
var found = search_items("steel blade");
for (var i = 0; i < array_length(found); i++) {
var item = found[i].document;
var score = found[i].score;
show_debug_message("Found: " + item.metadata.title +
" (Type: " + item.metadata.tags[0] +
", Score: " + string(score) + ")");
// Access custom fieldsif (item.metadata.damage != undefined) {
show_debug_message(" Damage: " + string(item.metadata.damage));
}
if (item.metadata.heal != undefined) {
show_debug_message(" Heal: " + string(item.metadata.heal));
}
}
Persistence Example: Save/Load Player Notes
// Player creates notes during gameplay
function add_player_note(id, title, content) {
gmls_add_document_weighted(id, content,
{ title: title, tags: ["player_note"], timestamp: current_time });
}
add_player_note("note1", "Dragon Location", "The dragon lives in the eastern mountains near the old tower");
add_player_note("note2", "Quest Reminder", "Talk to the blacksmith about the enchanted sword");
add_player_note("note3", "Secret Entrance", "Behind the waterfall in the forest");
// Search player notes
function search_notes(query) {
returngmls_search(query, 20);
}
var notes = search_notes("dragon tower");
for (var i = 0; i < array_length(notes); i++) {
var note = notes[i];
draw_text(10, 50 + i*60, "Title: " + note.document.metadata.title);
draw_text(10, 70 + i*60, "Snippet: " + note.snippet);
}
// Save all notes to file
function save_all_notes() {
var save_data = gmls_save_to_string();
var file = file_text_open_write("player_notes.json");
file_text_write_string(file, save_data);
file_text_close(file);
show_debug_message("Notes saved!");
}
// Load notes at game start
function load_all_notes() {
if (file_exists("player_notes.json")) {
var file = file_text_open_read("player_notes.json");
var load_data = file_text_read_string(file);
file_text_close(file);
gmls_load_from_string(load_data);
show_debug_message("Notes loaded!");
returntrue;
}
returnfalse;
}
// Auto-save every 5 minutes
alarm[0] = room_speed * 300; // 5 minutes// In alarm event: save_all_notes();
Search UI with Typo Correction
// Create eventgmls_init();
search_query = "";
search_results = [];
selected_index = 0;
// Step event for inputif (keyboard_check_pressed(vk_enter)) {
// Try exact search first
search_results = gmls_search(search_query, 10);
// If no results, try fuzzyif (array_length(search_results) == 0) {
search_results = gmls_fuzzy_search(search_query, 10, 0.6);
show_debug_message("No exact matches, showing fuzzy results");
}
// If still no results, try n-gramif (array_length(search_results) == 0 && global.gmls.enable_ngrams) {
search_results = gmls_search_ngrams(search_query, 10);
show_debug_message("Trying n-gram search");
}
selected_index = 0;
}
// Keyboard navigationif (keyboard_check_pressed(vk_down)) {
selected_index = min(selected_index + 1, array_length(search_results) - 1);
}
if (keyboard_check_pressed(vk_up)) {
selected_index = max(selected_index - 1, 0);
}
// Draw search UIdraw_text(10, 10, "Search: " + search_query + "_");
draw_text(10, 30, "Results: " + string(array_length(search_results)));
for (var i = 0; i < array_length(search_results); i++) {
var res = search_results[i];
var _y = 60 + i * 70;
var color = (i == selected_index) ? c_yellow : c_white;
draw_set_color(color);
draw_text(10, _y, "Title: " + res.document.metadata.title);
draw_text(10, _y + 20, "Score: " + string(res.score));
draw_text(10, _y + 40, "Snippet: " + string_copy(res.snippet, 1, 80));
}
draw_set_color(c_white);
Performance Optimization Examples
// For small datasets (< 1000 docs) - use any mode
global.gmls.enable_ngrams = true; // Keep typo tolerancegmls_set_config(false, false, 2, "bm25");
// For medium datasets (1k - 10k docs)
global.gmls.enable_ngrams = false; // Disable for speedgmls_set_config(false, false, 2, "bm25");
gmls_set_bm25_params(1.2, 0.75);
// For large datasets (10k - 50k+ docs)
global.gmls.enable_ngrams = false;
gmls_set_config(false, false, 3, "bm25"); // Increase min word lengthgmls_set_bm25_params(1.2, 0.75);
global.gmls.max_doc_size = 20000; // Limit document size// Use hybrid search for better UX
function smart_search(query, max_results) {
if (string_length(query) <= 3) {
returngmls_search_prefix(query, max_results);
} else {
returngmls_search(query, max_results);
}
}
// Cache results for repeated queries
var last_query = "";
var last_results = [];
function cached_search(query, max_results) {
if (query == last_query) {
return last_results;
}
last_query = query;
last_results = gmls_search(query, max_results);
return last_results;
}