Jump to page: 1 2
Thread overview
Formatting a string on a variadic parameter function without using GC
Mar 02, 2016
David G. Maziero
Mar 02, 2016
Mike Parker
Mar 02, 2016
Mike Parker
Mar 02, 2016
David G. Maziero
Mar 02, 2016
David G. Maziero
Mar 02, 2016
David G. Maziero
Mar 02, 2016
David G. Maziero
Mar 02, 2016
David G. Maziero
Mar 02, 2016
cym13
Mar 02, 2016
Luis
Mar 02, 2016
Mike Parker
March 02, 2016
Hi. I think it's the first time I post here.
I've been flirting with D for some time, but now I decided to start a little project.

Consider the following function:

void RenderText( FontBMP font, int x, int y, const char* text )
{
	SDL_Rect rect1, rect2;
	rect2.x = x;
	rect2.y = y;
	rect2.w = font.width;
	rect2.h = font.height;
	rect1.w = font.width;
	rect1.h = font.height;
	for( int r=0; text[r]!='\0'; ++r )
	{
		char letter = text[r];
		rect1.x = font.width*(letter%font.horizontal_count);
		rect1.y = font.height*(letter/font.horizontal_count);
		if( letter>=33 ) SDL_RenderCopy( g_renderer, font.texture, &rect1, &rect2 );
		rect2.x += font.spacing_x;
		if( letter==10 )
		{
			rect2.x = x;
			rect2.y += font.spacing_y;
		}
	}
}

And the use code:

	char[256] text;
	sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", player.position, player.speed );
	RenderText( font, 0, 0, cast(char*)text );

I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));" but I want to avoid GC entirely.
The ideal scenario is to have something like "void RenderText( /*etc*/, const char* fmt, ... )" but I can't seem to figure a way to pass the variadic parameters to sprintf.

Any ideas?
Thanks in advance.
March 02, 2016
On Wednesday, 2 March 2016 at 01:39:13 UTC, David G. Maziero wrote:

> Consider the following function:
>
> void RenderText( FontBMP font, int x, int y, const char* text )
> {

> 	for( int r=0; text[r]!='\0'; ++r )
> 	{

You're asking for trouble here. There's no guarantee that any D string is going to be null terminated. String literals are, but beyond that, all bets are off.


>
> 	char[256] text;
> 	sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", player.position, player.speed );

Instead of sprintf, look into using std.format.sformat [1] (see below). It's equivalent to std.format.format, but allows you to provide a buffer.


> 	RenderText( font, 0, 0, cast(char*)text );

It's considered bad form to cast an array to a pointer like this. If you need a pointer to an array, just use the ptr property: text.ptr. Always be aware of the null terminator situation, though, when passing to C.

>
> I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));"

Won't compile without using the .ptr property (or casting, which again, you shouldn't do). Moreover, you run into the null terminator problem. "FPS :" will be null terminated because it's a literal, but the string produced by concatentating it with the result of to!string would not be.

> but I want to avoid GC entirely.
> The ideal scenario is to have something like "void RenderText( /*etc*/, const char* fmt, ... )" but I can't seem to figure a way to pass the variadic parameters to sprintf.
>
> Any ideas?

With sformat, something like this (not tested):

import std.format;
char buf[1024];
auto fmt = sformat(buf, "Foo: %s", 42);
if(fmt.length < buf.length) buf[fmt.length] = 0;
else buf[$-1] = 0;
RenderText(blah, blah, buf.ptr);

sformat returns a slice of the buffer containing the formatted string. You can use its length to guarantee the buffer is null terminated.


March 02, 2016
On Wednesday, 2 March 2016 at 04:12:13 UTC, Mike Parker wrote:

> char buf[1024];

Ugh. And the proper declaration in D:

char[1024] buf;

March 02, 2016
Yes, I'm aware of the null-termination thing. I might have pasted code that I already changed.

But I already messed with sformat, and it seems that it does use the GC. I've put @nogc in RenderText, and the compiler says sformat uses GC, so I don't know.

