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.

DS development > C# on DS?

#73379 - Kalagaraz - Sat Feb 25, 2006 5:17 am

Anyone know if anyone will ever get .NET languages to being able to compile to work on DS? Sounds unlikely to me, since .NET relys heavily on windows. But can't hurt to see if anyone of you heard anything.

#73384 - tepples - Sat Feb 25, 2006 5:48 am

The CLR has no particular dependency on Microsoft Windows (see Mono Project), but it might depend on the presence of more than 4 MB of RAM.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#73388 - knight0fdragon - Sat Feb 25, 2006 6:12 am

.net is suppose to be the competition to java's VM, it was designed to be cross platformed
_________________
http://www.myspace.com/knight0fdragonds

MK DS FC: Dragon 330772 075464
AC WW FC: Anthony SamsClub 1933-3433-9458
MPFH: Dragon 0215 4231 1206

#73404 - Mollusk - Sat Feb 25, 2006 10:56 am

it's probably much more memory-consuming than C or C++, so I would guess it wouldn't be possible.
And even if it worked, you wouldn't have all the .net framework to use with it, so it would be kind of useless
_________________
PAlib official forum : http://www.palib.info
PAlib official tutorials: http://www.palib.info/wiki
Updates, help, code examples, tutorials, etc...

#73461 - TheMikaus - Sat Feb 25, 2006 8:52 pm

Alternative solution would be to make a program that converted the C# to c/c++ compatable with devkitarm and just compile that.

Or port a compiler that just takes C# and turns it into the binary. (edit)

#73497 - tepples - Sun Feb 26, 2006 2:55 am

TheMikaus wrote:
Or port a compiler that just takes C# and turns it into the binary.

By the time you've done that, you've almost implemented a JIT compiler for MSIL bytecode.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#78946 - Chris Holmes - Mon Apr 10, 2006 8:35 pm

http://www.mono-project.com/Mono:ARM

Would it be possible to use the Mono project to run C# code on the DS? As the website says:
Quote:
Mono's port to the ARM uses the FPA mode for generating floating point instructions. We would like to add support for VFP and soft-float eventually, but we are not currently working on that.


If Mono works on the Nokia 770, which is powered by a 220 mHz Arm9, then it's possible that the DS will be able to handle it. Admittedly, the Nokia device is about 4 times faster and has a _lot_ more memory available, but, as long as the C# app doesn't require most of the standard C# libraries, I wonder if C# would work for the DS, at least for the Arm9 core.

Does this make the task of C# on the DS seem possible?

Chris

#78987 - dXtr - Tue Apr 11, 2006 12:45 am

Chris Holmes wrote:

Does this make the task of C# on the DS seem possible?


with lots of work probably possible.. but not practical.
_________________
go back to coding and stop screaming wolf :)

#78991 - sajiimori - Tue Apr 11, 2006 1:04 am

There's nothing particularly impractical about it, though I don't personally like the language very much. I'd miss RAII. (Or is there a good way to do it in C#?)

#78995 - dXtr - Tue Apr 11, 2006 1:28 am

it's impractical in that it's overbloated and it's memory usage isn't really optimized for these type of devices (well that's my point of view atleast)
_________________
go back to coding and stop screaming wolf :)

#78996 - sajiimori - Tue Apr 11, 2006 1:46 am

That's an implementation issue, not a language issue.

#79011 - tepples - Tue Apr 11, 2006 2:55 am

If memory usage is an "implementation issue", then a machine with less than 5 MB of RAM is likely a poor platform for "implementations" of the C# language and the rest of the .NET Framework.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#79014 - Chris Holmes - Tue Apr 11, 2006 3:02 am

RAII? Quite frankly, resource freeing is a task best suited for an automated tool, ala, a compiler.

I'd really love to get C# compiling on the DS because it's just a better language to have to deal with day in and day out than C++. If nothing else, I'll be thrilled with compile errors that make sense.

Also, tepples, C# compiles down quite efficiently, and really, while the entire .NET framework isn't tiny, as long as I've got the 2.0 generics, collections, and IO classes, that's really all I'll need on the DS.

And dxtr, while C# isn't "designed" for low-power work, the compact framework is. Hmm.. actually, the compact framework supports ARM directly... I'll have to look into that as well.

Chris

#79017 - sajiimori - Tue Apr 11, 2006 3:11 am

Garbage collection is all well and good, but I'd like to see a compiler decide when it should delete an arrow that's being held by an archer that just got killed, or when to delete a particle effect that's surrounding a powerup.

