मुझे एक एक्स्टेंसिबल एसेट लोडिंग सिस्टम की संरचना कैसे करनी चाहिए?


19

जावा में एक शौक गेम इंजन के लिए, मैं एक सरल लेकिन लचीली संपत्ति / संसाधन प्रबंधक को कोड करना चाहता हूं। एसेट्स ध्वनियाँ, चित्र, एनीमेशन, मॉडल, बनावट, वगैरह हैं। ब्राउज़िंग के कुछ घंटों और कुछ कोड प्रयोगों के बाद भी मुझे यकीन नहीं है कि इस चीज़ को कैसे डिज़ाइन किया जाए।

विशेष रूप से, मैं देख रहा हूं कि मैं प्रबंधक को एक तरह से कैसे डिजाइन कर सकता हूं ताकि यह सार हो कि विशिष्ट संपत्ति प्रकार कैसे लोड किए जाते हैं और कहां से संपत्ति लोड की जा रही है। मैं इस कार्यक्रम के बारे में जानने की आवश्यकता के बाकी कार्यक्रम के बिना फाइल सिस्टम और आरडीबीएमएस भंडारण दोनों का समर्थन करने में सक्षम होना चाहूंगा। इसी तरह, मैं एक एनीमेशन विवरण परिसंपत्ति (एफपीएस, रेंडर करने के लिए फ़्रेम, प्रेत छवि के संदर्भ में, एट cetera कि XML है) जोड़ना चाहते हैं। मुझे XML फ़ाइल खोजने और पढ़ने की कार्यक्षमता के साथ इसके लिए एक वर्ग लिखने में सक्षम होना चाहिए और AnimationAssetउस जानकारी के साथ एक वर्ग बनाना और वापस करना चाहिए । मैं एक डेटा-संचालित डिज़ाइन की तलाश कर रहा हूं ।

मैं के बारे में जानकारी का एक बहुत कुछ मिल सकता है क्या एक परिसंपत्ति प्रबंधक क्या करना चाहिए, लेकिन पर नहीं कैसे यह करने के लिए। इसमें शामिल जेनेरिकों का परिणाम कक्षाओं के कैस्केडिंग, या हेल्पर कक्षाओं के किसी न किसी रूप में दिखाई देता है। हालाँकि, मैंने एक स्पष्ट उदाहरण नहीं देखा है जो व्यक्तिगत हैक या आम सहमति के बिंदु की तरह नहीं दिखता है।

जवाबों:


23

मैं एक परिसंपत्ति प्रबंधक के बारे में नहीं सोचकर शुरू करता हूं । शिथिल-परिभाषित शब्दों (जैसे "प्रबंधक") में आपकी वास्तुकला के बारे में सोचकर आपको गलीचा के नीचे कई विवरणों को मानसिक रूप से झाड़ने की अनुमति मिलती है, और परिणामस्वरूप समाधान पर व्यवस्थित करना अधिक कठिन हो जाता है।

अपनी विशिष्ट आवश्यकताओं पर ध्यान केंद्रित करें, जो एक संसाधन लोडिंग तंत्र बनाने के साथ प्रतीत होता है जो अंतर्निहित मूल भंडारण को अमूर्त करता है और समर्थित प्रकार के सेट की व्यापकता के लिए अनुमति देता है। आपके प्रश्न में वास्तव में कुछ भी नहीं है, उदाहरण के लिए, पहले से लोड संसाधनों का कैशिंग - जो ठीक है, क्योंकि एकल-जिम्मेदारी सिद्धांत को ध्यान में रखते हुए आपको शायद एक अलग इकाई के रूप में एक परिसंपत्ति कैश का निर्माण करना चाहिए और दो इंटरफेस को कहीं और एकत्रित करना चाहिए। , के रूप में उपयुक्त।

अपनी विशिष्ट चिंता को दूर करने के लिए, आपको अपने लोडर को डिज़ाइन करना चाहिए ताकि यह किसी भी संपत्ति का लोडिंग खुद न करे, बल्कि उस जिम्मेदारी को सौंपता है जो विशिष्ट प्रकार की संपत्ति को लोड करने के लिए इंटरफेस के अनुरूप हो। उदाहरण के लिए:

interface ITypeLoader {
  object Load (Stream assetStream);
}

आप इस इंटरफ़ेस को लागू करने वाली नई कक्षाएं बना सकते हैं, प्रत्येक नई कक्षा को एक विशिष्ट प्रकार के डेटा को एक स्ट्रीम से लोड करने के अनुरूप बनाया जा सकता है। एक धारा का उपयोग करके, टाइप लोडर को एक आम, भंडारण-अज्ञेयवादी इंटरफ़ेस के खिलाफ लिखा जा सकता है, और डिस्क या डेटाबेस से लोड करने के लिए कठोर कोडित नहीं होना पड़ता है; यह आपको नेटवर्क स्ट्रीम से अपनी संपत्ति लोड करने की भी अनुमति देगा (जो आपके गेम कंसोल पर चल रहा है और नेटवर्क से जुड़े पीसी पर आपके संपादन उपकरण) जब संपत्ति को फिर से लोड करने में बहुत उपयोगी हो सकता है।

