gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

Beginners > Virtual Functions

#170285 - Azenris - Tue Sep 15, 2009 7:48 pm

Can classes use virtual functions on the NDS, I have an enemy class that inherits from mobile.


Code:
// =================================================================================
// CLASS: CMobile
class CMobile
{
public:
   CMobile(CScreenManager *pScreen, u16 tileID, u8 graphicID, u16 health, location loc, u8 unit, bool canRotate);   // constructor

   virtual void Update(void);               // update the mobile


Code:
/ =================================================================================
// CLASS: CEnemy
class CEnemy: public CMobile
{
public:
   CEnemy(CScreenManager *pScreen, u16 tileID, u8 graphicID, u16 health, location loc, bool canRotate);   // constructor

   void Update(void);


Code:
// =================================================================================
// Update:
void CEnemy::Update(void)
{
   // check if the enemy is still and has control
   if (HasControl() && !IsMoving())
   {      
      // depending on the ai, select a move
      switch (m_ai)
      {
      case ENEMY_BODRID:  AI_BODRID(this);     break;
      case ENEMY_BLUEY:   AI_BLUEY(this);      break;
      case ENEMY_ANGRY:   AI_ANGRY(this);      break;
      case ENEMY_WIMPY:   AI_WIMPY(this);      break;
      }
   }

   // update mobile
   CMobile::Update();
}


I wanted to add additional things to the enemy, such as AI but still have the basics of movement, which the basic mobile deals with.

But whenever I use the keyword virtual the screens go upside down and are non responsive.
_________________
My Homebrew Games

#170286 - vuurrobin - Tue Sep 15, 2009 10:24 pm

there is no problems with using polymorphism on the ds.

you should either implement CMobile::Update(), or make it a pure virtual function, like this:

Code:
virtual void Update(void) = 0;

_________________
my blog:
http://vuurrobin.100webcustomers.com/

#170287 - Azenris - Tue Sep 15, 2009 10:35 pm

CMobile::Update exists

Code:
// =================================================================================
// Update:
void CMobile::Update(void)
{
   // check if the mobile is moving, if so, update it on its movement
   // and check whether its time to stop moving
   if (m_status & MOBILE_STATUS_MOVING)
   {
      switch (m_direction)
      {
      case DIR_LEFT:
         if ((m_mapCoord.x * 16) != m_screenCoord.x)
            m_screenCoord.x -= m_movementSpeed;
         else
            m_status &= ~MOBILE_STATUS_MOVING;

         break;

      case DIR_RIGHT:
         if ((m_mapCoord.x * 16) != m_screenCoord.x)
            m_screenCoord.x += m_movementSpeed;
         else
            m_status &= ~MOBILE_STATUS_MOVING;

         break;

      case DIR_UP:
         if ((m_mapCoord.y * 16) != m_screenCoord.y)
            m_screenCoord.y -= m_movementSpeed;
         else
            m_status &= ~MOBILE_STATUS_MOVING;
         
         break;

      case DIR_DOWN:
         if ((m_mapCoord.y * 16) != m_screenCoord.y)
            m_screenCoord.y += m_movementSpeed;
         else
            m_status &= ~MOBILE_STATUS_MOVING;

         break;
      }

      if (! (m_status & MOBILE_STATUS_MOVING))
      {
         DEBUG_OUT(" Mobile Stopped Moving\n");
         m_pScreen->UpdateMobileToData(this);

         for (u8 layer = 0; layer < LAYERS_PER_SCREEN; ++layer)
         {
            if (g_pGame->GetTileData(m_pScreen->GetTileIDAt(layer, m_lastMapPoint))->ExitFrom != NULL)
               g_pGame->GetTileData(m_pScreen->GetTileIDAt(layer, m_lastMapPoint))->ExitFrom(this);

            if (g_pGame->GetTileData(m_pScreen->GetTileIDAt(layer, m_mapPoint))->SteppedOn != NULL)
               g_pGame->GetTileData(m_pScreen->GetTileIDAt(layer, m_mapPoint))->SteppedOn(this);   
         }
      }
   }

   // check if the mobile is visible or not
   if (IsVisible())
   {
      // if the mobile can rotate it will need an offset applying
      if (m_canRotate)
         m_pScreen->GetSpriteManager()->MoveSprite(m_graphicID, m_screenCoord.x + ANGLE_OFFSET_X, m_screenCoord.y + ANGLE_OFFSET_Y);
      else
         m_pScreen->GetSpriteManager()->MoveSprite(m_graphicID, m_screenCoord.x, m_screenCoord.y);
   }
   else
   {
      m_pScreen->GetSpriteManager()->MoveSprite(m_graphicID, SCREEN_WIDTH, 0);
   }
}


This code works and the problem only happens when I turn it virtual. So virtuals are ok, hmm, atleast now i know they should, will try some more things.
_________________
My Homebrew Games

#170317 - Azenris - Wed Sep 16, 2009 5:04 pm

Has anyone else experienced this? What am I doing wrong :/ I tried adding virtual destructors but doesnt help, not that I did anything in them anyway.

I tried to search gbadev and noone else ever complained about this.

I tested it on iDeasS Emulator, this is the one where the screen freezes. Then I tested it on no$gba, on this one it said "The rom-image has crashed".
Then i tested it on hardware, the game looks like its working, as in things are running, but the base Update is only called and the derived classes update is ignored.

I have no idea what to do :/
_________________
My Homebrew Games

#170319 - Drovor - Wed Sep 16, 2009 5:59 pm

I use class inheritance and polymorphism extensively in my project. You would get errors if you made any syntactical violations so you probably have some logic errors.

You said the problem only occurs when you add virtual so perhaps your derived class update has bugs which are only being executed after you declare the method virtual.

In a case like this:
Code:

CMobile* mobile = new CEnemy();
mobile->update()


CMobile's update method is called. If you add 'virtual' then CEnemy's update method is called.

#170322 - Azenris - Wed Sep 16, 2009 6:27 pm

i tried CEnemy::Update as empty and still the same problems are caused.

The enemy update wouldnt even run till the game starts anyway, the program is crashing the second the main menu is shown (and no enemies are even made)
_________________
My Homebrew Games

#170324 - Miked0801 - Wed Sep 16, 2009 6:29 pm

Are you perhaps out of RAM / Stack space? Or really close? Adding virtual does make the compiler allocate a bit more space for the VTab. Then boom as soon as pushs and pops spank your memory.

Another possibility is a RAM overrun bug that already exists and happens to smash your VTable after it's created.

#170325 - Azenris - Wed Sep 16, 2009 7:02 pm

Im not sure how to check that, but I just wrote this, maybe im doing something wrong here. I basically just made a tiny program just to see it working.

Code:

// =================================================================================
// INCLUDES:
#include <nds.h>
#include <stdio.h>
#include <vector>

class CMobile
{
public:
   CMobile(int hp) : m_hp(hp) { }
   virtual ~CMobile(void) {}