Last edited by sajiimori on Tue Apr 11, 2006 3:12 am; edited 1 time in total

#79018 - tepples - Tue Apr 11, 2006 3:11 am

Chris Holmes wrote:
while the entire .NET framework isn't tiny, as long as I've got the 2.0 generics, collections, and IO classes, that's really all I'll need on the DS.

Heavy use of collections on a small system without virtual memory might result in nasty heap fragmentation unless the framework supports a compacting GC. I/O classes might not help much either given the lack of FPU and the difference between the PC interaction model and that of a handheld device.

That said, I wish you luck.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#79022 - gladius - Tue Apr 11, 2006 3:27 am

.NET on the DS is certainly possible. But for it to be efficient you have to pull some tricks such as not compiling to IL and sacrificing some of the more powerful reflection abilities (dynamic methods for one). You'd also need to use unsafe methods to access memory directly, but that's no big deal, things are just a little less pretty :).

It'd be a fun little tech demo, that's for sure.

I'm not sure if using an Mono would be viable, it looks a bit heavy for the DS. Rotor might be a better starting point.

#79080 - Chris Holmes - Tue Apr 11, 2006 2:35 pm

Thanks for the tip about Rotor. I actually hadn't heard of that project, and that's definitely something that seems useful.

And..
Quote:
Garbage collection is all well and good, but I'd like to see a compiler decide when it should delete an arrow that's being held by an archer that just got killed, or when to delete a particle effect that's surrounding a powerup.


Code:
foreach( Arrow a in Arrows ) {
  foreach( Enemy e in Enemies ) {
    if( Collision( a, e ) ) {
      HandleCollision( a, e );
      Arrows.Remove( a ); // No, you can't actually modify a collection while you're enumerating through it.
    }
  }
}


I would assume that would remove the last reference to the arrow, so it could be garbage collected or compile-time automatically deleted. It would just require another stage in the compile process to trace through the code and such. If this couldn't be done, then there wouldn't be tools that can examine code for memory leaks and such.

sajiimori, You are technically right that there are a few cases that require the programmer to destruct an object or to use a garbage collector, but most frees could be handled automatically by the compiler.

Chris

#79103 - sajiimori - Tue Apr 11, 2006 6:52 pm

Chris, the example I gave was an archer holding an arrow. The arrow should be removed from the world when the archer dies, not when the runtime feels like collecting it.

Sure, I could write it like this:
Code:
void Archer::die()
{
  if(mHeldArrow != null)
    mHeldArrow.kill();
}

...but that's exactly what I don't want to do by hand. Would the compiler have automatically destroyed the arrow? Maybe. Or maybe the arrow was also in a list of actors to be updated every tick. Or maybe the code will later change in some subtle way that will keep the object alive.

Regardless, the author will have to manually verify that the compiler is destroying the object, and more importantly, future readers will have trouble proving to themselves that the code is correct.

In C++, I'd do it like this:
Code:
boost::scoped_ptr<Arrow> mHeldArrow;

Done.

Is there a way to get this kind of behavior in C#?


Last edited by sajiimori on Tue Apr 11, 2006 7:06 pm; edited 1 time in total

#79109 - Chris Holmes - Tue Apr 11, 2006 7:06 pm

As far as I know, no, you can't do that in C#.

But like you said, what if someone changes the behavior and that arrow is held in a list of actors being updated every frame? I'm not entirely sure that your usage of scoped_ptr is correct. You can't pass ownership of a scoped_ptr around because it is allocated on the stack, so you couldn't use that construct for an object that is held in two places.

And quite frankly, I'd rather write the code with a functioning garbage collector and a smart compiler handling all memory freeing.

Honestly, the biggest draw to C# or Java or Perl or any other higher level language is productivity. Developing in C++ is just a nightmare for productivity, and really, the performance boost you get is usually overrated. And if you're using C#, you can always drop into unsafe code and start manipulating pointers directly. If you're trying to build a rapid prototype though, high level languages make the task much easier.

Chris

#79113 - sajiimori - Tue Apr 11, 2006 7:13 pm

Glad you noticed regarding multiple ownership! Here is the actual line from my current project, verbatim:
Code:
ScopedWeakPtr<Projectile> mHeldProjectile;

ScopedWeakPtr deletes the object when it goes out of scope, but is also nullified if someone else deletes the object first.

The performance boost is a red herring. I'm interested in writing code that is easy to prove correct and hard to get wrong. C++ is clearly not the ideal language for that, but C# seems to lack a feature that I am not willing to give up.

#79125 - tepples - Tue Apr 11, 2006 7:52 pm