But the thing is, I don't want to build the string before calling RenderText, I want RenderText to do it.
March 02, 2016
I forgot to add that the "RenderText(font,0,0,"FPS: "~to!string(fps));" was an older version where it wasn't const char *, but string. And I was using a foreach, so no null-termination. But that's beyond the point of not using GC.
March 02, 2016
I figured out what I wanted. Thanks for your answer Mike, sorry to bother you though.

Here's the result:

void RenderText( FontBMP font, int x, int y, const char* text, ... )
{
	SDL_Rect rect1, rect2;
	rect2.x = x;
	rect2.y = y;
	rect2.w = font.width;
	rect2.h = font.height;
	rect1.w = font.width;
	rect1.h = font.height;

	char[256] buff;
	va_list	ap;
	va_start( ap, text );
	sprintf( buff.ptr, text, ap );
	va_end( ap );

	for( int r=0; buff[r]!='\0'; ++r )
	{
		char letter = buff[r];
		rect1.x = font.width*(letter%font.horizontal_count);
		rect1.y = font.height*(letter/font.horizontal_count);
		if( letter>=33 ) SDL_RenderCopy( g_renderer, font.texture, &rect1, &rect2 );
		rect2.x += font.spacing_x;
		if( letter==10 )
		{
			rect2.x = x;
			rect2.y += font.spacing_y;
		}
	}
}

Since the "string" is built by sprintf, it'll be null-terminated.

Sorry for posting without doing more research. I didn't realise I could use va_start/va_end just like in C.

March 02, 2016
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero wrote:
> 	char[256] buff;
> 	va_list	ap;
> 	va_start( ap, text );
> 	sprintf( buff.ptr, text, ap );
> 	va_end( ap );

Sorry again, where it reads "sprintf" should be "vsprintf".
March 02, 2016
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero wrote:
> void RenderText( FontBMP font, int x, int y, const char* text,

Just one more correction for future reference, RenderText should be

> extern(C) void RenderText...

in order for it to work correctly with va_start/etc.

March 02, 2016
On Wednesday, 2 March 2016 at 05:04:37 UTC, David G. Maziero wrote:
> I figured out what I wanted. Thanks for your answer Mike, sorry to bother you though.
>
> [...]

Even null-terminated be sure to profile your loop, for isn't garanteed to be faster than foreach at all and foreach is definitely safer.
March 02, 2016
On Wednesday, 2 March 2016 at 04:12:13 UTC, Mike Parker wrote:
> On Wednesday, 2 March 2016 at 01:39:13 UTC, David G. Maziero wrote:
>
>> Consider the following function:
>>
>> void RenderText( FontBMP font, int x, int y, const char* text )
>> {
>
>> 	for( int r=0; text[r]!='\0'; ++r )
>> 	{
>
> You're asking for trouble here. There's no guarantee that any D string is going to be null terminated. String literals are, but beyond that, all bets are off.
>
>
>>
>> 	char[256] text;
>> 	sprintf( &text[0], "Player: pos:%.3f - speed:%.3f", player.position, player.speed );
>
> Instead of sprintf, look into using std.format.sformat [1] (see below). It's equivalent to std.format.format, but allows you to provide a buffer.
>
>
>> 	RenderText( font, 0, 0, cast(char*)text );
>
> It's considered bad form to cast an array to a pointer like this. If you need a pointer to an array, just use the ptr property: text.ptr. Always be aware of the null terminator situation, though, when passing to C.
>
>>
>> I could simply do "RenderText(font,0,0,"FPS: "~to!string(fps));"
>
> Won't compile without using the .ptr property (or casting, which again, you shouldn't do). Moreover, you run into the null terminator problem. "FPS :" will be null terminated because it's a literal, but the string produced by concatentating it with the result of to!string would not be.
>

Read https://dlang.org/spec/arrays.html#strings and try to use std.string.toStringz (http://dlang.org/phobos/std_string.html#.toStringz)


« First   ‹ Prev
1 2