   virtual void Update(void)
   {
      iprintf("mobile\n");
   }

private:
   int m_hp;
};

class CEnemy : public CMobile
{
public:
   CEnemy(int hp) : CMobile(hp) { }
   ~CEnemy(void) {}

   void Update(void)
   {
      iprintf("enemy\n");
   }

private:
};

// =================================================================================
int main(void)
{
   consoleDemoInit();

   iprintf("testing...\n");

   CMobile *enemy = new CEnemy(15);
   enemy->Update();

   while(1)
   {
      swiWaitForVBlank();
   }


   return 0;
}


OUTPUT:
testing...


however if I remove the virtual its

OUTPUT:
testing...
mobile
_________________
My Homebrew Games

#170326 - Drovor - Wed Sep 16, 2009 7:16 pm

Maybe there's something wrong with your Makefile.

I copy/pasted your example into a new copy of the "arm9" template (changed main.c to main.cpp) and no$gba printed out:

"testing...
enemy"

#170327 - Azenris - Wed Sep 16, 2009 7:19 pm

I got mine from the examples, the arm7 + arm9 one. Only a few small changes, it there an option I should enable for it ?

this is the arm9 one

Code:
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif

include $(DEVKITARM)/ds_rules

#---------------------------------------------------------------------------------
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
# DATA is a list of directories containing binary files
# all directories are relative to this makefile
#---------------------------------------------------------------------------------
BUILD      :=   build
SOURCES   :=   source data/extra
INCLUDES   :=   include data/include data/extra
DATA      :=   data data/bin_files

#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH   :=   -mthumb -mthumb-interwork

CFLAGS   :=   -g -Wall -O2\
          -march=armv5te -mtune=arm946e-s -fomit-frame-pointer\
         -ffast-math \
         $(ARCH)

CFLAGS   +=   $(INCLUDE) -DARM9
CXXFLAGS   :=   $(CFLAGS) -fno-rtti -fno-exceptions

ASFLAGS   :=   -g $(ARCH) -march=armv5te -mtune=arm946e-s

LDFLAGS   =   -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)

#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS   :=   -lnds9
 
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS   :=   $(LIBNDS)
 
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
 
export ARM9BIN   :=   $(TOPDIR)/$(TARGET).arm9
export ARM9ELF   :=   $(CURDIR)/$(TARGET).arm9.elf
export DEPSDIR := $(CURDIR)/$(BUILD)

export VPATH   :=   $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
               $(foreach dir,$(DATA),$(CURDIR)/$(dir))
 
CFILES      :=   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES   :=   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES      :=   $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES   :=   $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
 
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
   export LD   :=   $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
   export LD   :=   $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------

export OFILES   :=   $(addsuffix .o,$(BINFILES)) \
               $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
 
export INCLUDE   :=   $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
         $(foreach dir,$(LIBDIRS),-I$(dir)/include) \
         -I$(CURDIR)/$(BUILD)
 
export LIBPATHS   :=   $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
 
.PHONY: $(BUILD) clean
 
#---------------------------------------------------------------------------------
$(BUILD):
   @[ -d $@ ] || mkdir -p $@
   @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
 
#---------------------------------------------------------------------------------
clean:
   @echo clean ...
   @rm -fr $(BUILD) *.elf *.nds* *.bin
 
 
#---------------------------------------------------------------------------------
else
 
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(ARM9BIN)   :   $(ARM9ELF)
   @$(OBJCOPY) -O binary $< $@
   @echo built ... $(notdir $@)

$(ARM9ELF)   :   $(OFILES)
   @echo linking $(notdir $@)
   @$(LD)  $(LDFLAGS) $(OFILES) $(LIBPATHS) $(LIBS) -o $@

#---------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#---------------------------------------------------------------------------------
%.bin.o   :   %.bin
#---------------------------------------------------------------------------------
   @echo $(notdir $<)
   @$(bin2o)

-include $(DEPSDIR)/*.d
 
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------


the main make file
Code:
TXTLINE1   := Krexblibos
TXTLINE2   := By Robert Morley
TXTLINE3   := www.morleyx22@hotmail.com
ICON       := -b $(CURDIR)/Logo.bmp

#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif

include $(DEVKITARM)/ds_rules

export TARGET      :=   $(shell basename $(CURDIR))
export TOPDIR      :=   $(CURDIR)


.PHONY: $(TARGET).arm7 $(TARGET).arm9

#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
all: $(TARGET).nds

#---------------------------------------------------------------------------------
$(TARGET).nds   :   $(TARGET).arm7 $(TARGET).arm9
   ndstool   -c $(TARGET).nds -7 $(TARGET).arm7 -9 $(TARGET).arm9 $(ICON) "$(TXTLINE1);$(TXTLINE2);$(TXTLINE3);"

#---------------------------------------------------------------------------------
$(TARGET).arm7   : arm7/$(TARGET).elf
$(TARGET).arm9   : arm9/$(TARGET).elf

#---------------------------------------------------------------------------------
arm7/$(TARGET).elf:
   $(MAKE) -C arm7
   
#---------------------------------------------------------------------------------
arm9/$(TARGET).elf:
   $(MAKE) -C arm9

#---------------------------------------------------------------------------------
clean:
   $(MAKE) -C arm9 clean
   $(MAKE) -C arm7 clean
   rm -f $(TARGET).nds $(TARGET).arm7 $(TARGET).arm9

_________________
My Homebrew Games

#170328 - Drovor - Wed Sep 16, 2009 7:42 pm

I didn't have to change anything except main.c->main.cpp and paste in your test, I'm using devkitARM r26 on linux installed with this Linux script.

It sounds like you have something to debug now at least... get that test program working then try your project.

#170329 - Azenris - Wed Sep 16, 2009 9:39 pm

I got it working. New makefile + I added some NULL pointer checking to alot of my pointers, still I dont see how adding the virtual caused the problem. meh, its all good now ty for your help.
_________________
My Homebrew Games