Chris Holmes wrote:
Code:
      Arrows.Remove( a ); // No, you can't actually modify a collection while you're enumerating through it.

You can in Java, by calling the iterator's remove() method. Why can't you in C#?
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#79158 - gladius - Tue Apr 11, 2006 10:30 pm

sajimori: I must admit, I can't see what you are driving for. The garbage collector will find all objects that no longer have any live references to them and collect them. Perhaps not immediately when it goes out of scope, but this is only a problem if there is code doing stuff in the destructor which is an unwise idea in any case.

In your example, if the archer is killed and he is the only object that has a reference to the arrow, the arrow will be GC'ed when the archer is.

If you need determinstic destruction there are patterns that enable that kind of thing (such as IDisposable), but they are mainly intended for handling unmanaged resources that the GC can not handle for you.

#79167 - sajiimori - Tue Apr 11, 2006 11:43 pm

It's actually really helpful to do things in a destructor, and I'll show you why with a somewhat paradoxical example.

I'm gonna post some more code from my current project, but somehow I don't think it will break any NDAs:
Code:
Enemy::~Enemy()
{
}

That's a complicated destructor that does lots of work! Didn't notice?

If he was holding an arrow, the arrow will be removed from the scene immediately, not on the next GC cycle.

If he's in a list of active enemies that needs to be searched when you press the "select target" button, he'll automatically be removed from that list.

If an AI is holding a weak reference to the enemy, that reference will automatically be nullified.

If the enemy has subscribed to be notified when a particular event happens, it will automatically be removed from the list of subscribers.

All these things are guaranteed to happen immediately and automatically because all the necessary work happens in the destructors for the scoped arrow pointer, the active list entry, the weak pointer target, and the subscription object.

The reason my Enemy destructor is "empty" is because everything it needs to do has already been written.

#79199 - gladius - Wed Apr 12, 2006 2:37 am

I think we're drifting off-topic here, but what are forums for anyway ;).

And what order do those things happen in your destructor? I certainly can't tell from that function, I have to go look at the order of declaration of the members. Complicating matters is the fact that the destructor of one of those classes could affect my overall destruction state. ie. some child declared later on declares on the destructor effects of one of the children decalred before it.

Then another programmer comes along and refactors the class a bit and all of a sudden things start crashing.

That's more than a little scary for building your entire object framework on. Exceptions also pose interesting problems, what happens if the event handler removal code throws from within some destructor - very, very bad news.

At that point I'd be happier writing an explicit Die() function.

RAII is great for a lot of things, but using it everywhere (it's amazing for handling resources though) can lead to more problems than it solves in my experience. Of course there are also associated drawbacks for the C# way of doing things. But overall I think the advantage of a GC outweighs the drawbacks.

#79201 - sajiimori - Wed Apr 12, 2006 3:10 am

Whoa there, cowboy. :) The destructors are order-independent and no-throw. If they weren't, it would be a bug in one or more of the utilities that are in use.

Furthermore, because I've abstracted over the cleanup code, fixing such a bug would fix all client code, whereas your manual solution allows for the possibility of the error appearing again.

Note that nothing I've said in this thread is an argument against GC, but rather an argument in favor of RAII, and against the idea that GC makes RAII obsolete.

Edit: I just can't stress enough how important it is that the reader just knows that all the correct cleanup is happening because the cleanup is an invariant of the utilities that are in use. Their interface simply does not allow for the possibility of improper or untimely cleanup.

#79218 - gladius - Wed Apr 12, 2006 5:54 am

Well, you say the destructors are order independent, but that is not something the compiler can verify (nor programmers all the time - for example a class that adds itself to a static event list on construction and removes itself on destruction - when an event is fired to that class the order of calls could certainly matter). That also puts some pretty funky requirements on the dev sometimes.

No-throw is verifyable, but then you have to write try{} catch {} in all of your destructors if they call any methods that could throw, which is not super pretty.

But for all my blathering, I agree, RAII is nice when available. I don't miss it when I work in C# though.

/back to the regularly scheduled programming ;)

#79221 - sajiimori - Wed Apr 12, 2006 6:28 am

Quote:
Well, you say the destructors are order independent, but that is not something the compiler can verify...
So, compilers can't verify order-independence, and they can't verify correctness of repetitive hand-written cleanup code. Then our choice is between personally verifying formalized cleanup code once, or personally verifying informal cleanup code every time it's written.
Quote:
for example a class that adds itself to a static event list on construction and removes itself on destruction - when an event is fired to that class the order of calls could certainly matter
You mean if the destructor fires the signal in mid-execution? That situation seems to require special attention whether you use RAII or not. I'd consider refactoring.
Quote:
No-throw is verifyable, but then you have to write try{} catch {} in all of your destructors if they call any methods that could throw, which is not super pretty.
Everything my destructors do is no-throw.