आपके मुख्य परिसंपत्ति लोडर को इन प्रकार-विशिष्ट लोडर को पंजीकृत और ट्रैक करने में सक्षम होना चाहिए:

class AssetLoader {
  public void RegisterType (string key, ITypeLoader loader) {
    loaders[key] = loader;
  }

  Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}

यहां उपयोग की जाने वाली "कुंजी" वह हो सकती है जो आपको पसंद है - और इसे एक स्ट्रिंग की आवश्यकता नहीं है, लेकिन उन के साथ शुरू करना आसान है। मुख्य कारक यह होगा कि आप उपयोगकर्ता से किसी विशेष संपत्ति की पहचान करने की अपेक्षा कैसे करते हैं और इसका उपयोग उचित लोडर को देखने के लिए किया जाएगा। क्योंकि आप इस तथ्य को छिपाना चाहते हैं कि कार्यान्वयन एक फ़ाइल सिस्टम या डेटाबेस का उपयोग कर रहा हो सकता है, आपके पास एक फ़ाइल सिस्टम पथ या ऐसा कुछ भी करने के लिए संपत्ति का जिक्र करने वाले उपयोगकर्ता नहीं हो सकते हैं।

उपयोगकर्ताओं को एक नंगे न्यूनतम जानकारी के साथ एक संपत्ति का उल्लेख करना चाहिए। कुछ मामलों में, केवल एक फ़ाइल नाम ही पर्याप्त होगा, लेकिन मैंने पाया है कि यह अक्सर एक प्रकार / नाम जोड़ी का उपयोग करने के लिए वांछनीय है, इसलिए सब कुछ बहुत स्पष्ट है। इस प्रकार, एक उपयोगकर्ता आपके एनीमेशन XML फ़ाइलों में से एक के उदाहरण का उल्लेख कर सकता है "AnimationXml","PlayerWalkCycle"

यहां, AnimationXmlवह कुंजी होगी जिसके तहत आपने पंजीकरण किया था AnimationXmlLoader, जो लागू करता हैIAssetLoader । जाहिर है, PlayerWalkCycleविशिष्ट संपत्ति की पहचान करता है। एक प्रकार के नाम और एक संसाधन नाम को देखते हुए, आपका एसेट लोडर उस परिसंपत्ति के कच्चे बाइट्स के लिए अपने निरंतर भंडारण को क्वेरी कर सकता है। चूंकि हम यहां अधिकतम समानता के लिए जा रहे हैं, आप इसे लोड करते समय लोडर एक्सेस के साधन को पास करके इसे लागू कर सकते हैं, जिससे आप स्टोरेज माध्यम को किसी भी चीज से बदल सकते हैं जो बाद में एक स्ट्रीम प्रदान कर सकता है:

interface IAssetStreamProvider {
  Stream GetStream (string type, string name);
}

class AssetLoader {
  public AssetLoader (IAssetStreamProvider streamProvider) {
    provider = streamProvider;
  }

  object LoadAsset (string type, string name) {
    var loader = loaders[type];
    var stream = provider.GetStream(type, name);

    return loader.Load(stream);
  }

  public void RegisterType (string type, ITypeLoader loader) {
    loaders[type] = loader;
  }

  IAssetStreamProvider provider;
  Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}

एक बहुत ही सरल स्ट्रीम प्रदाता केवल एक उपनिर्देशिका नाम के लिए एक निर्दिष्ट संपत्ति रूट निर्देशिका में दिखेगा type और nameएक धारा में नामित फ़ाइल के कच्चे बाइट्स को लोड करेगा और इसे वापस करेगा।

संक्षेप में, आपके पास यहां क्या है एक प्रणाली है:

  • एक वर्ग है जो कुछ प्रकार के बैकेंड स्टोरेज (एक डिस्क, एक डेटाबेस, एक नेटवर्क स्ट्रीम, जो भी हो) से कच्चे बाइट्स को पढ़ना जानता है।
  • ऐसी कक्षाएं हैं जो एक विशेष प्रकार के संसाधन में एक कच्ची बाइट स्ट्रीम को चालू करने और इसे वापस करने का तरीका जानती हैं।
  • आपके वास्तविक "एसेट लोडर" में सिर्फ उपरोक्त एब्स का एक संग्रह है और यह जानता है कि स्ट्रीम प्रदाता के आउटपुट को किस प्रकार के विशिष्ट लोडर में पाइप करना है और इस प्रकार एक ठोस संपत्ति का उत्पादन होता है। स्ट्रीम प्रदाता और प्रकार-विशिष्ट लोडर को कॉन्फ़िगर करने के तरीकों को उजागर करके, आपके पास एक ऐसी प्रणाली है जिसे वास्तविक परिसंपत्ति लोडर कोड को संशोधित किए बिना ग्राहकों (या अपने आप) द्वारा बढ़ाया जा सकता है।

