วิธีหนึ่งสามารถใช้โมดูล C ++ แบบถอดเปลี่ยนได้?


39

เวลาในการทำซ้ำอย่างรวดเร็วเป็นกุญแจสำคัญในการพัฒนาเกมมากกว่ากราฟิกและเครื่องยนต์ที่มีคุณสมบัติมากมายในความคิดของฉัน ไม่น่าแปลกใจนักพัฒนาขนาดเล็กจำนวนมากเลือกภาษาสคริปต์

วิธี Unity 3D ในการหยุดเกมและแก้ไขเนื้อหาและรหัสจากนั้นดำเนินการต่อและให้การเปลี่ยนแปลงมีผลในทันทีเป็นสิ่งที่ยอดเยี่ยมสำหรับเรื่องนี้ คำถามของฉันคือมีใครใช้ระบบที่คล้ายกันในเครื่องมือเกม C ++?

ฉันได้ยินมาว่าเอ็นจิ้นระดับไฮเอนด์บางตัวทำได้ แต่ฉันสนใจที่จะค้นหาว่ามีวิธีในเครื่องยนต์หรือเกมที่ปลูกในบ้านหรือไม่

เห็นได้ชัดว่ามีการแลกเปลี่ยนและฉันไม่สามารถจินตนาการได้ว่าคุณสามารถคอมไพล์โค้ดของคุณใหม่ได้ในขณะที่เกมหยุดชั่วคราวให้โหลดใหม่และทำงานภายใต้ทุกสถานการณ์หรือทุกแพลตฟอร์ม

แต่อาจเป็นไปได้สำหรับการโปรแกรม AI และโมดูลตรรกะระดับง่าย ไม่ใช่ว่าฉันต้องการทำในโครงการใด ๆ ในระยะสั้น (หรือระยะยาว) แต่ฉันอยากรู้

คำตอบ:


26

เป็นไปได้แน่นอนที่จะทำแม้ว่าจะไม่ใช่รหัส C ++ ทั่วไปของคุณ คุณจะต้องสร้างไลบรารี่สไตล์ C ที่คุณสามารถลิงค์และรีโหลดตอนรันไทม์ เพื่อให้เป็นไปได้ห้องสมุดจะต้องมีสถานะทั้งหมดภายในตัวชี้ทึบแสงซึ่งสามารถให้กับห้องสมุดเมื่อโหลดใหม่

Timothy Farrar พูดถึงวิธีการของเขา:

"สำหรับการพัฒนารหัสจะถูกคอมไพล์เป็นไลบรารีโปรแกรมโหลดเดอร์ขนาดเล็กทำสำเนาของไลบรารีและโหลดสำเนาไลบรารีเพื่อเรียกใช้โปรแกรมโปรแกรมจะจัดสรรข้อมูลทั้งหมดเมื่อเริ่มต้นและใช้ตัวชี้เดียวเพื่ออ้างอิงข้อมูลใด ๆ ฉันใช้ข้อมูลแบบโกลบอลยกเว้นข้อมูลแบบอ่านอย่างเดียวในขณะที่โปรแกรมกำลังรันไลบรารี่ดั้งเดิมสามารถทำการคอมไพล์ใหม่ได้จากนั้นในการกดปุ่มครั้งเดียวโปรแกรมกลับไปยังตัวโหลดและส่งกลับตัวชี้ข้อมูลตัวเดียว โหลดสำเนาและส่งผ่านตัวชี้ข้อมูลไปยังรหัสใหม่จากนั้นเอ็นจินจะดำเนินการต่อจากจุดที่ค้างไว้ " ( แหล่งต้นฉบับตอนนี้พร้อมใช้งานผ่านเว็บเก็บถาวร )


ฉันชอบสิ่งนี้กับคำตอบของแบลร์เนื่องจากคุณตอบคำถามจริงๆ
Jonathan Dickinson

15