#79316 - dXtr - Wed Apr 12, 2006 10:03 pm

sajiimori wrote:
Garbage collection is all well and good, but I'd like to see a compiler decide when it should delete an arrow that's being held by an archer that just got killed, or when to delete a particle effect that's surrounding a powerup.


it's not really the compilers task to handle runtime events..
_________________
go back to coding and stop screaming wolf :)

#79330 - sajiimori - Wed Apr 12, 2006 11:06 pm

I was using the phrase "I'd like to see X" in a manner intended to express extreme doubt, as in, "I'd like to see you do that again without a parachute!"

That said, your statement about "handling runtime events" is ambiguous. Compilers produce code that can be statically determined to correctly handle an event that will occur at runtime.

So when I speak of compilers deciding to delete an object, that's shorthand for generating code that will eventually delete an object at runtime, just like my RAII code does.

#79355 - gladius - Thu Apr 13, 2006 3:30 am

sajiimori wrote:
So, compilers can't verify order-independence, and they can't verify correctness of repetitive hand-written cleanup code. Then our choice is between personally verifying formalized cleanup code once, or personally verifying informal cleanup code every time it's written.
I agree that writing the same clean-up code over and over again is error prone and not an optimal situation, however with good design this kind of problem can be minimized. C++ (or any language with determnistic destructors) certainly has an advantage here. C++/CLI has support for determistic destructors, so you could write the wrapper classes in that and use them in C# trivially (the wonders of .NET :). See http://www.geocities.com/Jeff_Louie/deterministic_destructors.htm for a good talk on this.

sajiimori wrote:
You mean if the destructor fires the signal in mid-execution? That situation seems to require special attention whether you use RAII or not. I'd consider refactoring.
No, the destructor doesn't fire a signal. I meant there is some static event list sitting around. The events in the list may have interdependencies (unintentionally or otherwise) that could be broken by changing order of initialization. It's certainly not a killer example, just one more thing to think about.

#79362 - sajiimori - Thu Apr 13, 2006 4:33 am

Unfortunately, the C# side of that pattern relies on the "using" keyword which only works when the lifetime of an object is tied to a lexical scope. The functionality I described earlier could not be implemented with the "using" keyword.

I'm not really clear on the event list example. If you make it more concrete, maybe I'll have a solution.

But speaking generally, if I run into a situation where ~A() must be run before ~B(), I make it so ~B() notifies A that it should not do the dependent operation. Then I abstract over the solution so I never have to think about it again.

Our WeakPtr class is an example of abstracting over such a solution. If A holds a WeakPtr<B> instead of a plain B*, A's reference will automatically be nullified in ~B().

#79449 - gladius - Thu Apr 13, 2006 7:47 pm

Right, you can't do it cleanly in C#, which is why you implement a ScopedObject<> equivalent in C++/CLI, which you then can use easily in C# (just link to the C++/CLI assembly with the ScopedObject implementation). Then you get the nice scoped destruction behavior in C#, without having to use the using keyword.

#79451 - sajiimori - Thu Apr 13, 2006 8:06 pm

I think I'm missing something. If I use ScopedObject in C#, doesn't that just mean ~ScopedObject() will be run when the GC collects it?
Code:
void CSharpType::method1()
{
  ScopedObject obj = new ScopedObject();
  // ~ScopedObject() guaranteed to be run immediately?
}

void CSharpType::method2()
{
  List<ScopedObject> list;
  list.add(new ScopedObject);
  list.clear();  // Contents destroyed immediately?
}

#79482 - gladius - Thu Apr 13, 2006 11:51 pm

You are completely correct. After actually going in and playing around with this a little, from C# you still need to manually call the Dispose() method (or use using { }) to get things to work as the C++/CLI destructor is just a Dispose() function.

So, the solution to RAII on .net then becomes use C++/CLI which has it's drawbacks. But at least it's possible in a GC'ed language :).

#79518 - sajiimori - Fri Apr 14, 2006 2:42 am

In Scheme, you can write it in a way that looks like "using" in C#, except continuations let you fit the whole world (or any piece of it) inside of any lexical scope. :)
Code:
void createEnemy(Function restOfGame(Function enemyDied()))
{
  using(Enemy e = new Enemy())
  {
    callWithCurrentContinuation(restOfGame);
  }
}