دليل ترحيل Lore VCS لحل مشكلات تكاليف Git LFS وتضارب أصول Unreal Engine
عندما ينمو المشروع، يتحول Git LFS إلى كارثة حقيقية. إصلاح بضعة أسطر من التعليمات البرمجية يستغرق لحظات، ولكن بمجرد أن تبدأ أصول الفن التي تقاس بالجيجابايت في الاختلاط، تدور الساعة الرملية لعشرات الثواني مع كل عملية دفع (Commit). وبسبب البنية البطيئة لـ LFS التي تعتمد على نسخ الملفات الكبيرة بالكامل، تتجاوز مساحة التخزين في المستودع التيرابايت في وقت قصير، وتظهر أرقام مرعبة في فواتير النطاق الترددي (Bandwidth).
صممت شركة Epic Games أداة Lore مفتوحة المصدر للتعامل مع الملفات الثنائية (Binary files) كأولوية قصوى. فهي تستخدم طريقة FastCDC لتقسيم الملفات إلى كتل وإزالة التكرار، مما يقلل بشكل كبير من حجم الحزمة ونطاق النقل الترددي. لقد قمنا بتلخيص طرق الترحيل العملي والتحسين في Lore التي يمكن للفرق الصغيرة، التي تفتقر إلى متخصصي DevOps، تطبيقها على الفور.
1. الترحيل التدريجي من Git LFS إلى Lore
ليس من الضروري التخلص من سجل التزامات (Commit history) التعليمات البرمجية المتراكم في مستودع Git الحالي. الاستراتيجية الأكثر واقعية هي الإبقاء على التعليمات البرمجية الخفيفة في Git، ونقل الأصول الثنائية الثقيلة فقط إلى مستودع Lore.
يقوم البرنامج النصي أدناه بتحديد قائمة الملفات التي كان يتتبعها Git LFS، وإنشاء هيكل دليل متناظر، ومطابقة تجزئة SHA-256 للنسخ لضمان الانتقال إلى Lore دون فقدان أي بيانات.
`bash
#!/usr/bin/env bash
set -euo pipefail
GIT_REPO_DIR="HOME/projects/my−game−git"LOREREPODIR="HOME/projects/my-game-lore"
LORE_SERVER_URL="lore://127.0.0.1:41337/my-project"
echo "[1/5] تهيئة مستودع Lore والربط عن بُعد..."
if [ ! -d "LOREREPODIR"];thenmkdir−p"LORE_REPO_DIR"
cd "LOREREPODIR"lorerepositorycreate"LORE_SERVER_URL"
else
cd "$LORE_REPO_DIR"
fi
echo "[2/5] جمع قائمة ملفات Git LFS المتتبعة..."
cd "GITREPODIR"LFSFILES=(git lfs ls-files -n | grep -E ".(uasset|umap|fbx|png|psd)$" || true)
if [ -z "$LFS_FILES" ]; then
echo "لا توجد ملفات LFS للترحيل."
exit 0
fi
echo "[3/5] نسخ الملفات وإنشاء بيان للتحقق..."
echo "LFSFILES"∣whileread−rfilepath;doif[−f"filepath" ]; then
dest_dir="LOREREPODIR/(dirname "filepath")"mkdir−p"dest_dir"
cp -p "filepath""LORE_REPO_DIR/filepath"sourcehash=(openssl dgst -sha256 "$filepath" | awk '{print 2}')
echo "filepath:sourcehash">>"LORE_REPO_DIR/.lore_migration_manifest"
fi
done
echo "[4/5] إلغاء تتبع Git LFS وتجهيز (Staging) Lore..."
cd "GITREPODIR"echo"LFS_FILES" | while read -r filepath; do
git rm --cached "$filepath"
done
sed -i.bak -E 's/filter=lfs diff=lfs merge=lfs//g' .gitattributes
cd "LOREREPODIR"echo"LFS_FILES" | while read -r filepath; do
lore stage "$filepath"
done
lore commit -m "Migration: Transfer heavy assets from Git LFS to Lore"
lore push
echo "[5/5] التحقق من سلامة البيانات..."
lore repository verify state
lore repository verify fragment
if [ -f ".lore_migration_manifest" ]; then
migration_errors=0
while IFS=":" read -r file sha; do
if [ -f "file"];thencurrenthash=(openssl dgst -sha256 "$file" | awk '{print 2}')
if [ "sha" != "$current_hash" ]; then
echo "تم اكتشاف ملف تالف: file"migrationerrors=((migration_errors + 1))
fi
fi
done < .lore_migration_manifest
if [ $migration_errors -eq 0 ]; then
echo "اكتمل الترحيل. تم استعادة جميع البيانات بسلامة 100%."
rm .lore_migration_manifest
else
echo "فشل التحقق: هناك مشاكل في سلامة $migration_errors ملفات."
exit 1
fi
fi
`
افتح المحطة الطرفية (Terminal) واحفظ المحتوى أعلاه باسم migrate.sh. بعد منح إذن التنفيذ باستخدام أمر chmod +x migrate.sh وتعديل المتغيرات لتناسب مساراتك المحلية، يمكنك تنفيذه.
بمجرد الانتهاء من هذه المهمة، سينخفض استخدام تخزين LFS السحابي البعيد على الفور. وفقاً لحالات الاعتماد الفعلية في Epic Games، يمكن لتقنية إزالة تكرار البيانات على مستوى الكتل خفض تكاليف صيانة البنية التحتية بنسبة 50%.
2. تطبيق الاسترداد عند الطلب (On-demand Hydration) في خط أنابيب CI/CD
خطوط الأنابيب التي تقوم بتنزيل مئات الجيجابايت من أصول اللعبة بالكامل في كل دورة بناء هي السبب الرئيسي لإتلاف بطاقات الشبكة وإدخال/إخراج القرص. تدعم Lore الاسترداد عند الطلب (On-demand Hydration) القائم على نظام الملفات الافتراضي لتقليل وقت البناء.
في مرحلة الاستنساخ الأولى (Clone)، يتم نسخ سلسلة هيكل البيانات الوصفية وشجرة الدليل فقط كقشرة خارجية، وعندما يقرأ المترجم الأصول الفعلية أثناء عملية الطهي (Cook)، يتم بث أجزاء الملفات الثنائية اللازمة فقط ديناميكياً لتعبئتها على القرص.
`yaml
name: Game Project On-Demand Production Build
on:
push:
branches: [ release-candidate ]
env:
LORE_SERVER: "lore://10.0.1.50:41337/game-depot"
SHARED_STORE_PATH: "/var/lib/lore_shared_chunk_store"
WORKSPACE_PATH: "game_build_workspace"
jobs:
assemble-assets:
runs-on: self-hosted
steps:
- name: Persistent Shared Store 준비
run: |
if [ ! -d "env.SHAREDSTOREPATH"];thensudomkdir−p"{{ env.SHARED_STORE_PATH }}"
sudo chmod 777 "env.SHAREDSTOREPATH"loreshared−storecreate"{{ env.SHARED_STORE_PATH }}"
fi
- name: Shared Cache를 경유한 Sparse Bare Clone
run: |
lore clone \
--bare \
--use-shared-store \
--shared-store-path "${{ env.SHARED_STORE_PATH }}" \
"${{ env.LORE_SERVER }}" \
"${{ env.WORKSPACE_PATH }}"
- name: 뷰 필터를 통한 불필요한 시네마틱 소스 제외
run: |
cd "${{ env.WORKSPACE_PATH }}"
echo "+ /SourceCode/" > .lore/view
echo "+ /Config/" >> .lore/view
echo "+ /Content/Core_Assets/" >> .lore/view
echo "- /Content/Cinematic_RAW/" >> .lore/view
- name: 필수 청크만 로컬 디스크에 동기화
run: |
cd "${{ env.WORKSPACE_PATH }}"
lore sync
- name: Unreal Engine Cooker 실행
run: |
cd "${{ env.WORKSPACE_PATH }}"
./RunUAT.sh BuildCookRun \
-project="$(pwd)/GameProject.uproject" \
-platform=Win64 \
-clientconfig=Development \
-cook -stage -pak -archive
`
يتم تحقيق أقصى قدر من الفعالية عند توفير مخزن ذاكرة تخزين مؤقت دائم مثل /var/lib/lore_shared_chunk_store على خادم البناء. بعد جلب معلومات الفهرس بسرعة باستخدام أمر lore clone --bare، نترك فقط المسارات الضرورية في ملف التصفية .lore/view ثم نشغل lore sync. من خلال ترسيخ هذا الهيكل، يختفي أكثر من 85% من حركة المرور المرسلة إلى عقدة البناء. ويتم تقليل وقت البناء الإجمالي بنسبة 30% في المتوسط.
3. منع تضارب الأصول باستخدام نظام قفل الملفات
لا يمكن دمج الأصول الثنائية مثل .uasset في Unreal Engine أو ملفات خريطة المستوى تلقائياً في الفرع الرئيسي مثل تعليمات البرمجية النصية. إذا قام فنانان بتعديل نفس الأصل في وقت واحد، فسيحدث حادث حيث يقوم الفنان الذي قام بالدفع لاحقاً بالكتابة فوق عمل الشخص السابق أو التسبب في تشابك الخط الزمني.
توفر Lore ميزة القفل (Lock) التي تنفذ التحكم الحصري بناءً على السجل غير القابل للتغيير للخادم البعيد.
`bash
طلب صلاحية تحرير حصرية لملف نموذج شخصية ثنائي معين
lore lock acquire Content/Characters/HeroMesh.uasset --branch main
التحقق من حالة قفل الموارد تحت مجلد معين
lore lock status Content/Characters/HeroMesh.uasset
البحث عن جميع الملفات التي قفلها عامل معين في المستودع البعيد
lore lock query --branch main --owner developer_artist_03
إرجاع الصلاحية بعد اكتمال العمل والدفع
lore lock release Content/Characters/HeroMesh.uasset --branch main
`
يجب وضع قاعدة تجعل الفنانين يطلبون القفل باستخدام أمر lore lock acquire قبل لمس الملفات. بمجرد انتهاء التعديل وانعكاس الالتزام والدفع على الموقع البعيد، يتم تحرير القفل باستخدام lore lock release لتسليمه إلى العامل التالي.
إذا كان المخططون والفنانون يجدون أوامر المحطة الطرفية غير مألوفة، فيمكن ربط تلك المسارات بأدوات واجهة المستخدم الرسومية لسطح المكتب مثل Anchorpoint، بحيث يمكن تنفيذ أوامر القفل هذه بنقرة زر الماوس الأيمن.
4. دليل ضبط الذاكرة المؤقتة حسب مواصفات الأجهزة
يجب ضبط إعدادات العميل وفقاً لمواصفات بيئة العمل لمنع اختناقات الإدخال/الإخراج للملفات.
ضبط العميل المحلي حسب مواصفات جهاز العامل
- الأنظمة عالية الأداء (NVMe SSD، ذاكرة وصول عشوائي 32 جيجابايت أو أكثر، وحدة معالجة مركزية متعددة النواة): السماح بالعمليات المباشرة القائمة على خرائط الذاكرة عبر تقنية تعيين الذاكرة الافتراضية لنظام التشغيل. قم بتوسيع سلاسل رسائل التنزيل المتوازية القصوى وحدد فترة احتفاظ طويلة لذاكرة التخزين المؤقت للقرص للتخلص من تأخير المزامنة.
- أنظمة المبتدئين وأنظمة الفنانين (SATA SSD أو HDD، ذاكرة وصول عشوائي 16 جيجابايت أو أقل): قم بإيقاف تشغيل طريقة تعيين الذاكرة لمنع تعطل المحرك بسبب استنزاف الذاكرة. قم بتشغيل علامات
--direct-file-write و --direct-file-io للكتابة مباشرة على القرص، وقم بزيادة الفاصل الزمني لجدول تجميع القمامة (Garbage Collection) لمنع حدوث ظاهرة احتلال القرص المفاجئة أثناء العمل.
تحليل الأداء بناءً على اختيار خوارزمية الضغط
بالرجوع إلى نتائج اجتماع تحديد بنية Lore (ADR-00016)، فإن ضبط معيار Zstandard Level 6 كمحرك ضغط افتراضي هو الأكثر توازناً من حيث الكفاءة.
| نوع خوارزمية الضغط |
نسبة الضغط النهائي للبيانات (%) |
سرعة عملية الضغط (MiB/s) |
سرعة فك الضغط (MiB/s) |
القابلية للنقل والميزات |
| LZ4 Default |
47.6% |
718.7 |
2494.5 |
سرعة عالية ولكن إهدار كبير لمساحة التخزين |
| Zstd Level 1 |
34.5% |
602.0 |
1363.1 |
نقطة توازن جيدة بين السرعة وحفظ المساحة |
| Zstd Level 6 (موصى به) |
28.9% |
136.5 |
1284.9 |
أفضل كفاءة معالجة مقارنة بتوفير المساحة |
| Oodle Kraken 3 (Fast) |
28.2% |
95.6 |
1413.1 |
يتطلب مكتبة Oodle الخاصة بـ Epic Games |
| Oodle Kraken 6 (الافتراضي السابق) |
23.9% |
2.2 |
1312.6 |
تنخفض السرعة بشدة عند الترحيل بالجيجابايت |
كان Oodle Kraken Level 6، الذي كان يستخدم بشكل متكرر في الماضي، السبب الرئيسي لإطالة وقت انتظار الفنانين قبل المغادرة، حيث تنخفض سرعة معالجة الضغط أثناء عملية التسلسل (Serialization) في الخلفية إلى مستوى 2.2 MiB في الثانية.
في المقابل، يوفر Zstd Level 6 نسبة ضغط تقترب من Oodle Kraken 6، بينما سرعة ضغط كتل الأجزاء المحلية تصل إلى 136.5 MiB/s، وهي أسرع بحوالي 62 مرة. إذا كان هناك العديد من المكاتب الخارجية أو العاملين عن بعد مما يسبب تداخلاً في النطاق الترددي، ففكر في تكوين طبقة ثانية من طوبولوجيا الخادم لتكوين عقد ذاكرة تخزين مؤقت فرعية (Proxy edge cache) داخل الشبكة المحلية.