Hot-swapping เป็นปัญหาที่ยากมากในการแก้ไขด้วยรหัสไบนารี่ แม้ว่ารหัสของคุณจะถูกวางไว้ในไลบรารีลิงก์แบบไดนามิกที่แยกจากกันรหัสของคุณจำเป็นต้องตรวจสอบให้แน่ใจว่าไม่มีการอ้างอิงโดยตรงไปยังฟังก์ชั่นในหน่วยความจำ (เนื่องจากที่อยู่ของฟังก์ชั่นสามารถเปลี่ยนแปลงได้ สิ่งนี้หมายความว่าไม่ได้ใช้ฟังก์ชั่นเสมือนจริงและสิ่งใดก็ตามที่ใช้พอยน์เตอร์ของฟังก์ชั่นก็สามารถทำได้ผ่านตารางส่ง

มีขนดก จะดีกว่าถ้าใช้ภาษาสคริปต์สำหรับรหัสที่ต้องการการวนซ้ำอย่างรวดเร็ว

ที่ทำงานฐานรหัสปัจจุบันของเราคือการผสมผสานระหว่าง C ++ และ Lua Lua ไม่ได้เป็นส่วนเล็ก ๆ ในโครงการของเราเช่นกัน - มันเกือบจะแยก 50/50 เราได้ดำเนินการโหลด Lua แบบทันทีทันใดเพื่อให้คุณสามารถเปลี่ยนรหัสบรรทัดโหลดใหม่และดำเนินต่อไป ในความเป็นจริงคุณสามารถทำเช่นนี้เพื่อแก้ไขข้อผิดพลาดความผิดพลาดที่เกิดขึ้นในรหัส Lua โดยไม่ต้องเริ่มเกมใหม่!


3
จุดสำคัญอีกอย่างคือแม้ว่าคุณจะไม่ได้ตั้งใจที่จะเก็บระบบไว้ในสคริปต์ แต่ถ้าคุณคาดว่าจะเริ่มต้นซ้ำ ๆ มันอาจจะยังคงสมเหตุสมผลเมื่อเริ่มต้นใน Lua จากนั้นย้ายไปที่ C ++ เมื่อคุณต้องการ ประสิทธิภาพพิเศษและอัตราการวนซ้ำได้ช้าลง ข้อเสียเปรียบที่เห็นได้ชัดคือที่นี่คุณจบการย้ายรหัส แต่หลายครั้งที่การใช้เหตุผลถูกต้องคือส่วนที่ยาก - รหัสเกือบจะเขียนตัวเองเมื่อคุณเข้าใจส่วนนั้น
Logan Kincaid

15

(คุณอาจต้องการทราบเกี่ยวกับคำว่า"การปะของลิง" หรือ "การต่อยเป็ด"หากไม่มีสิ่งใดนอกจากภาพจิตที่มีอารมณ์ขัน)

นอกเหนือจากนั้น: หากเป้าหมายของคุณคือการลดเวลาการทำซ้ำสำหรับการเปลี่ยนแปลง "พฤติกรรม" ลองใช้แนวทางบางอย่างที่จะทำให้คุณได้รับประโยชน์สูงสุดจากที่นั่น

(นี่จะออกไปสักหน่อยแทนเจนต์ แต่ฉันสัญญาว่ามันจะกลับมา!)

  • เริ่มต้นด้วยข้อมูลและเริ่มต้นเล็ก ๆ : โหลดซ้ำที่ขอบเขต ("ระดับ" หรือสิ่งที่คล้ายกัน) จากนั้นใช้วิธีการของคุณในการใช้งานฟังก์ชั่นระบบปฏิบัติการเพื่อรับการแจ้งเตือนการเปลี่ยนแปลงไฟล์หรือโพลเป็นประจำ
  • (สำหรับคะแนนโบนัสและเวลาในการโหลดลดลง (อีกครั้งลดเวลาการทำซ้ำ) ดูในการอบข้อมูล )
  • สคริปต์เป็นข้อมูลและอนุญาตให้คุณทำซ้ำพฤติกรรม หากคุณใช้ภาษาสคริปต์ตอนนี้คุณมีการแจ้งเตือน / ความสามารถในการโหลดสคริปต์เหล่านั้นตีความหรือเรียบเรียงใหม่ นอกจากนี้คุณยังสามารถขอล่ามของคุณไปที่คอนโซลในเกมซ็อกเก็ตเครือข่ายหรือสิ่งที่คล้ายกันเพื่อเพิ่มความยืดหยุ่นของรันไทม์
  • โค้ดอาจเป็นข้อมูลด้วยเช่นกันคอมไพเลอร์ของคุณอาจรองรับการซ้อนทับไลบรารีที่ใช้ร่วมกัน DLLs หรือสิ่งที่คล้ายกัน ดังนั้นตอนนี้คุณสามารถเลือกเวลา "ปลอดภัย" เพื่อยกเลิกการโหลดและโหลดโอเวอร์เลย์หรือ DLL ไม่ว่าจะด้วยตนเองหรือโดยอัตโนมัติ คำตอบอื่น ๆ มีรายละเอียดที่นี่ โปรดทราบว่าตัวแปรบางอย่างอาจทำให้ยุ่งเหยิงกับการเข้ารหัสลายเซ็นต์บิต, NX (ไม่มีการดำเนินการ) บิตหรือกลไกความปลอดภัยที่คล้ายกัน
  • พิจารณาลึก versioned บันทึกโหลด /ระบบ หากคุณสามารถบันทึกและกู้คืนสถานะของคุณได้อย่างมั่นคงแม้ในขณะที่ต้องเผชิญกับการเปลี่ยนแปลงของรหัสคุณสามารถปิดเกมของคุณและเริ่มต้นใหม่ด้วยตรรกะใหม่ ณ จุดเดียวกัน พูดง่ายกว่าทำเสร็จ แต่สามารถทำได้และทำได้ง่ายกว่าและพกพาง่ายกว่าเปลี่ยนหน่วยความจำเพื่อเปลี่ยนคำแนะนำ
  • ทั้งนี้ขึ้นอยู่กับโครงสร้างและชะตาของเกมของคุณคุณอาจจะสามารถที่จะทำบันทึกและการเล่น หากการบันทึกนั้นอยู่เหนือ "คำสั่งเกม" (ลองนึกถึงเกมไพ่ตัวอย่าง) คุณสามารถเปลี่ยนรหัสการแสดงผลทั้งหมดที่คุณต้องการและเล่นการบันทึกซ้ำเพื่อดูการเปลี่ยนแปลงของคุณ สำหรับบางเกมนี่เป็น "ง่าย" เหมือนกับการบันทึกพารามิเตอร์เริ่มต้นบางอย่าง (เช่นการสุ่มเมล็ด) จากนั้นผู้ใช้จะดำเนินการ สำหรับบางคนมันมีความซับซ้อนมากขึ้น
  • ทำให้ความพยายามที่จะลดเวลาในการรวบรวม เมื่อใช้ร่วมกับระบบการบันทึก / โหลดหรือการบันทึก / การเล่นข้างต้นหรือแม้กระทั่งการซ้อนทับหรือ DLLs สิ่งนี้อาจลดการตอบสนองของคุณมากกว่าสิ่งอื่นใด

ประเด็นเหล่านี้มีประโยชน์หลายประการแม้ว่าคุณจะไม่สามารถโหลดซ้ำข้อมูลหรือรหัสได้

สนับสนุนเกร็ดเล็กเกร็ดน้อย:

บนพีซี RTS ขนาดใหญ่ (ทีม 120 คนส่วนใหญ่เป็น C ++) มีระบบการบันทึกสถานะที่ล้ำลึกอย่างไม่น่าเชื่อที่ใช้สำหรับวัตถุประสงค์อย่างน้อยสามประการ:

  • บันทึก "ตื้น" ไม่ได้ถูกป้อนลงในดิสก์ แต่เป็นเครื่องมือ CRC เพื่อให้แน่ใจว่าเกมแบบผู้เล่นหลายคนยังคงอยู่ในการจำลองขั้นตอนล็อคหนึ่งซีอาร์ซีทุก 10-30 เฟรม; สิ่งนี้ทำให้มั่นใจได้ว่าไม่มีใครโกงและจับข้อผิดพลาด desync ไม่กี่เฟรมในภายหลัง
  • หากเกิดข้อผิดพลาด desync แบบผู้เล่นหลายคนการบันทึกที่มีความลึกเป็นพิเศษจะดำเนินการทุกเฟรมและป้อนอีกครั้งไปยังโปรแกรม CRC แต่คราวนี้เอ็นจิ้น CRC จะสร้าง CRC จำนวนมากแต่ละอันสำหรับแบตช์ขนาดเล็ก ในลักษณะนี้สามารถบอกคุณได้อย่างชัดเจนว่าส่วนใดของรัฐที่เริ่มแตกต่างจากเฟรมสุดท้าย เราพบความแตกต่าง "โหมดจุดลอยตัวเริ่มต้น" ที่น่ารังเกียจระหว่างโปรเซสเซอร์ AMD และ Intel ที่ใช้สิ่งนี้
  • การบันทึกความลึกปกติอาจไม่ได้รับการบันทึกเช่นกรอบภาพเคลื่อนไหวที่แน่นอนที่หน่วยของคุณกำลังเล่น แต่จะได้รับตำแหน่งสุขภาพ ฯลฯ ของหน่วยทั้งหมดของคุณช่วยให้คุณสามารถบันทึกและเล่นต่อได้ตลอดเวลาในระหว่างการเล่นเกม

ฉันใช้บันทึก / เล่นที่กำหนดไว้แล้วในเกมการ์ด C ++ และ Lua สำหรับ DS เราเชื่อมต่อกับ API ที่เราออกแบบมาสำหรับ AI (ด้าน C ++) และบันทึกการกระทำของผู้ใช้และ AI ทั้งหมด เราใช้ฟังก์ชั่นนี้ในเกม (เพื่อให้เล่นซ้ำสำหรับผู้เล่น) แต่ยังเพื่อวินิจฉัยปัญหา: เมื่อมีความผิดพลาดหรือพฤติกรรมแปลก ๆ สิ่งที่เราต้องทำก็คือรับไฟล์บันทึกและเล่นในบิลด์ debug

ฉันยังได้ใช้การซ้อนทับมากกว่าสองสามครั้งและเรารวมเข้ากับระบบ "สร้างไดเรกทอรีนี้โดยอัตโนมัติและอัปโหลดเนื้อหาใหม่ไปยังอุปกรณ์พกพา" สิ่งที่เราต้องทำคือปล่อยให้ cutscene / level / อะไรก็ตามกลับมาและไม่เพียง แต่ข้อมูลใหม่ (sprites, เลย์เอาต์เลเวล, ฯลฯ ) จะโหลด แต่ยังมีโค้ดใหม่ในโอเวอร์เลย์ด้วย โชคไม่ดีที่การพกพาอุปกรณ์พกพารุ่นใหม่ ๆ มีจำนวนมากขึ้นเนื่องจากกลไกการป้องกันการคัดลอกและการป้องกันการแฮ็กที่ใช้รหัสเป็นพิเศษ เรายังคงทำเพื่อ lua สคริปต์แม้ว่า

ท้ายสุด แต่ไม่ท้ายสุด: คุณสามารถ (และในสถานการณ์ที่เฉพาะเจาะจงเล็ก ๆ น้อย ๆ ) ฉันสามารถเจาะรูเป็ดได้โดยการสั่งการ opcodes โดยตรง วิธีนี้ใช้ได้ผลดีที่สุดถ้าคุณอยู่บนแพลตฟอร์มคงที่และคอมไพเลอร์ แต่เนื่องจากเกือบจะไม่สามารถทำให้เกิดข้อผิดพลาดได้มากและถูก จำกัด ในสิ่งที่คุณสามารถทำได้สำเร็จอย่างรวดเร็ว มันจะสอนคุณเกี่ยวกับสถาปัตยกรรมชุดคำสั่งของคุณอย่างรวดเร็ว


6

คุณสามารถทำอะไรบางอย่างเช่นโมดูลการแลกเปลี่ยนที่รันไทม์การใช้งานโมดูลเป็น dynamic link libraries (หรือ shared library บน UNIX) และใช้ dlopen () และ dlsym () เพื่อโหลดฟังก์ชั่นแบบ dinamically จากไลบรารี

สำหรับ Windows รายการเทียบเท่าคือ LoadLibrary และ GetProcAddress

นี่คือวิธีเซลเซียสและมีข้อผิดพลาดบางอย่างโดยใช้มันใน C ++ คุณสามารถอ่านเกี่ยวกับที่นี่


คู่มือนั้นเป็นกุญแจสำคัญในการสร้างห้องสมุดแบบถอดเปลี่ยนได้ขอบคุณ
JqueryToAddNumbers

4

หากคุณใช้ Visual Studio C ++ คุณสามารถหยุดและคอมไพล์รหัสของคุณใหม่ภายใต้สถานการณ์บางอย่าง Visual Studio สนับสนุนการแก้ไขและดำเนินการต่อ แนบกับเกมของคุณในดีบักเกอร์ทำให้หยุดที่เบรกพอยต์แล้วปรับเปลี่ยนรหัสหลังจุดพักของคุณ หากคุณต้องการบันทึกจากนั้นดำเนินการต่อ Visual Studio จะพยายามรวบรวมรหัสอีกครั้งแทรกรหัสดังกล่าวลงในโปรแกรมเรียกใช้ที่ทำงานอยู่และดำเนินการต่อ หากทุกอย่างเป็นไปด้วยดีการเปลี่ยนแปลงที่คุณทำจะมีผลกับเกมที่รันอยู่โดยไม่ต้องทำรอบการคอมไพล์ อย่างไรก็ตามสิ่งต่อไปนี้จะหยุดการทำงาน:

  1. การเปลี่ยนฟังก์ชั่นโทรกลับจะทำงานไม่ถูกต้อง E&C ทำงานโดยการเพิ่มรหัสใหม่และเปลี่ยนตารางการโทรให้ชี้ไปที่บล็อกใหม่ สำหรับการเรียกกลับและสิ่งใดก็ตามที่ใช้พอยน์เตอร์ของฟังก์ชั่นมันจะยังคงดำเนินการการโทรเก่าที่ไม่ได้แก้ไข ในการแก้ไขปัญหานี้คุณสามารถมีฟังก์ชั่นโทรกลับ wrapper ที่เรียกใช้ฟังก์ชันคงที่
  2. การแก้ไขไฟล์ส่วนหัวแทบจะไม่ทำงานเลย สิ่งนี้ถูกออกแบบมาเพื่อแก้ไขการเรียกใช้ฟังก์ชันจริง
  3. โครงสร้างภาษาต่าง ๆ จะทำให้มันล้มเหลวอย่างลึกลับ จากประสบการณ์ส่วนตัวของฉันการประกาศล่วงหน้าสิ่งต่าง ๆ เช่น enums มักจะทำเช่นนี้

ฉันใช้การแก้ไขและดำเนินการต่อเพื่อปรับปรุงความเร็วของสิ่งต่าง ๆ เช่นการทำซ้ำ UI สมมติว่าคุณมี UI การทำงานที่สร้างขึ้นทั้งหมดในรหัสยกเว้นว่าคุณสลับลำดับการวาดของกล่องสองกล่องโดยไม่ตั้งใจดังนั้นคุณจึงไม่เห็นอะไรเลย โดยการเปลี่ยนรหัส 2 บรรทัดสดคุณสามารถช่วยตัวเองวงจร / สร้าง / ทดสอบ 20 นาทีเพื่อตรวจสอบการแก้ไข UI เล็กน้อย

นี่ไม่ใช่วิธีการแก้ปัญหาที่สมบูรณ์สำหรับสภาพแวดล้อมการผลิตและฉันได้พบวิธีที่ดีที่สุดในการย้ายตรรกะของคุณให้มากที่สุดเท่าที่จะเป็นไปได้ในไฟล์ข้อมูลจากนั้นทำให้สามารถโหลดไฟล์ข้อมูลเหล่านั้นได้


รอบการคอมไพล์ใดใช้เวลา 20 นาที?!? ฉันอยากจะยิงตัวเอง
JqueryToAddNumbers

3

อย่างที่คนอื่น ๆ พูดกันมันเป็นปัญหาที่ยากมากการเชื่อมโยง C ++ แบบไดนามิก แต่มันเป็นปัญหาที่แก้ไขแล้ว - คุณอาจเคยได้ยินชื่อ COM หรือหนึ่งในชื่อทางการตลาดที่ใช้กับมันในช่วงหลายปีที่ผ่านมา: ActiveX

COM มีชื่อที่ไม่ดีเล็กน้อยจากมุมมองของนักพัฒนาเนื่องจากอาจเป็นความพยายามอย่างมากในการใช้งานส่วนประกอบ C ++ ที่แสดงการทำงานของพวกเขาโดยใช้ (แม้ว่านี่จะทำให้ง่ายขึ้นด้วย ATL - ไลบรารีเทมเพลต ActiveX) จากมุมมองของผู้บริโภคมันมีชื่อที่ไม่ดีเนื่องจากแอปพลิเคชันที่ใช้ตัวอย่างเช่นการฝังสเปรดชีต Excel ในเอกสาร Word หรือไดอะแกรม Visio ในสเปรดชีต Excel มีแนวโน้มที่จะเกิดความผิดพลาดเล็กน้อยในแต่ละวัน และนั่นก็เป็นประเด็นเดียวกัน - แม้จะมีคำแนะนำทั้งหมดที่ Microsoft นำเสนอ COM / ActiveX / OLE คือ / ยากที่จะทำให้ถูกต้อง

ฉันจะเน้นว่าเทคโนโลยีของ COM นั้นไม่ได้เลวร้ายอย่างแท้จริง ก่อนอื่น DirectX ใช้ส่วนต่อประสาน COM เพื่อแสดงให้เห็นถึงฟังก์ชั่นการใช้งานและใช้งานได้ดีพอ ๆ กับแอพพลิเคชั่นมากมายที่ฝัง Internet Explorer โดยใช้ตัวควบคุม ActiveX ประการที่สองเป็นวิธีที่ง่ายที่สุดวิธีหนึ่งในการลิงก์รหัส C ++ แบบไดนามิกส่วนติดต่อ COM นั้นเป็นเพียงคลาสเสมือนจริง A ถึงแม้ว่ามันจะมี IDL เช่น CORBA แต่คุณไม่ได้ถูกบังคับให้ใช้โดยเฉพาะอย่างยิ่งถ้าส่วนต่อประสานที่คุณกำหนดนั้นถูกใช้ภายในโครงการของคุณเท่านั้น

หากคุณไม่ได้เขียนสำหรับ Windows อย่าคิดว่า COM นั้นไม่คุ้มค่าที่จะพิจารณา Mozilla ติดตั้งใหม่ใน codebase (ใช้ในเบราว์เซอร์ Firefox) เพราะพวกเขาต้องการวิธีในการสร้างรหัส C ++


2

มีการดำเนินการรันไทม์รวบรวม c ++ รหัสเพลย์เป็นที่นี่ นอกจากนี้ฉันรู้ว่ามีเอ็นจิ้นเกมอย่างน้อยหนึ่งรายการที่ทำเช่นเดียวกัน มันเป็นเรื่องยาก แต่ทำได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.