कुछ चेतावनी और अंतिम नोट:

  • उपरोक्त कोड मूल रूप से C # है, लेकिन न्यूनतम प्रयास के साथ किसी भी भाषा में अनुवाद करना चाहिए। इसे सुविधाजनक बनाने के लिए मैंने बहुत सी चीज़ों को छोड़ दिया जैसे त्रुटि जाँच या ठीक से उपयोग करना IDisposableऔर अन्य मुहावरे जो अन्य भाषाओं में सीधे लागू नहीं हो सकते हैं। उन्हें पाठक के लिए होमवर्क के रूप में छोड़ दिया जाता है।

  • इसी तरह, मैं objectऊपर के रूप में ठोस संपत्ति लौटाता हूं , लेकिन यदि आप चाहें, तो आप जेनरिक या टेम्प्लेट का उपयोग कर सकते हैं या जो भी अधिक विशिष्ट ऑब्जेक्ट प्रकार का उत्पादन कर सकते हैं (आप के साथ काम करना अच्छा है)।

  • ऊपर के रूप में, मैं यहाँ बिल्कुल भी कैशिंग से नहीं निपटता। हालांकि, आप कैशिंग को आसानी से और उसी तरह की सामान्यता और विन्यास के साथ जोड़ सकते हैं। यह कोशिश करो और देखो!

  • ऐसा करने के लिए बहुत सारे और बहुत सारे और बहुत सारे तरीके हैं, और निश्चित रूप से कोई एक तरीका या आम सहमति नहीं है, यही कारण है कि आप एक खोजने में सक्षम नहीं हैं। मैंने इस उत्तर को बिना दर्द के लंबी दीवार में बदलकर विशिष्ट बिंदुओं को प्राप्त करने के लिए पर्याप्त कोड प्रदान करने की कोशिश की है। यह पहले से ही बहुत लंबा है क्योंकि यह है। यदि आपके पास स्पष्ट प्रश्न हैं, तो टिप्पणी करने के लिए स्वतंत्र महसूस करें या मुझे चैट में खोजें


1
अच्छा प्रश्न और अच्छा उत्तर जो न केवल डेटा चालित डिज़ाइन के लिए समाधान को संचालित करता है बल्कि यह भी कि डेटा चालित तरीके से सोचना
पैट्रिक ह्यूजेस

बहुत अच्छा और गहराई से जवाब। मुझे अच्छा लगता है कि आपने मेरे सवाल की व्याख्या कैसे की और मुझे बताया कि मुझे यह जानने की जरूरत है कि मैंने इसे कितना खराब तरीके से तैयार किया है। धन्यवाद! किसी भी संयोग से, क्या आप मुझे स्ट्रीम के बारे में कुछ संसाधनों की ओर संकेत कर सकते हैं?
user8363

एक "स्ट्रीम" बाइट्स या डेटा का सिर्फ एक अनुक्रम (संभवतः कोई निश्चित अंत नहीं है)। मैं विशेष रूप से सी # स्ट्रीम के बारे में सोच रहा था , लेकिन आप शायद जावा की स्ट्रीम कक्षाओं में अधिक रुचि रखते हैं - हालांकि मुझे चेतावनी दी जाती है कि मुझे बहुत अधिक जावा पता नहीं है, ताकि उपयोग करने के लिए एक आदर्श वर्ग न हो।

धाराएँ आमतौर पर स्टेटफुल होती हैं, जिसमें किसी दिए गए स्ट्रीम ऑब्जेक्ट में आम तौर पर धारा के भीतर एक मौजूदा रीड या राइट पोजीशन होती है, और आप जो भी IO करते हैं, वह उस स्थिति से होता है - इसीलिए मैंने उन्हें ऊपर एसेट इंटरफेस के इनपुट के रूप में उपयोग किया, क्योंकि वे अनिवार्य रूप से कह रहे हैं "यहां कुछ कच्चे डेटा और कहां से पढ़ना शुरू करना है, इससे पढ़ें और अपनी बात करें।"

यह दृष्टिकोण SOLID और OOP दोनों के कुछ मुख्य सिद्धांतों का सम्मान करता है । वाहवाही।
एडम नाइलर
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.